mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2024-11-12 18:04:26 +01:00
fix(YouTube): Show video chapter titles without clipping when overlay buttons are enabled (#3674)
This commit is contained in:
parent
f27cbece71
commit
4b88c316ed
41 changed files with 537 additions and 273 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
// Change visibility of the buttons.
|
||||
PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
|
||||
|
||||
val controlsLayoutStubResourceId =
|
||||
ResourceMappingPatch["id", "controls_layout_stub"]
|
||||
val zoomOverlayResourceId =
|
||||
ResourceMappingPatch["id", "video_zoom_overlay_stub"]
|
||||
PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
|
||||
|
||||
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.injectVisibilityCheckCall("$INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibility(Z)V")
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibility(Z)V")
|
||||
|
||||
// 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 =
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {}
|
||||
}
|
|
@ -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<MethodReference>()
|
||||
reference?.definingClass == "Landroid/view/ViewStub;" &&
|
||||
reference.name == "inflate"
|
||||
}
|
||||
|
||||
showPlayerControlsFingerprintResult = PlayerControlsVisibilityFingerprint.result!!
|
||||
PlayerBottomControlsInflateFingerprint.resultOrThrow().mutableMethod.apply{
|
||||
inflateBottomControlMethod = this
|
||||
|
||||
inflateFingerprintResult = BottomControlsInflateFingerprint.result!!.also {
|
||||
moveToRegisterInstructionIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
viewRegister =
|
||||
(it.mutableMethod.implementation!!.instructions[moveToRegisterInstructionIndex] as OneRegisterInstruction).registerA
|
||||
val inflateReturnObjectIndex = indexOfFirstViewInflateOrThrow() + 1
|
||||
inflateBottomControlRegister = getInstruction<OneRegisterInstruction>(inflateReturnObjectIndex).registerA
|
||||
inflateBottomControlInsertIndex = inflateReturnObjectIndex + 1
|
||||
}
|
||||
|
||||
PlayerTopControlsInflateFingerprint.resultOrThrow().mutableMethod.apply {
|
||||
inflateTopControlMethod = this
|
||||
|
||||
val inflateReturnObjectIndex = indexOfFirstViewInflateOrThrow() + 1
|
||||
inflateTopControlRegister = getInstruction<OneRegisterInstruction>(inflateReturnObjectIndex).registerA
|
||||
inflateTopControlInsertIndex = inflateReturnObjectIndex + 1
|
||||
}
|
||||
|
||||
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<TypeReference>()?.type ==
|
||||
"Landroid/widget/ImageView;"
|
||||
}
|
||||
val register = getInstruction<OneRegisterInstruction>(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)
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
)
|
|
@ -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")
|
|
@ -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)
|
||||
}
|
||||
)
|
|
@ -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 }
|
||||
)
|
|
@ -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;"
|
||||
}
|
||||
)
|
|
@ -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 }
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/content_copy/materialsymbolsoutlined/content_copy_wght200gradN25_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/content_copy/materialsymbolsoutlined/content_copy_wght200gradN25_24px.xml
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/schedule/materialsymbolsoutlined/schedule_wght300_24px.xml
|
||||
This icon is the result of a combination of "content copy" and "schedule" icons.
|
||||
Changes made: This icon is the result of a combination of "content copy" and "schedule" icons.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,5 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:yt="http://schemas.android.com/apk/res-auto" android:id="@+id/youtube_controls_bottom_ui_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layoutDirection="ltr">
|
||||
<com.google.android.libraries.youtube.common.ui.TouchImageView android:id="@+id/revanced_copy_video_url_timestamp_button" android:paddingLeft="12dp" android:paddingTop="22dp" android:paddingRight="12dp" android:paddingBottom="16dp" android:longClickable="false" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/revanced_yt_copy_timestamp" android:scaleType="center" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" style="@style/YouTubePlayerButton"/>
|
||||
<com.google.android.libraries.youtube.common.ui.TouchImageView android:id="@+id/revanced_copy_video_url_button" android:paddingLeft="12dp" android:paddingTop="22dp" android:paddingRight="12dp" android:paddingBottom="16dp" android:longClickable="false" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/revanced_yt_copy" android:scaleType="center" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" style="@style/YouTubePlayerButton"/>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:yt="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/youtube_controls_bottom_ui_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<com.google.android.libraries.youtube.common.ui.TouchImageView
|
||||
android:id="@+id/revanced_copy_video_url_timestamp_button"
|
||||
style="@style/YouTubePlayerButton"
|
||||
android:layout_width="48.0dip"
|
||||
android:layout_height="60.0dip"
|
||||
android:paddingTop="6.0dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:longClickable="false"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/revanced_yt_copy_timestamp"
|
||||
yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container"
|
||||
yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" />
|
||||
|
||||
<com.google.android.libraries.youtube.common.ui.TouchImageView
|
||||
android:id="@+id/revanced_copy_video_url_button"
|
||||
style="@style/YouTubePlayerButton"
|
||||
android:layout_width="48.0dip"
|
||||
android:layout_height="60.0dip"
|
||||
android:paddingTop="6.0dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:longClickable="false"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/revanced_yt_copy"
|
||||
yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container"
|
||||
yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" />
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/download/materialsymbolsoutlined/download_wght200gradN25_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:yt="http://schemas.android.com/apk/res-auto" android:id="@+id/youtube_controls_bottom_ui_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layoutDirection="ltr">
|
||||
<com.google.android.libraries.youtube.common.ui.TouchImageView android:id="@+id/revanced_external_download_button" android:paddingLeft="12dp" android:paddingTop="22dp" android:paddingRight="12dp" android:paddingBottom="16dp" android:longClickable="false" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/revanced_yt_download_button" android:scaleType="center" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" style="@style/YouTubePlayerButton"/>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:yt="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/youtube_controls_bottom_ui_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<com.google.android.libraries.youtube.common.ui.TouchImageView
|
||||
android:id="@+id/revanced_external_download_button"
|
||||
style="@style/YouTubePlayerButton"
|
||||
android:layout_width="48.0dip"
|
||||
android:layout_height="60.0dip"
|
||||
android:paddingTop="6.5dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:longClickable="false"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/revanced_yt_download_button"
|
||||
yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container"
|
||||
yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" />
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/slow_motion_video/materialsymbolsoutlined/slow_motion_video_wght200gradN25_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:yt="http://schemas.android.com/apk/res-auto" android:id="@+id/youtube_controls_bottom_ui_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layoutDirection="ltr">
|
||||
<com.google.android.libraries.youtube.common.ui.TouchImageView android:id="@+id/revanced_playback_speed_dialog_button" android:paddingLeft="12dp" android:paddingTop="22dp" android:paddingRight="12dp" android:paddingBottom="16dp" android:longClickable="false" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/revanced_playback_speed_dialog_button" android:scaleType="center" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" style="@style/YouTubePlayerButton"/>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:yt="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/youtube_controls_bottom_ui_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<com.google.android.libraries.youtube.common.ui.TouchImageView
|
||||
android:id="@+id/revanced_playback_speed_dialog_button"
|
||||
style="@style/YouTubePlayerButton"
|
||||
android:layout_width="48.0dip"
|
||||
android:layout_height="60.0dip"
|
||||
android:paddingTop="6.0dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:longClickable="false"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/revanced_playback_speed_dialog_button"
|
||||
yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container"
|
||||
yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" />
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/adjust/materialsymbolsoutlined/adjust_wght200gradN25_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/fast_forward/materialsymbolsoutlined/fast_forward_wght200gradN25_24px.xml
|
||||
The icon has been mirrored and resized
|
||||
Changes made: The icon has been mirrored and resized
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/compare/materialsymbolsoutlined/compare_wght200gradN25_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/edit/materialsymbolsoutlined/edit_wght200gradN25_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/fast_forward/materialsymbolsoutlined/fast_forward_wght200gradN25_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/ajayyy/SponsorBlock/blob/e1d656f43f8b3cfb40e1c521e4103d61db756872/public/icons/PlayerStartIconSponsorBlocker.svg
|
||||
The SponsorBlock logo was inverted
|
||||
Changes made: The SponsorBlock logo was inverted.
|
||||
|
||||
|
||||
Copyright 2021 Ajay Ramachandran <dev@ajay.app>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/publish/materialsymbolsoutlined/publish_wght200gradN25_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/thumbs_up_down/materialsymbolsoutlined/thumbs_up_down_wght300gradN25_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,18 +1,5 @@
|
|||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<com.google.android.libraries.youtube.common.ui.TouchImageView
|
||||
android:id="@+id/revanced_sb_create_segment_button"
|
||||
style="@style/YouTubePlayerButton"
|
||||
android:layout_width="@dimen/controls_overlay_action_button_size"
|
||||
android:layout_height="@dimen/controls_overlay_action_button_size"
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_toStartOf="@+id/player_additional_view_container"
|
||||
android:padding="@dimen/controls_overlay_action_button_padding"
|
||||
android:src="@drawable/revanced_sb_logo" />
|
||||
|
||||
<com.google.android.libraries.youtube.common.ui.TouchImageView
|
||||
android:id="@+id/revanced_sb_voting_button"
|
||||
style="@style/YouTubePlayerButton"
|
||||
|
@ -25,4 +12,17 @@
|
|||
android:layout_toStartOf="@+id/revanced_sb_create_segment_button"
|
||||
android:padding="@dimen/controls_overlay_action_button_padding"
|
||||
android:src="@drawable/revanced_sb_voting" />
|
||||
|
||||
<com.google.android.libraries.youtube.common.ui.TouchImageView
|
||||
android:id="@+id/revanced_sb_create_segment_button"
|
||||
style="@style/YouTubePlayerButton"
|
||||
android:layout_width="@dimen/controls_overlay_action_button_size"
|
||||
android:layout_height="@dimen/controls_overlay_action_button_size"
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_toStartOf="@+id/player_additional_view_container"
|
||||
android:padding="@dimen/controls_overlay_action_button_padding"
|
||||
android:src="@drawable/revanced_sb_logo" />
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/brightness_auto/materialsymbolsoutlined/brightness_auto_wght300_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/brightness_6/materialsymbolsoutlined/brightness_6_wght300_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/volume_off/materialsymbolsoutlined/volume_off_wght300_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/volume_up/materialsymbolsoutlined/volume_up_wght300_24px.xml
|
||||
The icon has been resized
|
||||
Changes made: Icon has been resized.
|
||||
|
||||
|
||||
Copyright 2022 Google
|
||||
|
|
Loading…
Reference in a new issue