From 4b88c316ed90c56e83e2aee266561833b36fc37d Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 23 Sep 2024 18:50:16 -0400 Subject: [PATCH] fix(YouTube): Show video chapter titles without clipping when overlay buttons are enabled (#3674) --- api/revanced-patches.api | 15 +- .../settings/BaseSettingsResourcePatch.kt | 7 +- .../copyvideourl/CopyVideoUrlBytecodePatch.kt | 4 +- .../copyvideourl/CopyVideoUrlResourcePatch.kt | 6 +- .../interaction/downloads/DownloadsPatch.kt | 4 +- .../downloads/DownloadsResourcePatch.kt | 6 +- .../sponsorblock/SponsorBlockBytecodePatch.kt | 63 ++------ .../sponsorblock/SponsorBlockResourcePatch.kt | 49 +----- .../BottomControlsResourcePatch.kt | 68 +------- .../PlayerControlsBytecodePatch.kt | 143 +++++++++++++---- .../PlayerControlsResourcePatch.kt | 146 ++++++++++++++++++ .../BottomControlsInflateFingerprint.kt | 19 --- ...rprint.kt => ControlsOverlayVisibility.kt} | 5 +- .../OverlayViewInflateFingerprint.kt | 17 ++ .../PlayerBottomControlsInflateFingerprint.kt | 10 ++ ...layerControlsIntegrationHookFingerprint.kt | 15 ++ .../PlayerTopControlsInflateFingerprint.kt | 13 ++ .../speed/button/PlaybackSpeedButtonPatch.kt | 4 +- .../PlaybackSpeedButtonResourcePatch.kt | 6 +- .../kotlin/app/revanced/util/BytecodeUtils.kt | 28 +++- .../kotlin/app/revanced/util/ResourceUtils.kt | 47 +++++- .../drawable/revanced_yt_copy.xml | 2 +- .../drawable/revanced_yt_copy_timestamp.xml | 2 +- .../youtube_controls_bottom_ui_container.xml | 35 ++++- .../drawable/revanced_yt_download_button.xml | 2 +- .../youtube_controls_bottom_ui_container.xml | 21 ++- .../revanced_playback_speed_dialog_button.xml | 2 +- .../youtube_controls_bottom_ui_container.xml | 21 ++- .../drawable/revanced_sb_adjust.xml | 2 +- .../drawable/revanced_sb_backward.xml | 2 +- .../drawable/revanced_sb_compare.xml | 2 +- .../drawable/revanced_sb_edit.xml | 2 +- .../drawable/revanced_sb_forward.xml | 2 +- .../drawable/revanced_sb_logo.xml | 2 +- .../drawable/revanced_sb_publish.xml | 2 +- .../drawable/revanced_sb_voting.xml | 2 +- .../host/layout/youtube_controls_layout.xml | 12 +- .../revanced_ic_sc_brightness_auto.xml | 2 +- .../revanced_ic_sc_brightness_manual.xml | 2 +- .../drawable/revanced_ic_sc_volume_mute.xml | 2 +- .../drawable/revanced_ic_sc_volume_normal.xml | 2 +- 41 files changed, 530 insertions(+), 266 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch.kt delete mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt rename src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/{PlayerControlsVisibilityFingerprint.kt => ControlsOverlayVisibility.kt} (70%) create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/OverlayViewInflateFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerBottomControlsInflateFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsIntegrationHookFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerTopControlsInflateFingerprint.kt diff --git a/api/revanced-patches.api b/api/revanced-patches.api index f94f26b011..8185c9e021 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -2000,13 +2000,19 @@ public final class app/revanced/patches/youtube/misc/playercontrols/BottomContro public final class app/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch : app/revanced/patcher/patch/BytecodePatch { public static final field INSTANCE Lapp/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch; - public static field showPlayerControlsFingerprintResult Lapp/revanced/patcher/fingerprint/MethodFingerprintResult; public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V - public final fun getShowPlayerControlsFingerprintResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult; + public final fun initializeBottomControl (Ljava/lang/String;)V public final fun initializeControl (Ljava/lang/String;)V public final fun injectVisibilityCheckCall (Ljava/lang/String;)V - public final fun setShowPlayerControlsFingerprintResult (Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;)V +} + +public final class app/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch : app/revanced/patcher/patch/ResourcePatch, java/io/Closeable { + public static final field INSTANCE Lapp/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch; + public final fun addBottomControls (Ljava/lang/String;)V + public fun close ()V + public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V + public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V } public final class app/revanced/patches/youtube/misc/playeroverlay/PlayerOverlaysHookPatch : app/revanced/patcher/patch/BytecodePatch { @@ -2174,6 +2180,8 @@ public final class app/revanced/util/BytecodeUtilsKt { public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I public static final fun indexOfFirstWideLiteralInstructionValue (Lcom/android/tools/smali/dexlib2/iface/Method;J)I public static final fun indexOfFirstWideLiteralInstructionValueOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I + public static final fun indexOfFirstWideLiteralInstructionValueReversed (Lcom/android/tools/smali/dexlib2/iface/Method;J)I + public static final fun indexOfFirstWideLiteralInstructionValueReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I public static final fun indexOfIdResource (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun indexOfIdResourceOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V @@ -2201,6 +2209,7 @@ public final class app/revanced/util/ResourceUtilsKt { public static final fun copyXmlNode (Ljava/lang/String;Lapp/revanced/patcher/util/DomFileEditor;Lapp/revanced/patcher/util/DomFileEditor;)Ljava/lang/AutoCloseable; public static final fun doRecursively (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V public static final fun forEachChildElement (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V + public static final fun insertFirst (Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)V public static final fun iterateXmlNodeChildren (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V } diff --git a/src/main/kotlin/app/revanced/patches/shared/misc/settings/BaseSettingsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/shared/misc/settings/BaseSettingsResourcePatch.kt index f1e488370d..1570cc3dc0 100644 --- a/src/main/kotlin/app/revanced/patches/shared/misc/settings/BaseSettingsResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/misc/settings/BaseSettingsResourcePatch.kt @@ -9,6 +9,7 @@ import app.revanced.patches.shared.misc.settings.preference.IntentPreference import app.revanced.util.ResourceGroup import app.revanced.util.copyResources import app.revanced.util.getNode +import app.revanced.util.insertFirst import org.w3c.dom.Node import java.io.Closeable @@ -47,11 +48,7 @@ abstract class BaseSettingsResourcePatch( // It may be necessary to ask for the desired resourceValue in the future. AddResourcesPatch("values", resource) }.let { preferenceNode -> - if (prepend && firstChild != null) { - insertBefore(preferenceNode, firstChild) - } else { - appendChild(preferenceNode) - } + insertFirst(preferenceNode) } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlBytecodePatch.kt index 5d740aa59c..cea3988c28 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlBytecodePatch.kt @@ -51,8 +51,8 @@ object CopyVideoUrlBytecodePatch : BytecodePatch(emptySet()) { override fun execute(context: BytecodeContext) { BUTTONS_DESCRIPTORS.forEach { descriptor -> - PlayerControlsBytecodePatch.initializeControl("$descriptor->initializeButton(Landroid/view/View;)V") - PlayerControlsBytecodePatch.injectVisibilityCheckCall("$descriptor->changeVisibility(Z)V") + PlayerControlsBytecodePatch.initializeBottomControl(descriptor) + PlayerControlsBytecodePatch.injectVisibilityCheckCall(descriptor) } } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlResourcePatch.kt index 23476080b9..a53216c283 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoUrlResourcePatch.kt @@ -5,7 +5,7 @@ import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.all.misc.resources.AddResourcesPatch import app.revanced.patches.shared.misc.settings.preference.SwitchPreference -import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch +import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.util.ResourceGroup import app.revanced.util.copyResources @@ -13,7 +13,7 @@ import app.revanced.util.copyResources @Patch( dependencies = [ SettingsPatch::class, - BottomControlsResourcePatch::class, + PlayerControlsResourcePatch::class, AddResourcesPatch::class ] ) @@ -34,6 +34,6 @@ internal object CopyVideoUrlResourcePatch : ResourcePatch() { ) ) - BottomControlsResourcePatch.addControls("copyvideourl") + PlayerControlsResourcePatch.addBottomControls("copyvideourl") } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt index b22a146166..6c8119fe93 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt @@ -58,8 +58,8 @@ object DownloadsPatch : BytecodePatch( private const val BUTTON_DESCRIPTOR = "Lapp/revanced/integrations/youtube/videoplayer/ExternalDownloadButton;" override fun execute(context: BytecodeContext) { - PlayerControlsBytecodePatch.initializeControl("$BUTTON_DESCRIPTOR->initializeButton(Landroid/view/View;)V") - PlayerControlsBytecodePatch.injectVisibilityCheckCall("$BUTTON_DESCRIPTOR->changeVisibility(Z)V") + PlayerControlsBytecodePatch.initializeBottomControl(BUTTON_DESCRIPTOR) + PlayerControlsBytecodePatch.injectVisibilityCheckCall(BUTTON_DESCRIPTOR) // Main activity is used to launch downloader intent. MainActivityFingerprint.resultOrThrow().mutableMethod.apply { diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsResourcePatch.kt index 24285e53b1..8884a296fe 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsResourcePatch.kt @@ -9,14 +9,14 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.TextPreference -import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch +import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.util.ResourceGroup import app.revanced.util.copyResources @Patch( dependencies = [ - BottomControlsResourcePatch::class, + PlayerControlsResourcePatch::class, SettingsPatch::class, AddResourcesPatch::class, ], @@ -42,6 +42,6 @@ internal object DownloadsResourcePatch : ResourcePatch() { ResourceGroup("drawable", "revanced_yt_download_button.xml"), ) - BottomControlsResourcePatch.addControls("downloads") + PlayerControlsResourcePatch.addBottomControls("downloads") } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt index e41d0e4214..85aad5fd01 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch.kt @@ -10,7 +10,6 @@ import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod -import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.AppendTimeFingerprint import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.ControlsOverlayFingerprint import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.RectangleFieldInvalidatorFingerprint @@ -26,7 +25,10 @@ import app.revanced.patches.youtube.video.information.VideoInformationPatch import app.revanced.patches.youtube.video.videoid.VideoIdPatch import app.revanced.util.exception import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.* +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.Instruction +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.formats.Instruction35c import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference @@ -169,59 +171,14 @@ object SponsorBlockBytecodePatch : BytecodePatch( break } - /* - * Voting & Shield button - */ - val controlsMethodResult = PlayerControlsBytecodePatch.showPlayerControlsFingerprintResult - - val controlsLayoutStubResourceId = - ResourceMappingPatch["id", "controls_layout_stub"] - val zoomOverlayResourceId = - ResourceMappingPatch["id", "video_zoom_overlay_stub"] - - methods@ for (method in controlsMethodResult.mutableClass.methods) { - val instructions = method.implementation?.instructions!! - instructions@ for ((index, instruction) in instructions.withIndex()) { - // search for method which inflates the controls layout view - if (instruction.opcode != Opcode.CONST) continue@instructions - - when ((instruction as NarrowLiteralInstruction).wideLiteral) { - controlsLayoutStubResourceId -> { - // replace the view with the YouTubeControlsOverlay - val moveResultInstructionIndex = index + 5 - val inflatedViewRegister = - (instructions[moveResultInstructionIndex] as OneRegisterInstruction).registerA - // initialize with the player overlay object - method.addInstructions( - moveResultInstructionIndex + 1, // insert right after moving the view to the register and use that register - """ - invoke-static {v$inflatedViewRegister}, $INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->initialize(Landroid/view/View;)V - invoke-static {v$inflatedViewRegister}, $INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->initialize(Landroid/view/View;)V - """, - ) - } - - zoomOverlayResourceId -> { - val invertVisibilityMethod = - context.toMethodWalker(method).nextMethod(index - 6, true).getMethod() as MutableMethod - // change visibility of the buttons - invertVisibilityMethod.addInstructions( - 0, - """ - invoke-static {p1}, $INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibilityNegatedImmediate(Z)V - invoke-static {p1}, $INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibilityNegatedImmediate(Z)V - """.trimIndent(), - ) - } - } - } - } + // Change visibility of the buttons. + PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR) + PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR) - // change visibility of the buttons - PlayerControlsBytecodePatch.injectVisibilityCheckCall("$INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibility(Z)V") - PlayerControlsBytecodePatch.injectVisibilityCheckCall("$INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibility(Z)V") + PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR) + PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR) - // append the new time to the player layout + // Append the new time to the player layout. val appendTimeFingerprintResult = AppendTimeFingerprint.result!! val appendTimePatternScanStartIndex = appendTimeFingerprintResult.scanResult.patternScanResult!!.startIndex val targetRegister = diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockResourcePatch.kt index 459e7d9d8e..04c158dd8f 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockResourcePatch.kt @@ -1,18 +1,16 @@ package app.revanced.patches.youtube.layout.sponsorblock import app.revanced.patcher.data.ResourceContext -import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.all.misc.resources.AddResourcesPatch import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch import app.revanced.patches.shared.misc.settings.preference.IntentPreference +import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.patches.youtube.misc.settings.SettingsResourcePatch import app.revanced.util.ResourceGroup import app.revanced.util.copyResources -import app.revanced.util.copyXmlNode -import app.revanced.util.inputStreamFromBundledResource @Patch( dependencies = [ @@ -60,49 +58,6 @@ internal object SponsorBlockResourcePatch : ResourcePatch() { context.copyResources("sponsorblock", resourceGroup) } - // copy nodes from host resources to their real xml files - - val hostingResourceStream = - inputStreamFromBundledResource( - "sponsorblock", - "host/layout/youtube_controls_layout.xml", - )!! - - var modifiedControlsLayout = false - val editor = context.xmlEditor["res/layout/youtube_controls_layout.xml"] - "RelativeLayout".copyXmlNode( - context.xmlEditor[hostingResourceStream], - editor, - ).also { - val document = editor.file - - val children = document.getElementsByTagName("RelativeLayout").item(0).childNodes - - // Replace the startOf with the voting button view so that the button does not overlap - for (i in 1 until children.length) { - val view = children.item(i) - - // Replace the attribute for a specific node only - if (!( - view.hasAttributes() && - view.attributes.getNamedItem( - "android:id", - ).nodeValue.endsWith("live_chat_overlay_button") - ) - ) { - continue - } - - // voting button id from the voting button view from the youtube_controls_layout.xml host file - val votingButtonId = "@+id/revanced_sb_voting_button" - - view.attributes.getNamedItem("android:layout_toStartOf").nodeValue = votingButtonId - - modifiedControlsLayout = true - break - } - }.close() - - if (!modifiedControlsLayout) throw PatchException("Could not modify controls layout") + PlayerControlsResourcePatch.addTopControls("sponsorblock") } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/BottomControlsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/BottomControlsResourcePatch.kt index 474ccf4798..b62de28726 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/BottomControlsResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/BottomControlsResourcePatch.kt @@ -3,70 +3,18 @@ package app.revanced.patches.youtube.misc.playercontrols import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.annotation.Patch -import app.revanced.patcher.util.DomFileEditor -import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch import java.io.Closeable -@Patch(dependencies = [ResourceMappingPatch::class]) +@Patch( + dependencies = [PlayerControlsBytecodePatch::class], +) +@Deprecated("Patch renamed to PlayerControlsResourcePatch", replaceWith = ReplaceWith("PlayerControlsBytecodePatch")) object BottomControlsResourcePatch : ResourcePatch(), Closeable { - internal var bottomUiContainerResourceId: Long = -1 + override fun execute(context: ResourceContext) {} - private const val TARGET_RESOURCE_NAME = "youtube_controls_bottom_ui_container.xml" - private const val TARGET_RESOURCE = "res/layout/$TARGET_RESOURCE_NAME" - - // The element to the left of the element being added. - private var lastLeftOf = "fullscreen_button" - - private lateinit var resourceContext: ResourceContext - private lateinit var targetDocumentEditor: DomFileEditor - - override fun execute(context: ResourceContext) { - resourceContext = context - targetDocumentEditor = context.xmlEditor[TARGET_RESOURCE] - - bottomUiContainerResourceId = ResourceMappingPatch["id", "bottom_ui_container_stub"] - } - - /** - * Add new controls to the bottom of the YouTube player. - * - * @param resourceDirectoryName The name of the directory containing the hosting resource. - */ fun addControls(resourceDirectoryName: String) { - val sourceDocumentEditor = resourceContext.xmlEditor[ - this::class.java.classLoader.getResourceAsStream( - "$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME", - )!!, - ] - val sourceDocument = sourceDocumentEditor.file - val targetDocument = targetDocumentEditor.file - - val targetElementTag = "android.support.constraint.ConstraintLayout" - - val sourceElements = sourceDocument.getElementsByTagName(targetElementTag).item(0).childNodes - val targetElement = targetDocument.getElementsByTagName(targetElementTag).item(0) - - for (index in 1 until sourceElements.length) { - val element = sourceElements.item(index).cloneNode(true) - - // If the element has no attributes there's no point to adding it to the destination. - if (!element.hasAttributes()) continue - - // Set the elements lastLeftOf attribute to the lastLeftOf value. - val namespace = "@+id" - element.attributes.getNamedItem("yt:layout_constraintRight_toLeftOf").nodeValue = - "$namespace/$lastLeftOf" - - // Set lastLeftOf attribute to the current element. - val nameSpaceLength = 5 - lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength) - - // Add the element. - targetDocument.adoptNode(element) - targetElement.appendChild(element) - } - sourceDocumentEditor.close() + PlayerControlsResourcePatch.addBottomControls(resourceDirectoryName) } - override fun close() = targetDocumentEditor.close() -} + override fun close() {} +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch.kt index 1cc2cfcf0f..b9de57d151 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch.kt @@ -1,65 +1,144 @@ package app.revanced.patches.youtube.misc.playercontrols -import app.revanced.util.exception import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.fingerprint.MethodFingerprintResult +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.Patch -import app.revanced.patches.youtube.shared.fingerprints.LayoutConstructorFingerprint -import app.revanced.patches.youtube.misc.playercontrols.fingerprints.BottomControlsInflateFingerprint -import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsVisibilityFingerprint +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import app.revanced.patches.youtube.misc.playercontrols.fingerprints.ControlsOverlayVisibility +import app.revanced.patches.youtube.misc.playercontrols.fingerprints.OverlayViewInflateFingerprint +import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerBottomControlsInflateFingerprint +import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsIntegrationHookFingerprint +import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerTopControlsInflateFingerprint +import app.revanced.util.alsoResolve +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueReversedOrThrow +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.iface.reference.TypeReference @Patch( description = "Manages the code for the player controls of the YouTube player.", - dependencies = [BottomControlsResourcePatch::class], + dependencies = [PlayerControlsResourcePatch::class], ) object PlayerControlsBytecodePatch : BytecodePatch( - setOf(LayoutConstructorFingerprint, BottomControlsInflateFingerprint) + setOf( + PlayerTopControlsInflateFingerprint, + PlayerBottomControlsInflateFingerprint, + OverlayViewInflateFingerprint, + PlayerControlsIntegrationHookFingerprint + ) ) { - lateinit var showPlayerControlsFingerprintResult: MethodFingerprintResult + private const val INTEGRATIONS_CLASS_DESCRIPTOR = + "Lapp/revanced/integrations/youtube/patches/PlayerControlsPatch;" - private var moveToRegisterInstructionIndex: Int = 0 - private var viewRegister: Int = 0 - private lateinit var inflateFingerprintResult: MethodFingerprintResult + private lateinit var inflateTopControlMethod: MutableMethod + private var inflateTopControlInsertIndex: Int = -1 + private var inflateTopControlRegister: Int = -1 + + private lateinit var inflateBottomControlMethod: MutableMethod + private var inflateBottomControlInsertIndex: Int = -1 + private var inflateBottomControlRegister: Int = -1 + + private lateinit var visibilityMethod: MutableMethod + private var visibilityInsertIndex: Int = 0 + + private lateinit var visibilityImmediateMethod: MutableMethod + private var visibilityImmediateInsertIndex: Int = 0 override fun execute(context: BytecodeContext) { - LayoutConstructorFingerprint.result?.let { - if (!PlayerControlsVisibilityFingerprint.resolve(context, it.classDef)) - throw LayoutConstructorFingerprint.exception - } ?: throw LayoutConstructorFingerprint.exception + fun MutableMethod.indexOfFirstViewInflateOrThrow() = + indexOfFirstInstructionOrThrow { + val reference = getReference() + reference?.definingClass == "Landroid/view/ViewStub;" && + reference.name == "inflate" + } + + PlayerBottomControlsInflateFingerprint.resultOrThrow().mutableMethod.apply{ + inflateBottomControlMethod = this + + val inflateReturnObjectIndex = indexOfFirstViewInflateOrThrow() + 1 + inflateBottomControlRegister = getInstruction(inflateReturnObjectIndex).registerA + inflateBottomControlInsertIndex = inflateReturnObjectIndex + 1 + } + + PlayerTopControlsInflateFingerprint.resultOrThrow().mutableMethod.apply { + inflateTopControlMethod = this - showPlayerControlsFingerprintResult = PlayerControlsVisibilityFingerprint.result!! + val inflateReturnObjectIndex = indexOfFirstViewInflateOrThrow() + 1 + inflateTopControlRegister = getInstruction(inflateReturnObjectIndex).registerA + inflateTopControlInsertIndex = inflateReturnObjectIndex + 1 + } - inflateFingerprintResult = BottomControlsInflateFingerprint.result!!.also { - moveToRegisterInstructionIndex = it.scanResult.patternScanResult!!.endIndex - viewRegister = - (it.mutableMethod.implementation!!.instructions[moveToRegisterInstructionIndex] as OneRegisterInstruction).registerA + ControlsOverlayVisibility.alsoResolve( + context, PlayerTopControlsInflateFingerprint + ).mutableMethod.apply { + visibilityMethod = this } + + // Hook the fullscreen close button. Used to fix visibility + // when seeking and other situations. + OverlayViewInflateFingerprint.resultOrThrow().mutableMethod.apply { + val resourceIndex = indexOfFirstWideLiteralInstructionValueReversedOrThrow( + PlayerControlsResourcePatch.fullscreenButton + ) + + val index = indexOfFirstInstructionOrThrow(resourceIndex) { + opcode == Opcode.CHECK_CAST && getReference()?.type == + "Landroid/widget/ImageView;" + } + val register = getInstruction(index).registerA + + addInstruction(index + 1, "invoke-static { v$register }, " + + "$INTEGRATIONS_CLASS_DESCRIPTOR->setFullscreenCloseButton(Landroid/widget/ImageView;)V") + } + + visibilityImmediateMethod = PlayerControlsIntegrationHookFingerprint.resultOrThrow().mutableMethod } /** - * Injects the code to change the visibility of controls. + * Injects the code to initialize the controls. * @param descriptor The descriptor of the method which should be called. */ - fun injectVisibilityCheckCall(descriptor: String) { - showPlayerControlsFingerprintResult.mutableMethod.addInstruction( - 0, - """ - invoke-static {p1}, $descriptor - """ + internal fun initializeTopControl(descriptor: String) { + inflateTopControlMethod.addInstruction( + inflateTopControlInsertIndex++, + "invoke-static { v$inflateTopControlRegister }, $descriptor->initialize(Landroid/view/View;)V" ) } /** * Injects the code to initialize the controls. - * @param descriptor The descriptor of the method which should be calleed. + * @param descriptor The descriptor of the method which should be called. */ - fun initializeControl(descriptor: String) { - inflateFingerprintResult.mutableMethod.addInstruction( - moveToRegisterInstructionIndex + 1, - "invoke-static {v$viewRegister}, $descriptor" + fun initializeBottomControl(descriptor: String) { + inflateBottomControlMethod.addInstruction( + inflateBottomControlInsertIndex++, + "invoke-static { v$inflateBottomControlRegister }, $descriptor->initializeButton(Landroid/view/View;)V" ) } + /** + * Injects the code to change the visibility of controls. + * @param descriptor The descriptor of the method which should be called. + */ + fun injectVisibilityCheckCall(descriptor: String) { + visibilityMethod.addInstruction( + visibilityInsertIndex++, + "invoke-static { p1 , p2 }, $descriptor->changeVisibility(ZZ)V" + ) + + visibilityImmediateMethod.addInstruction( + visibilityImmediateInsertIndex++, + "invoke-static { p0 }, $descriptor->changeVisibilityImmediate(Z)V" + ) + } + + + @Deprecated("Obsolete", replaceWith = ReplaceWith("initializeBottomControl")) + fun initializeControl(descriptor: String)= initializeBottomControl(descriptor) } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch.kt new file mode 100644 index 0000000000..f78bc92592 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch.kt @@ -0,0 +1,146 @@ +package app.revanced.patches.youtube.misc.playercontrols + +import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.PatchException +import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patcher.util.DomFileEditor +import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch +import app.revanced.util.copyXmlNode +import app.revanced.util.findElementByAttributeValue +import app.revanced.util.findElementByAttributeValueOrThrow +import app.revanced.util.inputStreamFromBundledResource +import org.w3c.dom.Node +import java.io.Closeable + +@Patch(dependencies = [ResourceMappingPatch::class]) +object PlayerControlsResourcePatch : ResourcePatch(), Closeable { + private const val TARGET_RESOURCE_NAME = "youtube_controls_bottom_ui_container.xml" + private const val TARGET_RESOURCE = "res/layout/$TARGET_RESOURCE_NAME" + + internal var bottomUiContainerResourceId: Long = -1L + internal var controlsLayoutStub: Long = -1L + internal var heatseekerViewstub = -1L + internal var fullscreenButton = -1L + + private lateinit var resourceContext: ResourceContext + + /** + * The element to the left of the element being added. + */ + private var bottomLastLeftOf = "@id/fullscreen_button" + private lateinit var bottomInsertBeforeNode: Node + private lateinit var bottomTargetDocumentEditor: DomFileEditor + private lateinit var bottomTargetElement : Node + + override fun execute(context: ResourceContext) { + bottomUiContainerResourceId = ResourceMappingPatch["id", "bottom_ui_container_stub"] + controlsLayoutStub = ResourceMappingPatch["id", "controls_layout_stub"] + heatseekerViewstub = ResourceMappingPatch["id", "heatseeker_viewstub"] + fullscreenButton = ResourceMappingPatch["id", "fullscreen_button"] + + resourceContext = context + bottomTargetDocumentEditor = context.xmlEditor[TARGET_RESOURCE] + val document = bottomTargetDocumentEditor.file + + bottomTargetElement = document.getElementsByTagName( + "android.support.constraint.ConstraintLayout" + ).item(0) + + bottomInsertBeforeNode = document.childNodes.findElementByAttributeValue( + "android:inflatedId", + bottomLastLeftOf + ) ?: document.childNodes.findElementByAttributeValueOrThrow( + "android:id", // Older targets use non inflated id. + bottomLastLeftOf + ) + } + + // Internal until this is modified to work with any patch (and not just SponsorBlock). + internal fun addTopControls(resourceDirectoryName: String) { + val hostingResourceStream = inputStreamFromBundledResource( + resourceDirectoryName, + "host/layout/youtube_controls_layout.xml", + )!! + + val editor = resourceContext.xmlEditor["res/layout/youtube_controls_layout.xml"] + + "RelativeLayout".copyXmlNode( + resourceContext.xmlEditor[hostingResourceStream], + editor, + ).use { + val document = editor.file + val children = document.getElementsByTagName("RelativeLayout").item(0).childNodes + + // Replace the startOf with the voting button view so that the button does not overlap + for (index in 1 until children.length) { + val view = children.item(index) + + // FIXME: This uses hard coded values that only works with SponsorBlock. + // If other top buttons are added by other patches, this code must be changed. + if (view.hasAttributes() && view.attributes.getNamedItem("android:id") + .nodeValue.endsWith("live_chat_overlay_button") + ) { + // voting button id from the voting button view from the youtube_controls_layout.xml host file + val votingButtonId = "@+id/revanced_sb_voting_button" + view.attributes.getNamedItem("android:layout_toStartOf").nodeValue = + votingButtonId + + return + } + } + } + + throw PatchException("Could not find expected xml to modify") + } + + /** + * Add new controls to the bottom of the YouTube player. + * + * @param resourceDirectoryName The name of the directory containing the hosting resource. + */ + fun addBottomControls(resourceDirectoryName: String) { + val sourceDocumentEditor = resourceContext.xmlEditor[ + this::class.java.classLoader.getResourceAsStream( + "$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME", + )!!, + ] + + val sourceElements = sourceDocumentEditor.file.getElementsByTagName( + "android.support.constraint.ConstraintLayout" + ).item(0).childNodes + + // Copy the patch layout xml into the target layout file. + for (index in 1 until sourceElements.length) { + val element = sourceElements.item(index).cloneNode(true) + + // If the element has no attributes there's no point to adding it to the destination. + if (!element.hasAttributes()) continue + + element.attributes.getNamedItem("yt:layout_constraintRight_toLeftOf").nodeValue = bottomLastLeftOf + bottomLastLeftOf = element.attributes.getNamedItem("android:id").nodeValue + + bottomTargetDocumentEditor.file.adoptNode(element) + // Elements do not need to be added in the layout order since a layout constraint is used, + // but in order is easier to make sense of while debugging. + bottomTargetElement.insertBefore(element, bottomInsertBeforeNode) + bottomInsertBeforeNode = element + } + + sourceDocumentEditor.close() + } + + override fun close() { + arrayOf( + "@id/bottom_end_container", + "@id/multiview_button", + ).forEach { + bottomTargetDocumentEditor.file.childNodes.findElementByAttributeValue( + "android:id", + it + )?.setAttribute("yt:layout_constraintRight_toLeftOf", bottomLastLeftOf) + } + + bottomTargetDocumentEditor.close() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt deleted file mode 100644 index 9480c6a1e4..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt +++ /dev/null @@ -1,19 +0,0 @@ -package app.revanced.patches.youtube.misc.playercontrols.fingerprints - -import app.revanced.patcher.extensions.or -import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch -import app.revanced.util.patch.LiteralValueFingerprint -import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode - -internal object BottomControlsInflateFingerprint : LiteralValueFingerprint( - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.SYNTHETIC, - returnType = "L", - parameters = listOf(), - opcodes = listOf( - Opcode.CHECK_CAST, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT - ), - literalSupplier = { BottomControlsResourcePatch.bottomUiContainerResourceId } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsVisibilityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/ControlsOverlayVisibility.kt similarity index 70% rename from src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsVisibilityFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/ControlsOverlayVisibility.kt index 672086f60d..6655c09c86 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsVisibilityFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/ControlsOverlayVisibility.kt @@ -4,7 +4,10 @@ import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags -internal object PlayerControlsVisibilityFingerprint : MethodFingerprint( +/** + * Resolves to the class found in [PlayerTopControlsInflateFingerprint]. + */ +internal object ControlsOverlayVisibility : MethodFingerprint( accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, returnType = "V", parameters = listOf("Z", "Z") diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/OverlayViewInflateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/OverlayViewInflateFingerprint.kt new file mode 100644 index 0000000000..21b0592e10 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/OverlayViewInflateFingerprint.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.youtube.misc.playercontrols.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch +import app.revanced.util.containsWideLiteralInstructionValue +import com.android.tools.smali.dexlib2.AccessFlags + +internal object OverlayViewInflateFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = listOf("Landroid/view/View;"), + customFingerprint = { methodDef, _ -> + methodDef.containsWideLiteralInstructionValue(PlayerControlsResourcePatch.fullscreenButton) && + methodDef.containsWideLiteralInstructionValue(PlayerControlsResourcePatch.heatseekerViewstub) + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerBottomControlsInflateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerBottomControlsInflateFingerprint.kt new file mode 100644 index 0000000000..8deab7b9e3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerBottomControlsInflateFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.misc.playercontrols.fingerprints + +import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch +import app.revanced.util.patch.LiteralValueFingerprint + +internal object PlayerBottomControlsInflateFingerprint : LiteralValueFingerprint( + returnType = "Ljava/lang/Object;", + parameters = listOf(), + literalSupplier = { PlayerControlsResourcePatch.bottomUiContainerResourceId } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsIntegrationHookFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsIntegrationHookFingerprint.kt new file mode 100644 index 0000000000..2fb86baa44 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsIntegrationHookFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.youtube.misc.playercontrols.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object PlayerControlsIntegrationHookFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, + returnType = "V", + parameters = listOf("Z"), + customFingerprint = { methodDef, classDef -> + methodDef.name == "fullscreenButtonVisibilityChanged" && + classDef.type == "Lapp/revanced/integrations/youtube/patches/PlayerControlsPatch;" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerTopControlsInflateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerTopControlsInflateFingerprint.kt new file mode 100644 index 0000000000..7b7a952c86 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerTopControlsInflateFingerprint.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.playercontrols.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object PlayerTopControlsInflateFingerprint : LiteralValueFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = listOf(), + literalSupplier = { PlayerControlsResourcePatch.controlsLayoutStub } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt index 19895fa8ac..c99e5a2005 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt @@ -32,7 +32,7 @@ object PlaybackSpeedButtonPatch : BytecodePatch(emptySet()) { SwitchPreference("revanced_playback_speed_dialog_button"), ) - PlayerControlsBytecodePatch.initializeControl("$SPEED_BUTTON_CLASS_DESCRIPTOR->initializeButton(Landroid/view/View;)V") - PlayerControlsBytecodePatch.injectVisibilityCheckCall("$SPEED_BUTTON_CLASS_DESCRIPTOR->changeVisibility(Z)V") + PlayerControlsBytecodePatch.initializeBottomControl(SPEED_BUTTON_CLASS_DESCRIPTOR) + PlayerControlsBytecodePatch.injectVisibilityCheckCall(SPEED_BUTTON_CLASS_DESCRIPTOR) } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonResourcePatch.kt index ffc8d8f1db..b35975f7cf 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonResourcePatch.kt @@ -3,12 +3,12 @@ package app.revanced.patches.youtube.video.speed.button import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.annotation.Patch -import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch +import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch import app.revanced.util.ResourceGroup import app.revanced.util.copyResources @Patch( - dependencies = [BottomControlsResourcePatch::class], + dependencies = [PlayerControlsResourcePatch::class], ) internal object PlaybackSpeedButtonResourcePatch : ResourcePatch() { override fun execute(context: ResourceContext) { @@ -20,6 +20,6 @@ internal object PlaybackSpeedButtonResourcePatch : ResourcePatch() { ), ) - BottomControlsResourcePatch.addControls("speedbutton") + PlayerControlsResourcePatch.addBottomControls("speedbutton") } } diff --git a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index 0f81d8d4e2..d22bfbdbe0 100644 --- a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -15,6 +15,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction import com.android.tools.smali.dexlib2.iface.reference.Reference import com.android.tools.smali.dexlib2.util.MethodUtil +import org.stringtemplate.v4.compiler.Bytecode.instructions fun MethodFingerprint.resultOrThrow() = result ?: throw exception @@ -73,7 +74,7 @@ fun MutableMethod.injectHideViewCall( * @param resourceName the name of the resource to find the id for. * @return the index of the first instruction with the id of the given resource name, or -1 if not found. * @throws PatchException if the resource cannot be found. - * @see [indexOfIdResourceOrThrow] + * @see [indexOfIdResourceOrThrow], [indexOfFirstWideLiteralInstructionValueReversed] */ fun Method.indexOfIdResource(resourceName: String): Int { val resourceId = ResourceMappingPatch["id", resourceName] @@ -86,6 +87,7 @@ fun Method.indexOfIdResource(resourceName: String): Int { * Requires [ResourceMappingPatch] as a dependency. * * @throws [PatchException] if the resource is not found, or the method does not contain the resource id literal value. + * @see [indexOfIdResource], [indexOfFirstWideLiteralInstructionValueReversedOrThrow] */ fun Method.indexOfIdResourceOrThrow(resourceName: String): Int { val index = indexOfIdResource(resourceName) @@ -120,6 +122,30 @@ fun Method.indexOfFirstWideLiteralInstructionValueOrThrow(literal: Long): Int { return index } +/** + * Find the index of the last wide literal instruction with the given value. + * + * @return the last literal instruction with the value, or -1 if not found. + * @see indexOfFirstWideLiteralInstructionValueOrThrow + */ +fun Method.indexOfFirstWideLiteralInstructionValueReversed(literal: Long) = implementation?.let { + it.instructions.indexOfLast { instruction -> + (instruction as? WideLiteralInstruction)?.wideLiteral == literal + } +} ?: -1 + +/** + * Find the index of the last wide literal instruction with the given value, + * or throw an exception if not found. + * + * @return the last literal instruction with the value, or throws [PatchException] if not found. + */ +fun Method.indexOfFirstWideLiteralInstructionValueReversedOrThrow(literal: Long): Int { + val index = indexOfFirstWideLiteralInstructionValueReversed(literal) + if (index < 0) throw PatchException("Could not find literal value: $literal") + return index +} + /** * Check if the method contains a literal with the given value. * diff --git a/src/main/kotlin/app/revanced/util/ResourceUtils.kt b/src/main/kotlin/app/revanced/util/ResourceUtils.kt index 56076078d3..2f0d93f840 100644 --- a/src/main/kotlin/app/revanced/util/ResourceUtils.kt +++ b/src/main/kotlin/app/revanced/util/ResourceUtils.kt @@ -1,8 +1,11 @@ package app.revanced.util import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.PatchException import app.revanced.patcher.util.DomFileEditor import app.revanced.util.resource.BaseResource +import org.w3c.dom.Attr +import org.w3c.dom.Element import org.w3c.dom.Node import org.w3c.dom.NodeList import java.io.InputStream @@ -39,6 +42,14 @@ fun Node.doRecursively(action: (Node) -> Unit) { for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action) } +fun Node.insertFirst(node: Node) { + if (hasChildNodes()) { + insertBefore(node, firstChild) + } else { + appendChild(node) + } +} + /** * Copy resources from the current class loader to the resource directory. * @@ -49,7 +60,7 @@ fun ResourceContext.copyResources( sourceResourceDirectory: String, vararg resources: ResourceGroup, ) { - val targetResourceDirectory = this.get("res") + val targetResourceDirectory = this["res", false] for (resourceGroup in resources) { resourceGroup.resources.forEach { resource -> @@ -164,3 +175,37 @@ internal fun Node.addResource( } internal fun org.w3c.dom.Document.getNode(tagName: String) = this.getElementsByTagName(tagName).item(0) + +internal fun NodeList.findElementByAttributeValue(attributeName: String, value: String): Element? { + for (i in 0 until length) { + val node = item(i) + if (node.nodeType == Node.ELEMENT_NODE) { + val element = node as Element + + if (element.getAttribute(attributeName) == value) { + return element + } + + // Recursively search. + val found = element.childNodes.findElementByAttributeValue(attributeName, value) + if (found != null) { + return found + } + } + } + + return null +} + +internal fun NodeList.findElementByAttributeValueOrThrow(attributeName: String, value: String): Element { + return findElementByAttributeValue(attributeName, value) ?: throw PatchException("Could not find: $attributeName $value") +} + +internal fun Element.copyAttributesFrom(oldContainer: Element) { + // Copy attributes from the old element to the new element + val attributes = oldContainer.attributes + for (i in 0 until attributes.length) { + val attr = attributes.item(i) as Attr + setAttribute(attr.name, attr.value) + } +} diff --git a/src/main/resources/copyvideourl/drawable/revanced_yt_copy.xml b/src/main/resources/copyvideourl/drawable/revanced_yt_copy.xml index 008393c30c..2a5bbc872a 100644 --- a/src/main/resources/copyvideourl/drawable/revanced_yt_copy.xml +++ b/src/main/resources/copyvideourl/drawable/revanced_yt_copy.xml @@ -1,6 +1,6 @@