diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/annotation/DownloadsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/annotation/DownloadsCompatibility.kt new file mode 100644 index 00000000..75f14c11 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/annotation/DownloadsCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.interaction.downloads.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.27.39", "17.32.35") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class DownloadsCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/bytecode/patch/DownloadsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/bytecode/patch/DownloadsBytecodePatch.kt new file mode 100644 index 00000000..57db4d2b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/bytecode/patch/DownloadsBytecodePatch.kt @@ -0,0 +1,52 @@ +package app.revanced.patches.youtube.interaction.downloads.bytecode.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.impl.BytecodeData +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.patch.impl.BytecodePatch +import app.revanced.patches.youtube.interaction.downloads.annotation.DownloadsCompatibility +import app.revanced.patches.youtube.interaction.downloads.resource.patch.DownloadsResourcePatch +import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch +import app.revanced.patches.youtube.misc.videoid.patch.VideoIdPatch + +@Patch +@Name("downloads") +@DependsOn([DownloadsResourcePatch::class, PlayerControlsBytecodePatch::class, VideoIdPatch::class]) +@Description("Enables downloading music and videos from YouTube.") +@DownloadsCompatibility +@Version("0.0.1") +class DownloadsBytecodePatch : BytecodePatch() { + override fun execute(data: BytecodeData): PatchResult { + val integrationsPackage = "app/revanced/integrations" + val classDescriptor = "L$integrationsPackage/videoplayer/DownloadButton;" + + /* + initialize the control + */ + + val initializeDownloadsDescriptor = "$classDescriptor->initializeDownloadButton(Ljava/lang/Object;)V" + PlayerControlsBytecodePatch.initializeControl(initializeDownloadsDescriptor) + + /* + add code to change the visibility of the control + */ + + val changeVisibilityDescriptor = "$classDescriptor->changeVisibility(Z)V" + PlayerControlsBytecodePatch.injectVisibilityCheckCall(changeVisibilityDescriptor) + + /* + add code to change to update the video id + */ + + val setVideoIdDescriptor = + "L$integrationsPackage/patches/downloads/DownloadsPatch;->setVideoId(Ljava/lang/String;)V" + VideoIdPatch.injectCall(setVideoIdDescriptor) + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/resource/patch/DownloadsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/resource/patch/DownloadsResourcePatch.kt new file mode 100644 index 00000000..719efbb5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/resource/patch/DownloadsResourcePatch.kt @@ -0,0 +1,56 @@ +package app.revanced.patches.youtube.interaction.downloads.resource.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.impl.ResourceData +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.impl.ResourcePatch +import app.revanced.patches.youtube.interaction.downloads.annotation.DownloadsCompatibility +import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch +import app.revanced.patches.youtube.misc.playercontrols.resource.patch.BottomControlsResourcePatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource +import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.Settings.mergeStrings +import app.revanced.util.resources.ResourceUtils.copyResources + +@Name("downloads-resource-patch") +@DependsOn([BottomControlsResourcePatch::class, FixLocaleConfigErrorPatch::class, SettingsPatch::class]) +@Description("Makes necessary changes to resources for the download button.") +@DownloadsCompatibility +@Version("0.0.1") +class DownloadsResourcePatch : ResourcePatch() { + override fun execute(data: ResourceData): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(SwitchPreference( + "revanced_downloads", + StringResource("revanced_downloads_enabled_title", "Show download button"), + true, + StringResource("revanced_downloads_enabled_summary_on", "Download button is visible"), + StringResource("revanced_downloads_enabled_summary_off", "Download button is hidden") + )) + + /* + * Copy strings + */ + + data.mergeStrings("downloads/host/values/strings.xml") + + /* + * Copy resources + */ + + data.copyResources("downloads", ResourceUtils.ResourceGroup("drawable", "revanced_yt_download_button.xml")) + + /* + * Add download button node + */ + + BottomControlsResourcePatch.addControls("downloads/host/layout/${BottomControlsResourcePatch.TARGET_RESOURCE_NAME}") + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/resource/SwipeControlsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/resource/SwipeControlsResourcePatch.kt index 05d732e0..84f8fb4e 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/resource/SwipeControlsResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/resource/SwipeControlsResourcePatch.kt @@ -1,6 +1,5 @@ package app.revanced.patches.youtube.interaction.swipecontrols.patch.resource -import app.revanced.extensions.injectResources import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Version import app.revanced.patcher.data.impl.ResourceData @@ -11,6 +10,8 @@ import app.revanced.patcher.patch.impl.ResourcePatch import app.revanced.patches.youtube.interaction.swipecontrols.annotation.SwipeControlsCompatibility import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch import app.revanced.patches.youtube.misc.settings.framework.components.impl.* +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.copyResources @Name("swipe-controls-resource-patch") @DependsOn([SettingsPatch::class]) @@ -93,16 +94,15 @@ class SwipeControlsResourcePatch : ResourcePatch() { val resourcesDir = "swipecontrols" - data.injectResources( - this.javaClass.classLoader, - resourcesDir, - "drawable", - listOf( - "ic_sc_brightness_auto", - "ic_sc_brightness_manual", - "ic_sc_volume_mute", - "ic_sc_volume_normal" - ).map { "$it.xml" } + data.copyResources( + "swipecontrols", + ResourceUtils.ResourceGroup( + "drawable", + "ic_sc_brightness_auto.xml", + "ic_sc_brightness_manual.xml", + "ic_sc_volume_mute.xml", + "ic_sc_volume_normal.xml" + ) ) return PatchResultSuccess() } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/resource/patch/ReturnYouTubeDislikeResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/resource/patch/ReturnYouTubeDislikeResourcePatch.kt index a229d7d3..220dd657 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/resource/patch/ReturnYouTubeDislikeResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/resource/patch/ReturnYouTubeDislikeResourcePatch.kt @@ -13,7 +13,7 @@ import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatc import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch import app.revanced.patches.youtube.misc.settings.framework.components.impl.Preference import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource -import app.revanced.util.resources.ResourceUtils.iterateXmlNodeChildren +import app.revanced.util.resources.ResourceUtils.Settings.mergeStrings @DependsOn([FixLocaleConfigErrorPatch::class, SettingsPatch::class]) @Name("return-youtube-dislike-resource-patch") @@ -35,12 +35,7 @@ class ReturnYouTubeDislikeResourcePatch : ResourcePatch() { ) ) // merge strings - data.iterateXmlNodeChildren("returnyoutubedislike/host/values/strings.xml", "resources") { - // TODO: figure out why this is needed - if (!it.hasAttributes()) return@iterateXmlNodeChildren - val attributes = it.attributes - SettingsPatch.addString(attributes.getNamedItem("name")!!.nodeValue!!, it.textContent!!) - } + data.mergeStrings("returnyoutubedislike/host/values/strings.xml") return PatchResultSuccess() } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/ShowPlayerControlsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/ShowPlayerControlsFingerprint.kt deleted file mode 100644 index 39c330ae..00000000 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/ShowPlayerControlsFingerprint.kt +++ /dev/null @@ -1,19 +0,0 @@ -package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints - -import app.revanced.patcher.annotation.Name -import app.revanced.patcher.annotation.Version -import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod -import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod -import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint -import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility - -@Name("show-player-controls-fingerprint") -@MatchingMethod( - "LYouTubeControlsOverlay;", "ac" -) -@DirectPatternScanMethod -@SponsorBlockCompatibility -@Version("0.0.1") -object ShowPlayerControlsFingerprint : MethodFingerprint( - "V", null, listOf("Z","Z"), null, null -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt index 3ba5406e..c2ff0764 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt @@ -23,6 +23,7 @@ import app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints.* import app.revanced.patches.youtube.layout.sponsorblock.resource.patch.SponsorBlockResourcePatch import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch import app.revanced.patches.youtube.misc.mapping.patch.ResourceIdMappingProviderResourcePatch +import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch import app.revanced.patches.youtube.misc.videoid.patch.VideoIdPatch import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.Opcode @@ -38,7 +39,7 @@ import org.jf.dexlib2.util.MethodUtil @Patch @DependsOn( - dependencies = [IntegrationsPatch::class, ResourceIdMappingProviderResourcePatch::class, SponsorBlockResourcePatch::class, VideoIdPatch::class] + dependencies = [PlayerControlsBytecodePatch::class, IntegrationsPatch::class, ResourceIdMappingProviderResourcePatch::class, SponsorBlockResourcePatch::class, VideoIdPatch::class] ) @Name("sponsorblock") @Description("Integrate SponsorBlock.") @@ -171,8 +172,7 @@ class SponsorBlockBytecodePatch : BytecodePatch( /* Voting & Shield button */ - ShowPlayerControlsFingerprint.resolve(data, data.classes.find { it.type.endsWith("YouTubeControlsOverlay;") }!!) - val controlsMethodResult = ShowPlayerControlsFingerprint.result!! + val controlsMethodResult = PlayerControlsBytecodePatch.showPlayerControlsFingerprintResult val controlsLayoutStubResourceId = ResourceIdMappingProviderResourcePatch.resourceMappings.single { it.type == "id" && it.name == "controls_layout_stub" }.id @@ -217,12 +217,8 @@ class SponsorBlockBytecodePatch : BytecodePatch( } // change visibility of the buttons - controlsMethodResult.mutableMethod.addInstructions( - 0, """ - invoke-static {p1}, Lapp/revanced/integrations/sponsorblock/ShieldButton;->changeVisibility(Z)V - invoke-static {p1}, Lapp/revanced/integrations/sponsorblock/VotingButton;->changeVisibility(Z)V - """.trimIndent() - ) + PlayerControlsBytecodePatch.injectVisibilityCheckCall("Lapp/revanced/integrations/sponsorblock/ShieldButton;->changeVisibility(Z)V") + PlayerControlsBytecodePatch.injectVisibilityCheckCall("Lapp/revanced/integrations/sponsorblock/VotingButton;->changeVisibility(Z)V") // set SegmentHelperLayout.context to the player layout instance val instanceRegister = 0 diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/resource/patch/SponsorBlockResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/resource/patch/SponsorBlockResourcePatch.kt index b2d1c50f..cad7b796 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/resource/patch/SponsorBlockResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/resource/patch/SponsorBlockResourcePatch.kt @@ -13,9 +13,9 @@ import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch import app.revanced.patches.youtube.misc.settings.framework.components.impl.Preference import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.Settings.mergeStrings import app.revanced.util.resources.ResourceUtils.copyResources import app.revanced.util.resources.ResourceUtils.copyXmlNode -import app.revanced.util.resources.ResourceUtils.iterateXmlNodeChildren @Name("sponsorblock-resource-patch") @SponsorBlockCompatibility @@ -40,20 +40,7 @@ class SponsorBlockResourcePatch : ResourcePatch() { /* merge SponsorBlock strings to main strings */ - data.iterateXmlNodeChildren("sponsorblock/host/values/strings.xml", "resources") { - // TODO: figure out why this is needed - if (!it.hasAttributes()) return@iterateXmlNodeChildren - - val attributes = it.attributes - val key = attributes.getNamedItem("name")!!.nodeValue!! - val value = it.textContent!! - - // all strings of SponsorBlock which have this attribute have the attribute value false, - // hence a null check suffices - val formatted = attributes.getNamedItem("formatted") == null - - SettingsPatch.addString(key, value, formatted) - } + data.mergeStrings("sponsorblock/host/values/strings.xml") /* merge SponsorBlock drawables to main drawables diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/annotation/PlayerControlsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/annotation/PlayerControlsCompatibility.kt new file mode 100644 index 00000000..4f00f603 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/annotation/PlayerControlsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.playercontrols.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.27.39", "17.32.35") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PlayerControlsCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/bytecode/patch/PlayerControlsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/bytecode/patch/PlayerControlsBytecodePatch.kt new file mode 100644 index 00000000..e629ecbf --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/bytecode/patch/PlayerControlsBytecodePatch.kt @@ -0,0 +1,83 @@ +package app.revanced.patches.youtube.misc.playercontrols.bytecode.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.impl.BytecodeData +import app.revanced.patcher.extensions.addInstruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult +import app.revanced.patcher.fingerprint.method.utils.MethodFingerprintUtils.resolve +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.impl.BytecodePatch +import app.revanced.patches.youtube.misc.mapping.patch.ResourceIdMappingProviderResourcePatch +import app.revanced.patches.youtube.misc.playercontrols.annotation.PlayerControlsCompatibility +import app.revanced.patches.youtube.misc.playercontrols.fingerprints.BottomControlsInflateFingerprint +import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsVisibilityFingerprint +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Name("player-controls-bytecode-patch") +@DependsOn([ResourceIdMappingProviderResourcePatch::class]) +@Description("Manages the code for the player controls of the YouTube player.") +@PlayerControlsCompatibility +@Version("0.0.1") +class PlayerControlsBytecodePatch : BytecodePatch( + listOf(PlayerControlsVisibilityFingerprint) +) { + override fun execute(data: BytecodeData): PatchResult { + showPlayerControlsFingerprintResult = PlayerControlsVisibilityFingerprint.result!! + + bottomUiContainerResourceId = ResourceIdMappingProviderResourcePatch + .resourceMappings + .single { it.type == "id" && it.name == "bottom_ui_container_stub" }.id + + // TODO: another solution is required, this is hacky + listOf(BottomControlsInflateFingerprint).resolve(data, data.classes) + inflateFingerprintResult = BottomControlsInflateFingerprint.result!! + + return PatchResultSuccess() + } + + internal companion object { + var bottomUiContainerResourceId: Long = 0 + + lateinit var showPlayerControlsFingerprintResult: MethodFingerprintResult + + private var inflateFingerprintResult: MethodFingerprintResult? = null + set(fingerprint) { + field = fingerprint!!.also { + moveToRegisterInstructionIndex = it.patternScanResult!!.endIndex + viewRegister = + (it.mutableMethod.implementation!!.instructions[moveToRegisterInstructionIndex] as OneRegisterInstruction).registerA + } + } + + private var moveToRegisterInstructionIndex: Int = 0 + private var viewRegister: Int = 0 + + /** + * Injects the code to change the visibility of controls. + * @param descriptor The descriptor of the method which should be called. + */ + fun injectVisibilityCheckCall(descriptor: String) { + showPlayerControlsFingerprintResult.mutableMethod.addInstruction( + 0, + """ + invoke-static {p1}, $descriptor + """ + ) + } + + /** + * Injects the code to initialize the controls. + * @param descriptor The descriptor of the method which should be calleed. + */ + fun initializeControl(descriptor: String) { + inflateFingerprintResult!!.mutableMethod.addInstruction( + moveToRegisterInstructionIndex + 1, + "invoke-static {v$viewRegister}, $descriptor" + ) + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..ee86a246 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt @@ -0,0 +1,31 @@ +package app.revanced.patches.youtube.misc.playercontrols.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod +import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.misc.playercontrols.annotation.PlayerControlsCompatibility +import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +@Name("bottom-controls-inflate-fingerprint") +@MatchingMethod( + "Lknf;", "a" +) +@DirectPatternScanMethod +@PlayerControlsCompatibility +@Version("0.0.1") +object BottomControlsInflateFingerprint : MethodFingerprint( + null, null, null, listOf( + Opcode.CHECK_CAST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT + ), null, + { methodDef -> + methodDef.implementation?.instructions?.any { instruction -> + (instruction as? WideLiteralInstruction)?.wideLiteral == PlayerControlsBytecodePatch.bottomUiContainerResourceId + } == true + } +) \ 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/PlayerControlsVisibilityFingerprint.kt new file mode 100644 index 00000000..e3500a1b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsVisibilityFingerprint.kt @@ -0,0 +1,21 @@ +package app.revanced.patches.youtube.misc.playercontrols.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod +import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.misc.playercontrols.annotation.PlayerControlsCompatibility + +@Name("player-controls-visibility-fingerprint") +@MatchingMethod( + "LYouTubeControlsOverlay;", "ag" +) +@DirectPatternScanMethod +@PlayerControlsCompatibility +@Version("0.0.1") +object PlayerControlsVisibilityFingerprint : MethodFingerprint( + "V", null, listOf("Z", "Z"), null, null, { methodDef -> + methodDef.definingClass.endsWith("YouTubeControlsOverlay;") + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/resource/patch/BottomControlsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/resource/patch/BottomControlsResourcePatch.kt new file mode 100644 index 00000000..b6a51c8d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/resource/patch/BottomControlsResourcePatch.kt @@ -0,0 +1,86 @@ +package app.revanced.patches.youtube.misc.playercontrols.resource.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.impl.DomFileEditor +import app.revanced.patcher.data.impl.ResourceData +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.impl.ResourcePatch +import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch +import app.revanced.patches.youtube.misc.playercontrols.annotation.PlayerControlsCompatibility +import java.io.Closeable + +@Name("bottom-controls-resource-patch") +@DependsOn([FixLocaleConfigErrorPatch::class]) +@Description("Manages the resources for the bottom controls of the YouTube player.") +@PlayerControlsCompatibility +@Version("0.0.1") +class BottomControlsResourcePatch : ResourcePatch(), Closeable { + override fun execute(data: ResourceData): PatchResult { + resourceData = data + targetXmlEditor = data.xmlEditor[TARGET_RESOURCE] + + return PatchResultSuccess() + } + + companion object { + internal const val TARGET_RESOURCE_NAME = "youtube_controls_bottom_ui_container.xml" + private const val TARGET_RESOURCE = "res/layout/$TARGET_RESOURCE_NAME" + + private lateinit var resourceData: ResourceData + private lateinit var targetXmlEditor: DomFileEditor + + // The element to which to add the new elements to + private var lastLeftOf = "fullscreen_button" + + + + /** + * Add new controls to the bottom of the YouTube player. + * @param hostYouTubeControlsBottomUiResourceName The hosting resource name containing the elements. + */ + internal fun addControls(hostYouTubeControlsBottomUiResourceName: String) { + val sourceXmlEditor = + resourceData.xmlEditor[this::class.java.classLoader.getResourceAsStream( + hostYouTubeControlsBottomUiResourceName + )!!] + + val targetElement = + "android.support.constraint.ConstraintLayout" + + val hostElements = sourceXmlEditor.file.getElementsByTagName(targetElement).item(0).childNodes + + val destinationResourceFile = this.targetXmlEditor.file + val destinationElement = + destinationResourceFile.getElementsByTagName(targetElement).item(0) + + for (index in 1 until hostElements.length) { + val element = hostElements.item(index).cloneNode(true) + + // if the element has no attributes theres 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 the current element + val nameSpaceLength = 4 + lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength) + + // copy the element + destinationResourceFile.adoptNode(element) + destinationElement.appendChild(element) + } + sourceXmlEditor.close() + } + } + + override fun close() { + targetXmlEditor.close() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/resources/ResourceUtils.kt b/src/main/kotlin/app/revanced/util/resources/ResourceUtils.kt index 301bad2c..167375a1 100644 --- a/src/main/kotlin/app/revanced/util/resources/ResourceUtils.kt +++ b/src/main/kotlin/app/revanced/util/resources/ResourceUtils.kt @@ -2,11 +2,38 @@ package app.revanced.util.resources import app.revanced.patcher.data.impl.DomFileEditor import app.revanced.patcher.data.impl.ResourceData +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource import org.w3c.dom.Node import java.nio.file.Files import java.nio.file.StandardCopyOption internal object ResourceUtils { + + /** + * Settings related utilities + */ + internal object Settings { + /** + * Merge strings. This handles [StringResource]s automatically. + * @param host The hosting xml resource. Needs to be a valid strings.xml resource. + */ + internal fun ResourceData.mergeStrings(host: String) { + this.iterateXmlNodeChildren(host, "resources") { + // TODO: figure out why this is needed + if (!it.hasAttributes()) return@iterateXmlNodeChildren + + val attributes = it.attributes + val key = attributes.getNamedItem("name")!!.nodeValue!! + val value = it.textContent!! + + val formatted = attributes.getNamedItem("formatted") == null + + SettingsPatch.addString(key, value, formatted) + } + } + } + /** * Copy resources from the current class loader to the resource directory. * @param sourceResourceDirectory The source resource directory name. diff --git a/src/main/resources/downloads/drawable/revanced_yt_download_button.xml b/src/main/resources/downloads/drawable/revanced_yt_download_button.xml new file mode 100644 index 00000000..44ef4b6a --- /dev/null +++ b/src/main/resources/downloads/drawable/revanced_yt_download_button.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml b/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml new file mode 100644 index 00000000..23254785 --- /dev/null +++ b/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/downloads/host/values/strings.xml b/src/main/resources/downloads/host/values/strings.xml new file mode 100644 index 00000000..afcde12a --- /dev/null +++ b/src/main/resources/downloads/host/values/strings.xml @@ -0,0 +1,5 @@ + + + PowerTube is not installed + Please install PowerTube from https://github.com/razar-dev/PowerTube. +