From b826db02e387c363de1e3ef7bab374d0524525ad Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 7 Mar 2026 23:35:17 +0100 Subject: [PATCH 01/41] refactor(YouTube - Hide end screen suggested video): Use more idiomatic APIs --- .../endscreensuggestedvideo/Fingerprints.kt | 68 ++++++++++++++++--- .../HideEndScreenSuggestedVideoPatch.kt | 40 +++++------ 2 files changed, 74 insertions(+), 34 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/Fingerprints.kt index 432cae1cf5..58d4e3f0f0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/Fingerprints.kt @@ -1,14 +1,25 @@ package app.revanced.patches.youtube.layout.hide.endscreensuggestedvideo import app.revanced.patcher.* -import app.revanced.patcher.extensions.instructions +import app.revanced.patcher.accessFlags +import app.revanced.patcher.definingClass +import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.methodReference +import app.revanced.patcher.firstMethodComposite +import app.revanced.patcher.instructions +import app.revanced.patcher.name +import app.revanced.patcher.parameterTypes import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.returnType import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.ClassDef +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction -internal val BytecodePatchContext.autoNavConstructorMethod by gettingFirstImmutableMethodDeclaratively("main_app_autonav") { +internal val BytecodePatchContext.autoNavConstructorMethod by gettingFirstImmutableMethodDeclaratively( + "main_app_autonav" +) { returnType("V") accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) } @@ -24,15 +35,50 @@ internal val BytecodePatchContext.removeOnLayoutChangeListenerMethodMatch by com accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) returnType("V") parameterTypes() - opcodes( - Opcode.IPUT, - Opcode.INVOKE_VIRTUAL, + instructions( + allOf(Opcode.IPUT(), field { type == "I" }), + afterAtMost( + 3, + allOf( + Opcode.INVOKE_VIRTUAL(), + method { returnType == "V" && parameterTypes.isEmpty() } + ) + ), + allOf(Opcode.INVOKE_VIRTUAL(), method { + name == "removeOnLayoutChangeListener" && + returnType == "Z" && + definingClass == "Lcom/google/android/apps/youtube/app/common/" + + "player/overlay/YouTubePlayerOverlaysLayout;" + }), ) - // This is the only reference present in the entire smali. - custom { - instructions.anyInstruction { - val reference = methodReference - reference?.name == "removeOnLayoutChangeListener" && reference.definingClass.endsWith("/YouTubePlayerOverlaysLayout;") - } +} + +internal fun BytecodePatchContext.getEndScreenSuggestedVideoMethodMatch(autoNavStatusMethod: Method): CompositeMatch { + val endScreenMethod = removeOnLayoutChangeListenerMethodMatch.let { + firstMethod(it.method.getInstruction(it[1]).methodReference!!) + } + + return firstMethodComposite { + name(endScreenMethod.name) + definingClass(endScreenMethod.definingClass) + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returnType("V") + parameterTypes() + instructions( + allOf( + Opcode.IGET_OBJECT(), + field { + definingClass == endScreenMethod.definingClass && + type == autoNavStatusMethod.definingClass + } + ), + afterAtMost( + 3, + allOf( + Opcode.INVOKE_VIRTUAL(), + method { this == autoNavStatusMethod } + ) + ), + ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt index ae14785935..e667966c5e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt @@ -2,8 +2,10 @@ package app.revanced.patches.youtube.layout.hide.endscreensuggestedvideo import app.revanced.patcher.extensions.ExternalLabel import app.revanced.patcher.extensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.fieldReference import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.methodReference +import app.revanced.patcher.firstMethod import app.revanced.patcher.immutableClassDef import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.all.misc.resources.addResources @@ -11,9 +13,6 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen -import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.indexOfFirstInstructionReversedOrThrow -import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction private const val EXTENSION_CLASS_DESCRIPTOR = @@ -41,32 +40,27 @@ val hideEndScreenSuggestedVideoPatch = bytecodePatch( ) apply { - addResources("youtube", "layout.hide.endscreensuggestedvideo.hideEndScreenSuggestedVideoPatch") + addResources( + "youtube", + "layout.hide.endscreensuggestedvideo.hideEndScreenSuggestedVideoPatch" + ) PreferenceScreen.PLAYER.addPreferences( SwitchPreference("revanced_end_screen_suggested_video"), ) - removeOnLayoutChangeListenerMethodMatch.let { - val endScreenMethod = navigate(it.immutableMethod).to(it[-1]).stop() - endScreenMethod.apply { - val autoNavStatusMethodName = - autoNavConstructorMethod.immutableClassDef.getAutoNavStatusMethod().name + val autoNavStatusMethod = + autoNavConstructorMethod.immutableClassDef.getAutoNavStatusMethod() - val invokeIndex = indexOfFirstInstructionOrThrow { - val reference = methodReference - reference?.name == autoNavStatusMethodName && - reference.returnType == "Z" && - reference.parameterTypes.isEmpty() - } + val endScreenMethod = removeOnLayoutChangeListenerMethodMatch.let { + firstMethod(it.method.getInstruction(it[1]).methodReference!!) + } - val iGetObjectIndex = - indexOfFirstInstructionReversedOrThrow(invokeIndex, Opcode.IGET_OBJECT) - val invokeReference = getInstruction(invokeIndex).reference - val iGetObjectReference = - getInstruction(iGetObjectIndex).reference - val opcodeName = getInstruction(invokeIndex).opcode.name + + getEndScreenSuggestedVideoMethodMatch(autoNavStatusMethod).let { match -> + match.method.apply { + val autoNavField = getInstruction(match[0]).fieldReference!! addInstructionsWithLabels( 0, @@ -75,10 +69,10 @@ val hideEndScreenSuggestedVideoPatch = bytecodePatch( move-result v0 if-eqz v0, :show_end_screen_recommendation - iget-object v0, p0, $iGetObjectReference + iget-object v0, p0, $autoNavField # This reference checks whether autoplay is turned on. - $opcodeName { v0 }, $invokeReference + invoke-virtual { v0 }, $autoNavStatusMethod move-result v0 # Hide suggested video end screen only when autoplay is turned off. From 3c46a2d2e8b204357a75c3e985f99789729a9b5b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 7 Mar 2026 23:36:05 +0100 Subject: [PATCH 02/41] fix(YouTube - Hide Shorts components): Resolve Shorts header not being hidden Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../extension/youtube/patches/litho/ShortsFilter.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java index 462cd8c2e1..b01d8db393 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java @@ -71,7 +71,6 @@ public final class ShortsFilter extends Filter { private final StringFilterGroup shortsCompactFeedVideo; private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer; private final StringFilterGroup channelProfile; - private final ByteArrayFilterGroup channelProfileShelfHeaderBuffer; private final StringFilterGroup useSoundButton; private final ByteArrayFilterGroup useSoundButtonBuffer; private final StringFilterGroup useTemplateButton; @@ -112,11 +111,6 @@ public final class ShortsFilter extends Filter { "shorts_pivot_item" ); - channelProfileShelfHeaderBuffer = new ByteArrayFilterGroup( - Settings.HIDE_SHORTS_CHANNEL, - "Shorts" - ); - // Feed Shorts shelf header. // Use a different filter group for this pattern, as it requires an additional check after matching. shelfHeaderIdentifier = new StringFilterGroup( @@ -481,9 +475,6 @@ public final class ShortsFilter extends Filter { if (contentIndex != 0) { return false; } - if (!channelProfileShelfHeaderBuffer.check(buffer).isFiltered()) { - return false; - } return shouldHideShortsFeedItems(); } From f10f5e29102148f75cb157b926227eee87f28ee7 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 7 Mar 2026 23:39:50 +0100 Subject: [PATCH 03/41] refactor(YouTube - Add more double tap to seek length options): Use more idiomatic code --- .../AddMoreDoubleTapToSeekLengthOptionsPatch.kt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt index b84afcb4d9..c2b1686567 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt @@ -28,12 +28,7 @@ val addMoreDoubleTapToSeekLengthOptionsPatch = resourcePatch( execute { // Values are hard coded to keep patching simple. - val doubleTapLengthOptionsString = "3, 5, 10, 15, 20, 30, 60, 120, 180, 240" - - val doubleTapLengths = doubleTapLengthOptionsString - .replace(" ", "") - .split(",") - if (doubleTapLengths.isEmpty()) throw PatchException("Invalid double-tap length elements") + val doubleTapLengths = listOf(3, 5, 10, 15, 20, 30, 60, 120, 180, 240) document("res/values/arrays.xml").use { document -> fun Element.removeAllChildren() { @@ -56,10 +51,9 @@ val addMoreDoubleTapToSeekLengthOptionsPatch = resourcePatch( entries.removeAllChildren() doubleTapLengths.forEach { length -> - val item = document.createElement("item") - item.textContent = length - entries.appendChild(item) - values.appendChild(item.cloneNode(true)) + document.createElement("item").apply { textContent = length.toString() } + .also(entries::appendChild) + .cloneNode(true).let(values::appendChild) } } } From f22ea5507dc28739c2ede8b7e26e5b7990326d9b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 8 Mar 2026 02:07:34 +0100 Subject: [PATCH 04/41] feat(YouTube Music): Add `Change header` patch Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../music/patches/ChangeHeaderPatch.java | 37 +++ .../extension/music/settings/Settings.java | 2 + .../settings/YouTubeAndMusicSettings.java | 1 + .../layout/branding/CustomBrandingPatch.kt | 1 - .../branding/header/ChangeHeaderPatch.kt | 75 ++++++ .../branding/header/ChangeHeaderPatch.kt | 160 ++++++++++++ .../branding/header/ChangeHeaderPatch.kt | 237 ++++-------------- .../resources/addresources/values/arrays.xml | 22 +- .../resources/addresources/values/strings.xml | 8 +- 9 files changed, 351 insertions(+), 192 deletions(-) create mode 100644 extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeHeaderPatch.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/header/ChangeHeaderPatch.kt diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeHeaderPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeHeaderPatch.java new file mode 100644 index 0000000000..143bedd549 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeHeaderPatch.java @@ -0,0 +1,37 @@ +package app.revanced.extension.music.patches; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.ResourceType; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.music.settings.Settings; + +public class ChangeHeaderPatch { + public enum HeaderLogo { + DEFAULT(null), + REVANCED("revanced_header_dark"), + CUSTOM("revanced_header_custom_dark"); + + private final String drawableName; + + HeaderLogo(String drawableName) { + this.drawableName = drawableName; + } + + private Integer getDrawableId() { + if (drawableName == null) { + return null; + } + + int id = Utils.getResourceIdentifier(ResourceType.DRAWABLE, drawableName); + if (id == 0) { + Logger.printException(() -> + "Header drawable not found: " + drawableName + ); + Settings.HEADER_LOGO.resetToDefault(); + return null; + } + + return id; + } + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java index b2f61541a4..4ce48cbbbf 100644 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java @@ -2,6 +2,7 @@ package app.revanced.extension.music.settings; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; +import static app.revanced.extension.music.patches.ChangeHeaderPatch.*; import static app.revanced.extension.shared.settings.Setting.parent; import app.revanced.extension.shared.settings.YouTubeAndMusicSettings; @@ -28,6 +29,7 @@ public class Settings extends YouTubeAndMusicSettings { public static final BooleanSetting HIDE_NAVIGATION_BAR_UPGRADE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_upgrade_button", TRUE, true); public static final BooleanSetting HIDE_NAVIGATION_BAR = new BooleanSetting("revanced_music_hide_navigation_bar", FALSE, true); public static final BooleanSetting HIDE_NAVIGATION_BAR_LABEL = new BooleanSetting("revanced_music_hide_navigation_bar_labels", FALSE, true); + public static final EnumSetting HEADER_LOGO = new EnumSetting<>("revnaced_header_logo", HeaderLogo.DEFAULT, true); // Player public static final BooleanSetting CHANGE_MINIPLAYER_COLOR = new BooleanSetting("revanced_music_change_miniplayer_color", FALSE, true); diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java index 67adab4efa..8a0b7c8e18 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java @@ -11,4 +11,5 @@ public class YouTubeAndMusicSettings extends BaseSettings { // Miscellaneous public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false, "revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG)); + } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt index ff6b5326a5..d1b17e2e3f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt @@ -65,7 +65,6 @@ val customBrandingPatch = baseCustomBrandingPatch( mainActivityName = MUSIC_MAIN_ACTIVITY_NAME, activityAliasNameWithIntents = MUSIC_MAIN_ACTIVITY_NAME, preferenceScreen = PreferenceScreen.GENERAL, - block = { dependsOn(sharedExtensionPatch, disableSplashAnimationPatch) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt new file mode 100644 index 0000000000..d6c7886266 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt @@ -0,0 +1,75 @@ +package app.revanced.patches.music.layout.branding.header + +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.getInstruction +import app.revanced.patcher.extensions.wideLiteral +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.shared.layout.branding.header.changeHeaderPatch +import app.revanced.patches.shared.misc.mapping.ResourceType +import app.revanced.patches.shared.misc.mapping.resourceMappingPatch +import app.revanced.util.forEachInstructionAsSequence +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction + +private val targetResourceDirectoryNames = mapOf( + "drawable-hdpi" to "121x36 px", + "drawable-xhdpi" to "160x48 px", + "drawable-xxhdpi" to "240x72 px", + "drawable-xxxhdpi" to "320x96 px" +) + +private val variants = arrayOf("dark") +private val logoResourceNames = arrayOf("revanced_header_dark") + +private val headerDrawableNames = arrayOf( + "action_bar_logo_ringo2", + "ytm_logo_ringo2" +) + +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/music/patches/ChangeHeaderPatch;" + +private val changeHeaderBytecodePatch = bytecodePatch { + dependsOn(resourceMappingPatch) + + apply { + headerDrawableNames.forEach { drawableName -> + val drawableId = ResourceType.DRAWABLE[drawableName] + + forEachInstructionAsSequence({ _, method, instruction, index -> + if (instruction.wideLiteral != drawableId) return@forEachInstructionAsSequence null + + val register = method.getInstruction(index).registerA + + return@forEachInstructionAsSequence index to register + }) { method, (index, register) -> + method.addInstructions( + index + 1, + """ + invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->getHeaderDrawableId(I)I + move-result v$register + """, + ) + } + } + } +} + +@Suppress("unused") +val changeHeaderPatch = changeHeaderPatch( + targetResourceDirectoryNames = targetResourceDirectoryNames, + changeHeaderBytecodePatch = changeHeaderBytecodePatch, + logoResourceNames = logoResourceNames, + variants = variants, + preferenceScreen = PreferenceScreen.GENERAL, + compatiblePackages = arrayOf( + "com.google.android.apps.youtube.music" to setOf( + "7.29.52", + "8.10.52", + "8.37.56", + "8.40.54", + ), + ), + resourcesAppId = "music", +) diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/header/ChangeHeaderPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/header/ChangeHeaderPatch.kt new file mode 100644 index 0000000000..d03d18feb6 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/header/ChangeHeaderPatch.kt @@ -0,0 +1,160 @@ +package app.revanced.patches.shared.layout.branding.header + +import app.revanced.patcher.patch.Package +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.PatchException +import app.revanced.patcher.patch.ResourcePatchContext +import app.revanced.patcher.patch.resourcePatch +import app.revanced.patcher.patch.stringOption +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen +import app.revanced.patches.shared.misc.settings.preference.ListPreference +import app.revanced.util.ResourceGroup +import app.revanced.util.Utils.trimIndentMultiline +import app.revanced.util.copyResources +import java.io.File + +internal const val CUSTOM_HEADER_RESOURCE_NAME = "revanced_header_custom" + +@Suppress("unused") +fun changeHeaderPatch( + targetResourceDirectoryNames: Map, + changeHeaderBytecodePatch: Patch, + vararg compatiblePackages: Package, + variants: Array, + logoResourceNames: Array, + preferenceScreen: BasePreferenceScreen.Screen, + resourcesAppId: String, + applyBlock: ResourcePatchContext.() -> Unit = {}, +): Patch { + val customHeaderResourceFileNames = variants.map { variant -> + "${CUSTOM_HEADER_RESOURCE_NAME}_$variant.png" + }.toTypedArray() + + return resourcePatch( + name = "Change header", + description = "Adds an option to change the header logo in the top left corner of the app.", + ) { + dependsOn(addResourcesPatch, changeHeaderBytecodePatch) + + compatibleWith(packages = compatiblePackages) + + val custom by stringOption( + name = "Custom header logo", + description = """ + Folder with images to use as a custom header logo. + + The folder must contain one or more of the following folders, depending on the DPI of the device: + ${targetResourceDirectoryNames.keys.joinToString("\n") { "- $it" }} + + Each of the folders must contain all of the following files: + ${customHeaderResourceFileNames.joinToString("\n")} + + The image dimensions must be as follows: + ${targetResourceDirectoryNames.map { (dpi, dim) -> "- $dpi: $dim" }.joinToString("\n")} + """.trimIndentMultiline(), + ) + + apply { + addResources(resourcesAppId, "layout.branding.header.changeHeaderPatch") + + preferenceScreen.addPreferences( + if (custom == null) { + ListPreference("revanced_header_logo") + } else { + ListPreference( + key = "revanced_header_logo", + entriesKey = "revanced_header_logo_custom_entries", + entryValuesKey = "revanced_header_logo_custom_entry_values", + ) + }, + ) + + logoResourceNames.forEach { logo -> + variants.forEach { variant -> + copyResources( + "change-header", + ResourceGroup( + "drawable", + logo + "_" + variant + ".xml", + ), + ) + } + } + + // Copy custom template. Images are only used if settings + // are imported and a custom header is enabled. + targetResourceDirectoryNames.keys.forEach { dpi -> + variants.forEach { variant -> + copyResources( + "change-header", + ResourceGroup( + dpi, + resources = customHeaderResourceFileNames, + ), + ) + } + } + + applyBlock() + + // Copy user provided images last, so if an exception is thrown due to bad input. + if (custom != null) { + val customFile = File(custom!!.trim()) + if (!customFile.exists()) { + throw PatchException( + "The custom header path cannot be found: " + + customFile.absolutePath, + ) + } + + if (!customFile.isDirectory) { + throw PatchException( + "The custom header path must be a folder: " + + customFile.absolutePath, + ) + } + + var copiedFiles = false + + // For each source folder, copy the files to the target resource directories. + customFile.listFiles { file -> + file.isDirectory && file.name in targetResourceDirectoryNames + }!!.forEach { dpiSourceFolder -> + val targetDpiFolder = get("res").resolve(dpiSourceFolder.name) + if (!targetDpiFolder.exists()) { + // Should never happen. + throw IllegalStateException("Resource not found: $dpiSourceFolder") + } + + val customFiles = dpiSourceFolder.listFiles { file -> + file.isFile && file.name in customHeaderResourceFileNames + }!! + + if (customFiles.isNotEmpty() && customFiles.size != variants.size) { + throw PatchException( + "Both light/dark mode images " + + "must be specified but only found: " + customFiles.map { it.name }, + ) + } + + customFiles.forEach { imgSourceFile -> + val imgTargetFile = targetDpiFolder.resolve(imgSourceFile.name) + imgSourceFile.copyTo(target = imgTargetFile, overwrite = true) + + copiedFiles = true + } + } + + if (!copiedFiles) { + throw PatchException( + "Expected to find directories and files: " + + customHeaderResourceFileNames.contentToString() + + "\nBut none were found in the provided option file path: " + customFile.absolutePath, + ) + } + } + } + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt index 4555a64b3c..b7f1e6ad25 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt @@ -3,27 +3,26 @@ package app.revanced.patches.youtube.layout.branding.header import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.wideLiteral -import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patcher.patch.resourcePatch -import app.revanced.patcher.patch.stringOption import app.revanced.patcher.util.Document import app.revanced.patches.all.misc.resources.addResources -import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.shared.layout.branding.addBrandLicensePatch +import app.revanced.patches.shared.layout.branding.header.CUSTOM_HEADER_RESOURCE_NAME +import app.revanced.patches.shared.layout.branding.header.changeHeaderPatch +import app.revanced.patches.shared.layout.branding.header.variants import app.revanced.patches.shared.misc.mapping.ResourceType import app.revanced.patches.shared.misc.mapping.resourceMappingPatch -import app.revanced.patches.shared.misc.settings.preference.ListPreference import app.revanced.patches.youtube.misc.settings.PreferenceScreen -import app.revanced.util.ResourceGroup -import app.revanced.util.Utils.trimIndentMultiline -import app.revanced.util.copyResources import app.revanced.util.findElementByAttributeValueOrThrow import app.revanced.util.forEachInstructionAsSequence import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction -import java.io.File -private val variants = arrayOf("light", "dark") +internal val variants = arrayOf("light", "dark") + +private val logoResourceNames = arrayOf( + "revanced_header_minimal", + "revanced_header_rounded", +) private val targetResourceDirectoryNames = mapOf( "drawable-hdpi" to "194x72 px", @@ -32,25 +31,6 @@ private val targetResourceDirectoryNames = mapOf( "drawable-xxxhdpi" to "512x192 px", ) -/** - * Header logos built into this patch. - */ -private val logoResourceNames = arrayOf( - "revanced_header_minimal", - "revanced_header_rounded", -) - -/** - * Custom header resource/file name. - */ -private const val CUSTOM_HEADER_RESOURCE_NAME = "revanced_header_custom" - -/** - * Custom header resource/file names. - */ -private val customHeaderResourceFileNames = variants.map { variant -> - "${CUSTOM_HEADER_RESOURCE_NAME}_$variant.png" -}.toTypedArray() private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/ChangeHeaderPatch;" @@ -97,182 +77,61 @@ private val changeHeaderBytecodePatch = bytecodePatch { } } -@Suppress("unused") -val changeHeaderPatch = resourcePatch( - name = "Change header", - description = "Adds an option to change the header logo in the top left corner of the app.", -) { - dependsOn(addResourcesPatch, changeHeaderBytecodePatch) - compatibleWith( - "com.google.android.youtube"( +val changeHeaderPatch = changeHeaderPatch( + targetResourceDirectoryNames = targetResourceDirectoryNames, + changeHeaderBytecodePatch = changeHeaderBytecodePatch, + compatiblePackages = arrayOf( + "com.google.android.youtube" to setOf( "20.14.43", "20.21.37", "20.26.46", "20.31.42", "20.37.48", "20.40.45" - ), - ) - - val custom by stringOption( - name = "Custom header logo", - description = """ - Folder with images to use as a custom header logo. - - The folder must contain one or more of the following folders, depending on the DPI of the device: - ${targetResourceDirectoryNames.keys.joinToString("\n") { "- $it" }} - - Each of the folders must contain all of the following files: - ${customHeaderResourceFileNames.joinToString("\n")} - - The image dimensions must be as follows: - ${targetResourceDirectoryNames.map { (dpi, dim) -> "- $dpi: $dim" }.joinToString("\n")} - """.trimIndentMultiline(), - ) - - apply { - addResources("youtube", "layout.branding.changeHeaderPatch") - - PreferenceScreen.GENERAL.addPreferences( - if (custom == null) { - ListPreference("revanced_header_logo") - } else { - ListPreference( - key = "revanced_header_logo", - entriesKey = "revanced_header_logo_custom_entries", - entryValuesKey = "revanced_header_logo_custom_entry_values", - ) - }, ) + ), + variants = variants, + logoResourceNames = logoResourceNames, + preferenceScreen = PreferenceScreen.GENERAL, + resourcesAppId = "youtube", +) { + // Logo is replaced using an attribute reference. + document("res/values/attrs.xml").use { document -> + val resources = document.childNodes.item(0) - logoResourceNames.forEach { logo -> - variants.forEach { variant -> - copyResources( - "change-header", - ResourceGroup( - "drawable", - logo + "_" + variant + ".xml", - ), - ) - } + fun addAttributeReference(logoName: String) { + val item = document.createElement("attr") + item.setAttribute("format", "reference") + item.setAttribute("name", logoName) + resources.appendChild(item) } - // Copy custom template. Images are only used if settings - // are imported and a custom header is enabled. - targetResourceDirectoryNames.keys.forEach { dpi -> - variants.forEach { variant -> - copyResources( - "change-header", - ResourceGroup( - dpi, - *customHeaderResourceFileNames, - ), - ) - } - } + logoResourceNames.forEach { logoName -> addAttributeReference(logoName) } - // Logo is replaced using an attribute reference. - document("res/values/attrs.xml").use { document -> - val resources = document.childNodes.item(0) + addAttributeReference(CUSTOM_HEADER_RESOURCE_NAME) + } - fun addAttributeReference(logoName: String) { - val item = document.createElement("attr") - item.setAttribute("format", "reference") + // Add custom drawables to all styles that use the regular and premium logo. + document("res/values/styles.xml").use { document -> + arrayOf( + "Base.Theme.YouTube.Light" to "light", + "Base.Theme.YouTube.Dark" to "dark", + "CairoLightThemeRingo2Updates" to "light", + "CairoDarkThemeRingo2Updates" to "dark", + ).forEach { (style, mode) -> + val styleElement = document.childNodes.findElementByAttributeValueOrThrow("name", style) + + fun addDrawableElement(document: Document, logoName: String, mode: String) { + val item = document.createElement("item") item.setAttribute("name", logoName) - resources.appendChild(item) + item.textContent = "@drawable/${logoName}_$mode" + styleElement.appendChild(item) } - logoResourceNames.forEach { logoName -> - addAttributeReference(logoName) - } + logoResourceNames.forEach { logoName -> addDrawableElement(document, logoName, mode) } - addAttributeReference(CUSTOM_HEADER_RESOURCE_NAME) - } - - // Add custom drawables to all styles that use the regular and premium logo. - document("res/values/styles.xml").use { document -> - arrayOf( - "Base.Theme.YouTube.Light" to "light", - "Base.Theme.YouTube.Dark" to "dark", - "CairoLightThemeRingo2Updates" to "light", - "CairoDarkThemeRingo2Updates" to "dark", - ).forEach { (style, mode) -> - val styleElement = document.childNodes.findElementByAttributeValueOrThrow( - "name", - style, - ) - - fun addDrawableElement(document: Document, logoName: String, mode: String) { - val item = document.createElement("item") - item.setAttribute("name", logoName) - item.textContent = "@drawable/${logoName}_$mode" - styleElement.appendChild(item) - } - - logoResourceNames.forEach { logoName -> - addDrawableElement(document, logoName, mode) - } - - addDrawableElement(document, CUSTOM_HEADER_RESOURCE_NAME, mode) - } - } - - // Copy user provided images last, so if an exception is thrown due to bad input. - if (custom != null) { - val customFile = File(custom!!.trim()) - if (!customFile.exists()) { - throw PatchException( - "The custom header path cannot be found: " + - customFile.absolutePath, - ) - } - - if (!customFile.isDirectory) { - throw PatchException( - "The custom header path must be a folder: " + - customFile.absolutePath, - ) - } - - var copiedFiles = false - - // For each source folder, copy the files to the target resource directories. - customFile.listFiles { file -> - file.isDirectory && file.name in targetResourceDirectoryNames - }!!.forEach { dpiSourceFolder -> - val targetDpiFolder = get("res").resolve(dpiSourceFolder.name) - if (!targetDpiFolder.exists()) { - // Should never happen. - throw IllegalStateException("Resource not found: $dpiSourceFolder") - } - - val customFiles = dpiSourceFolder.listFiles { file -> - file.isFile && file.name in customHeaderResourceFileNames - }!! - - if (customFiles.isNotEmpty() && customFiles.size != variants.size) { - throw PatchException( - "Both light/dark mode images " + - "must be specified but only found: " + customFiles.map { it.name }, - ) - } - - customFiles.forEach { imgSourceFile -> - val imgTargetFile = targetDpiFolder.resolve(imgSourceFile.name) - imgSourceFile.copyTo(target = imgTargetFile, overwrite = true) - - copiedFiles = true - } - } - - if (!copiedFiles) { - throw PatchException( - "Expected to find directories and files: " + - customHeaderResourceFileNames.contentToString() + - "\nBut none were found in the provided option file path: " + customFile.absolutePath, - ) - } + addDrawableElement(document, CUSTOM_HEADER_RESOURCE_NAME, mode) } } -} +} \ No newline at end of file diff --git a/patches/src/main/resources/addresources/values/arrays.xml b/patches/src/main/resources/addresources/values/arrays.xml index 230b9b8f38..a29199d359 100644 --- a/patches/src/main/resources/addresources/values/arrays.xml +++ b/patches/src/main/resources/addresources/values/arrays.xml @@ -188,6 +188,26 @@ + + + @string/revanced_header_logo_entry_1 + @string/revanced_header_logo_entry_2 + + + DEFAULT + REGULAR + + + @string/revanced_header_logo_entry_1 + @string/revanced_header_logo_entry_2 + @string/revanced_header_logo_entry_3 + + + DEFAULT + REGULAR + CUSTOM + + @@ -427,7 +447,7 @@ MODERN_3 - + @string/revanced_header_logo_entry_1 @string/revanced_header_logo_entry_2 diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 95da75bad4..27cec1b91f 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1568,7 +1568,7 @@ Swipe to expand or close" YT ReVanced YT - + Header logo Default Regular @@ -1801,6 +1801,12 @@ Video playback with AV1 may stutter or drop frames." + + Header logo + Default + ReVanced + Custom + YT Music ReVanced Music ReVanced From bc08ecf785a7a471c69c154bb08bdbc33a90edfe Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 8 Mar 2026 21:44:31 +0100 Subject: [PATCH 05/41] feat(YouTube Music): Add `Change start page` patch Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../music/patches/ChangeStartPagePatch.java | 98 +++++++++++++++ .../extension/music/settings/Settings.java | 2 + .../layout/startpage/ChangeStartPagePatch.kt | 113 ++++++++++++++++++ .../music/layout/startpage/Fingerprints.kt | 17 +++ .../patches/music/misc/gms/Fingerprints.kt | 11 -- .../resources/addresources/values/arrays.xml | 26 ++++ .../resources/addresources/values/strings.xml | 15 +++ 7 files changed, 271 insertions(+), 11 deletions(-) create mode 100644 extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeStartPagePatch.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/ChangeStartPagePatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/Fingerprints.kt delete mode 100644 patches/src/main/kotlin/app/revanced/patches/music/misc/gms/Fingerprints.kt diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeStartPagePatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeStartPagePatch.java new file mode 100644 index 0000000000..db3622f91b --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeStartPagePatch.java @@ -0,0 +1,98 @@ +package app.revanced.extension.music.patches; + +import static java.lang.Boolean.TRUE; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.music.settings.Settings; + +@SuppressWarnings("unused") +public final class ChangeStartPagePatch { + private static final String SHORTCUT_ACTION = "com.google.android.youtube.music.action.shortcut"; + private static final String SHORTCUT_CLASS_DESCRIPTOR = "com.google.android.apps.youtube.music.activities.InternalMusicActivity"; + private static final String SHORTCUT_TYPE = "com.google.android.youtube.music.action.shortcut_type"; + private static final String SHORTCUT_ID_SEARCH = "Eh4IBRDTnQEYmgMiEwiZn+H0r5WLAxVV5OcDHcHRBmPqpd25AQA="; + private static final int SHORTCUT_TYPE_SEARCH = 1; + + + public enum StartPage { + DEFAULT("", null), + CHARTS("FEmusic_charts", TRUE), + EXPLORE("FEmusic_explore", TRUE), + HISTORY("FEmusic_history", TRUE), + LIBRARY("FEmusic_library_landing", TRUE), + PLAYLISTS("FEmusic_liked_playlists", TRUE), + PODCASTS("FEmusic_non_music_audio", TRUE), + SUBSCRIPTIONS("FEmusic_library_corpus_artists", TRUE), + EPISODES_FOR_LATER("VLSE", TRUE), + LIKED_MUSIC("VLLM", TRUE), + SEARCH("", false); + + @NonNull + final String id; + + @Nullable + final Boolean isBrowseId; + + StartPage(@NonNull String id, @Nullable Boolean isBrowseId) { + this.id = id; + this.isBrowseId = isBrowseId; + } + + private boolean isBrowseId() { + return TRUE.equals(isBrowseId); + } + } + + private static final String ACTION_MAIN = "android.intent.action.MAIN"; + + public static String overrideBrowseId(@Nullable String original) { + var startPage = Settings.CHANGE_START_PAGE.get(); + + if (!startPage.isBrowseId()) { + return original; + } + + if (!"FEmusic_home".equals(original)) { + return original; + } + + String overrideBrowseId = startPage.id; + if (overrideBrowseId.isEmpty()) { + return original; + } + + Logger.printDebug(() -> "Changing browseId to: " + startPage.name()); + return overrideBrowseId; + } + + public static void overrideIntentActionOnCreate(@NonNull Activity activity, + @Nullable Bundle savedInstanceState) { + if (savedInstanceState != null) return; + + var startPage = Settings.CHANGE_START_PAGE.get(); + if (startPage != StartPage.SEARCH) return; + + var originalIntent = activity.getIntent(); + if (originalIntent == null) return; + + if (ACTION_MAIN.equals(originalIntent.getAction())) { + Logger.printDebug(() -> "Cold start: Launching search activity directly"); + var searchIntent = new Intent(); + + searchIntent.setAction(SHORTCUT_ACTION); + searchIntent.setClassName(activity, SHORTCUT_CLASS_DESCRIPTOR); + searchIntent.setPackage(activity.getPackageName()); + searchIntent.putExtra(SHORTCUT_TYPE, SHORTCUT_TYPE_SEARCH); + searchIntent.putExtra(SHORTCUT_ACTION, SHORTCUT_ID_SEARCH); + + activity.startActivity(searchIntent); + } + } +} \ No newline at end of file diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java index 4ce48cbbbf..2e057681d6 100644 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java @@ -3,6 +3,7 @@ package app.revanced.extension.music.settings; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import static app.revanced.extension.music.patches.ChangeHeaderPatch.*; +import static app.revanced.extension.music.patches.ChangeStartPagePatch.*; import static app.revanced.extension.shared.settings.Setting.parent; import app.revanced.extension.shared.settings.YouTubeAndMusicSettings; @@ -17,6 +18,7 @@ public class Settings extends YouTubeAndMusicSettings { public static final BooleanSetting HIDE_GET_PREMIUM_LABEL = new BooleanSetting("revanced_music_hide_get_premium_label", TRUE, true); // General + public static final EnumSetting CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true); public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_music_hide_cast_button", TRUE, true); public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_music_hide_category_bar", FALSE, true); public static final BooleanSetting HIDE_HISTORY_BUTTON = new BooleanSetting("revanced_music_hide_history_button", FALSE, true); diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/ChangeStartPagePatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/ChangeStartPagePatch.kt new file mode 100644 index 0000000000..9703780202 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/ChangeStartPagePatch.kt @@ -0,0 +1,113 @@ +package app.revanced.patches.music.layout.startpage + +import app.revanced.patcher.extensions.addInstruction +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.fieldReference +import app.revanced.patcher.extensions.getInstruction +import app.revanced.patcher.extensions.instructions +import app.revanced.patcher.extensions.string +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.music.misc.settings.settingsPatch +import app.revanced.patches.music.shared.mainActivityOnCreateMethod +import app.revanced.patches.shared.misc.settings.preference.ListPreference +import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference +import app.revanced.util.indexOfFirstInstruction +import app.revanced.util.indexOfFirstInstructionReversed +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction + +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/music/patches/ChangeStartPagePatch;" + +val changeStartPagePatch = bytecodePatch( + name = "Change start page", + description = "Adds an option to set which page the app opens in instead of the homepage.", +) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch + ) + + compatibleWith( + "com.google.android.apps.youtube.music"( + "7.29.52", + "8.10.52", + "8.37.56", + "8.40.54", + ) + ) + + apply { + addResources("music", "layout.startpage.changeStartPagePatch") + + PreferenceScreen.GENERAL.addPreferences( + PreferenceCategory( + titleKey = null, + sorting = PreferenceScreenPreference.Sorting.UNSORTED, + tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory", + preferences = setOf( + ListPreference( + key = "revanced_change_start_page", + tag = "app.revanced.extension.shared.settings.preference.SortedListPreference" + ) + ) + ) + ) + + coldStartUpMethodMatch.let { match -> + match.method.apply { + val defaultBrowseIdIndex = match[-1] + + val browseIdIndex = indexOfFirstInstructionReversed(defaultBrowseIdIndex) { + opcode == Opcode.IGET_OBJECT && fieldReference?.type == "Ljava/lang/String;" + } + + if (browseIdIndex != -1) { + val browseIdRegister = + getInstruction(browseIdIndex).registerA + addInstructions( + browseIdIndex + 1, + """ + invoke-static/range { v$browseIdRegister .. v$browseIdRegister }, $EXTENSION_CLASS_DESCRIPTOR->overrideBrowseId(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$browseIdRegister + """ + ) + } else { + instructions.mapIndexedNotNull { index, instr -> + if (instr.opcode == Opcode.RETURN_OBJECT) index else null + }.reversed().forEach { returnIndex -> + val returnRegister = + getInstruction(returnIndex).registerA + + addInstructions( + returnIndex, + """ + invoke-static/range { v$returnRegister .. v$returnRegister }, $EXTENSION_CLASS_DESCRIPTOR->overrideBrowseId(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$returnRegister + """ + ) + } + } + } + } + + mainActivityOnCreateMethod.apply { + val p0 = implementation!!.registerCount - 2 + val p1 = p0 + 1 + + addInstruction( + 0, + "invoke-static/range { v$p0 .. v$p1 }, " + + "$EXTENSION_CLASS_DESCRIPTOR->" + + "overrideIntentActionOnCreate(Landroid/app/Activity;Landroid/os/Bundle;)V" + ) + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/Fingerprints.kt new file mode 100644 index 0000000000..77d8e2c8a9 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/Fingerprints.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.music.layout.startpage + +import app.revanced.patcher.composingFirstMethod +import app.revanced.patcher.instructions +import app.revanced.patcher.invoke +import app.revanced.patcher.parameterTypes +import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.returnType + +internal val BytecodePatchContext.coldStartUpMethodMatch by composingFirstMethod { + returnType("Ljava/lang/String;") + parameterTypes() + instructions( + "FEmusic_library_sideloaded_tracks"(), + "FEmusic_home"() + ) +} diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/Fingerprints.kt deleted file mode 100644 index faa6de9ff6..0000000000 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/Fingerprints.kt +++ /dev/null @@ -1,11 +0,0 @@ -package app.revanced.patches.music.misc.gms - -import app.revanced.patcher.* -import app.revanced.patcher.patch.BytecodePatchContext - -internal val BytecodePatchContext.musicActivityOnCreateMethod by gettingFirstMethodDeclaratively { - name("onCreate") - definingClass("/MusicActivity;") - returnType("V") - parameterTypes("Landroid/os/Bundle;") -} diff --git a/patches/src/main/resources/addresources/values/arrays.xml b/patches/src/main/resources/addresources/values/arrays.xml index a29199d359..edfd33fed6 100644 --- a/patches/src/main/resources/addresources/values/arrays.xml +++ b/patches/src/main/resources/addresources/values/arrays.xml @@ -188,6 +188,32 @@ + + + @string/revanced_change_start_page_entry_default + @string/revanced_change_start_page_entry_charts + @string/revanced_change_start_page_entry_episodes_for_later + @string/revanced_change_start_page_entry_explore + @string/revanced_change_start_page_entry_history + @string/revanced_change_start_page_entry_library + @string/revanced_change_start_page_entry_liked_music + @string/revanced_change_start_page_entry_playlists + @string/revanced_change_start_page_entry_search + @string/revanced_change_start_page_entry_subscriptions + + + DEFAULT + CHARTS + EPISODES_FOR_LATER + EXPLORE + HISTORY + LIBRARY + LIKED_MUSIC + PLAYLISTS + SEARCH + SUBSCRIPTIONS + + @string/revanced_header_logo_entry_1 diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 27cec1b91f..d6ddd2c484 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1844,6 +1844,21 @@ Video playback with AV1 may stutter or drop frames." Search button is hidden Search button is shown + + Change start page + Default + Charts + Episodes for later + Explore + History + Library + Liked music + Playlists + Podcasts + Search + Subscriptions + + Hide category bar Category bar is hidden From 8c2445f92fbad8abe142fcbb90d71d160b0b4384 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 8 Mar 2026 22:39:22 +0100 Subject: [PATCH 06/41] feat(YouTube): Add `Override 'Open in YouTube Music' button` patch Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../app/revanced/extension/shared/Utils.java | 23 ++++ ...OverrideOpenInYouTubeMusicButtonPatch.java | 60 +++++++++ .../extension/youtube/settings/Settings.java | 1 + .../layout/buttons/music/Fingerprints.kt | 18 +++ .../OverrideOpenInYouTubeMusicButtonPatch.kt | 122 ++++++++++++++++++ .../resources/addresources/values/strings.xml | 10 +- 6 files changed, 230 insertions(+), 4 deletions(-) create mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OverrideOpenInYouTubeMusicButtonPatch.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java index e978301a65..18c6959aa4 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -373,6 +373,29 @@ public class Utils { return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(ResourceType.ARRAY, resourceIdentifierName)); } + /** + * Checks if a specific app package is installed and enabled on the device. + * + * @param packageName The application package name to check (e.g., "app.morphe.android.apps.youtube.music"). + * @return True if the package is installed and enabled, false otherwise. + */ + public static boolean isPackageEnabled(String packageName) { + Context context = getContext(); + if (context == null || !isNotEmpty(packageName)) { + return false; + } + + try { + PackageManager pm = context.getPackageManager(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return pm.getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(0)).enabled; + } else { + return pm.getApplicationInfo(packageName, 0).enabled; + } + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } public interface MatchFilter { boolean matches(T object); } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OverrideOpenInYouTubeMusicButtonPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OverrideOpenInYouTubeMusicButtonPatch.java new file mode 100644 index 0000000000..8e12b40482 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OverrideOpenInYouTubeMusicButtonPatch.java @@ -0,0 +1,60 @@ +package app.revanced.extension.youtube.patches; + + +import android.content.Intent; +import android.net.Uri; + +import androidx.annotation.Nullable; + +import app.revanced.extension.shared.Utils; +import app.revanced.extension.youtube.settings.Settings; + +@SuppressWarnings("unused") +public class OverrideOpenInYouTubeMusicButtonPatch { + + private static final String YOUTUBE_MUSIC_PACKAGE_NAME = "com.google.android.apps.youtube.music"; + + private static final Boolean overrideButton = Settings.OVERRIDE_OPEN_IN_YOUTUBE_MUSIC_BUTTON.get(); + + private static final String overridePackageName = getOverridePackageName(); + + @SuppressWarnings("SameReturnValue") + public static String getOverridePackageName() { + return ""; // Value is replaced during patching. + } + + public static @Nullable Intent overrideSetPackage(@Nullable Intent intent, @Nullable String packageName) { + if (intent == null || !overrideButton) return intent; + + if (YOUTUBE_MUSIC_PACKAGE_NAME.equals(packageName)) { + if (Utils.isNotEmpty(overridePackageName) && Utils.isPackageEnabled(overridePackageName)) { + return intent.setPackage(overridePackageName); + } + + return intent.setPackage(null); + } + + return intent.setPackage(packageName); + } + + public static @Nullable Intent overrideSetData(@Nullable Intent intent, @Nullable Uri uri) { + if (intent == null || uri == null || !overrideButton) return intent; + + String uriString = uri.toString(); + if (uriString.contains(YOUTUBE_MUSIC_PACKAGE_NAME)) { + if ("market".equals(uri.getScheme()) || uriString.contains("play.google.com/store/apps")) { + intent.setData(Uri.parse("https://music.youtube.com/")); + + if (Utils.isNotEmpty(overridePackageName) && Utils.isPackageEnabled(overridePackageName)) { + intent.setPackage(overridePackageName); + } else { + intent.setPackage(null); + } + + return intent; + } + } + + return intent.setData(uri); + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index d69ed54e2d..5bc9bfc6e4 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -296,6 +296,7 @@ public class Settings extends YouTubeAndMusicSettings { public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE, "revanced_remove_viewer_discretion_dialog_user_dialog_message"); public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message"); + public static final BooleanSetting OVERRIDE_OPEN_IN_YOUTUBE_MUSIC_BUTTON = new BooleanSetting("revanced_override_open_in_youtube_music_button", TRUE, true); public static final EnumSetting CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true); public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true, new ChangeStartPageTypeAvailability()); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/Fingerprints.kt new file mode 100644 index 0000000000..3e6241754a --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/Fingerprints.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.youtube.layout.buttons.music + +import app.revanced.patcher.accessFlags +import app.revanced.patcher.definingClass +import app.revanced.patcher.gettingFirstMethodDeclaratively +import app.revanced.patcher.name +import app.revanced.patcher.parameterTypes +import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.returnType +import com.android.tools.smali.dexlib2.AccessFlags + +internal val BytecodePatchContext.getOverridePackageNameMethod by gettingFirstMethodDeclaratively { + name("getOverridePackageName") + definingClass(EXTENSION_CLASS_DESCRIPTOR) + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + returnType("Ljava/lang/String;") + parameterTypes() +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt new file mode 100644 index 0000000000..014472b9e7 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt @@ -0,0 +1,122 @@ +package app.revanced.patches.youtube.layout.buttons.music + +import app.revanced.patcher.extensions.getInstruction +import app.revanced.patcher.extensions.methodReference +import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.resourcePatch +import app.revanced.patcher.patch.stringOption +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.youtube.misc.settings.PreferenceScreen +import app.revanced.patches.youtube.misc.settings.settingsPatch +import app.revanced.util.forEachInstructionAsSequence +import app.revanced.util.returnEarly +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstruction +import org.w3c.dom.Element + +internal const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/OverrideOpenInYouTubeMusicButtonPatch;" + +val packageNameOption = stringOption( + name = "YouTube Music package name", + description = "The package name of the YouTube Music app to open when clicking the 'Open in YouTube Music' button.", + default = "app.revanced.android.apps.youtube.music", + values = mapOf( + "Original package name" to "com.google.android.apps.youtube.music", + "ReVanced default package name" to "app.revanced.android.apps.youtube.music" + ), + required = true, +) + +private val overrideOpenInYouTubeMusicManifestResourcePatch = resourcePatch { + apply { + val packageName by packageNameOption + + document("AndroidManifest.xml").use { document -> + val queriesList = document.getElementsByTagName("queries") + + val queries = if (queriesList.length > 0) queriesList.item(0) as Element + else document.createElement("queries").also(document::appendChild) + + document.createElement("package").apply { + setAttribute("android:name", packageName) + }.let(queries::appendChild) + } + } +} + +@Suppress("unused") +val overrideOpenInYouTubeMusicButtonPatch = bytecodePatch( + name = "Override 'Open in YouTube Music' button", + description = "Overrides the button to open YouTube Music under a different package name. " + + "By default, it overrides to the ReVanced default package name of YouTube Music.", +) { + dependsOn( + settingsPatch, + addResourcesPatch, + overrideOpenInYouTubeMusicManifestResourcePatch + ) + + compatibleWith( + "com.google.android.youtube"( + "20.14.43", + "20.21.37", + "20.26.46", + "20.31.42", + "20.37.48", + "20.40.45" + ), + ) + + val packageName by packageNameOption() + + apply { + addResources("youtube", "layout.buttons.music.overrideOpenInYouTubeMusicButtonPatch") + + PreferenceScreen.GENERAL.addPreferences( + PreferenceCategory( + titleKey = null, + tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory", + preferences = setOf(SwitchPreference(key = "revanced_override_open_in_youtube_music_button")) + ) + ) + + getOverridePackageNameMethod.returnEarly(packageName!!) + + forEachInstructionAsSequence({ _, _, instruction, index -> + if (instruction.opcode != Opcode.INVOKE_VIRTUAL) return@forEachInstructionAsSequence null + val reference = instruction.methodReference ?: return@forEachInstructionAsSequence null + if (reference.definingClass != "Landroid/content/Intent;") return@forEachInstructionAsSequence null + + when (reference.name) { + "setPackage" if reference.parameterTypes == listOf("Ljava/lang/String;") -> + index to "overrideSetPackage(Landroid/content/Intent;Ljava/lang/String;)Landroid/content/Intent;" + + "setData" if reference.parameterTypes == listOf("Landroid/net/Uri;") -> + index to "overrideSetData(Landroid/content/Intent;Landroid/net/Uri;)Landroid/content/Intent;" + + else -> null + } + }) { method, (index, methodDescriptor) -> + val invokeString = when (val instruction = method.getInstruction(index)) { + is RegisterRangeInstruction -> + "invoke-static/range { v${instruction.startRegister} .. v${instruction.startRegister + instruction.registerCount - 1} }" + + is FiveRegisterInstruction -> + "invoke-static { v${instruction.registerC}, v${instruction.registerD} }" + + else -> return@forEachInstructionAsSequence + } + + method.replaceInstruction( + index, + "$invokeString, $EXTENSION_CLASS_DESCRIPTOR->$methodDescriptor" + ) + } + } +} diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index d6ddd2c484..6c05ad9dae 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1111,10 +1111,7 @@ To show the Audio track menu, change \'Spoof video streams\' to \'Android No SDK Hide end screen suggested video - "End screen suggested video is hidden when autoplay is turned off - -Autoplay can be changed in YouTube settings: -Settings → Playback → Autoplay next video" + "End screen suggested video is hidden End screen suggested video is shown @@ -1442,6 +1439,11 @@ If later turned off, it is recommended to clear the app data to prevent UI bugs. 20.13.41 - Restore non collapsed video action bar 20.05.46 - Restore transcript functionality + + Override \'Open in YouTube Music\' button + \'Open in YouTube Music\' button opens your target music app + \'Open in YouTube Music\' button opens the original app + Change start page Default From 6d8c94d0d2881f2be96efb3c0ab12db4124c8622 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 8 Mar 2026 22:40:58 +0100 Subject: [PATCH 07/41] fix(YouTube - Hide layout components): Resolve "Hide Explore the podcast" not working Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../youtube/patches/litho/DescriptionComponentsFilter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java index 9990aa46a4..d2288867f0 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java @@ -57,12 +57,13 @@ public final class DescriptionComponentsFilter extends Filter { playlistSectionGroupList.addAll( new ByteArrayFilterGroup( Settings.HIDE_EXPLORE_COURSE_SECTION, - "yt_outline_creator_academy", // For Disable bold icons. + "yt_outline_creator_academy", "yt_outline_experimental_graduation_cap" ), new ByteArrayFilterGroup( Settings.HIDE_EXPLORE_PODCAST_SECTION, - "FEpodcasts_destination" + "FEpodcasts_destination", + "yt_outline_experimental_podcast" ) ); From 5aec6cd3b7a93f0a992a8e9bd1a60e0f96ba4b09 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 8 Mar 2026 22:47:53 +0100 Subject: [PATCH 08/41] feat(YouTube Music): Add experimental support for `9.09.52` Co-authored-by: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../revanced/patches/music/misc/androidauto/Fingerprints.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/Fingerprints.kt index 6fb1748eac..ecd2f905a0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/Fingerprints.kt @@ -7,14 +7,15 @@ import app.revanced.patcher.invoke import app.revanced.patcher.parameterTypes import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.returnType +import app.revanced.patcher.strings import com.android.tools.smali.dexlib2.iface.ClassDef internal val BytecodePatchContext.checkCertificateMethod by gettingFirstMethodDeclaratively( "X509", ) { returnType("Z") - parameterTypes("Ljava/lang/String;") - instructions("Failed to get certificate"(String::contains)) + parameterTypes("L") + strings("X509", "isPartnerSHAFingerprint") } internal val BytecodePatchContext.searchMediaItemsConstructorMethod by gettingFirstMethodDeclaratively( From 749552881571f59b459a3ae07cd0a67898146658 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 11 Mar 2026 02:00:45 +0100 Subject: [PATCH 09/41] fix: Use encoded native byte array buffer to filter Litho components --- .../extension/shared/ConversionContext.java | 13 ++ .../app/revanced/extension/shared/Utils.java | 14 +- .../patches/litho/LithoFilterPatch.java | 179 +----------------- .../patches/LayoutReloadObserverPatch.java | 49 +++++ .../patches/LazilyConvertedElementPatch.java | 33 ++++ .../youtube/patches/PlayerControlsPatch.java | 2 +- .../patches/ReturnYouTubeDislikePatch.java | 59 +++--- .../litho/DescriptionComponentsFilter.java | 24 +-- .../litho/HorizontalShelvesFilter.java | 102 ++++++++++ .../patches/litho/LayoutComponentsFilter.java | 162 ++-------------- .../youtube/patches/litho/ShortsFilter.java | 130 +++---------- .../extension/youtube/settings/Settings.java | 1 - .../extension/youtube/shared/PlayerType.kt | 1 + .../misc/litho/filter/LithoFilterPatch.kt | 17 +- .../litho/context/ConversionContextPatch.kt | 154 +++++++++++++++ .../shared/misc/litho/context/Fingerprints.kt | 25 +++ .../shared/misc/litho/filter/Fingerprints.kt | 77 +++++--- .../misc/litho/filter/LithoFilterPatch.kt | 146 ++++++-------- .../youtube/ad/general/HideAdsPatch.kt | 2 + .../layout/hide/general/Fingerprints.kt | 12 +- .../hide/general/HideLayoutComponentsPatch.kt | 25 +-- .../shelves/HideHorizontalShelvesPatch.kt | 28 +++ .../layout/hide/shorts/Fingerprints.kt | 15 -- .../hide/shorts/HideShortsComponentsPatch.kt | 42 +--- .../ReturnYouTubeDislikePatch.kt | 32 ++-- .../youtube/misc/litho/filter/Fingerprints.kt | 3 + .../misc/litho/filter/LithoFilterPatch.kt | 42 +--- .../youtube/misc/litho/lazily/Fingerprints.kt | 39 ++++ .../lazily/LazilyConvertedElementHookPatch.kt | 54 ++++++ .../observer/LayoutReloadObserverPatch.kt | 24 +++ .../resources/addresources/values/strings.xml | 6 +- 31 files changed, 781 insertions(+), 731 deletions(-) create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/ConversionContext.java create mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LayoutReloadObserverPatch.java create mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LazilyConvertedElementPatch.java create mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/context/ConversionContextPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/context/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shelves/HideHorizontalShelvesPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/lazily/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/lazily/LazilyConvertedElementHookPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/observer/LayoutReloadObserverPatch.kt diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/ConversionContext.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/ConversionContext.java new file mode 100644 index 0000000000..ea1f94fc54 --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/ConversionContext.java @@ -0,0 +1,13 @@ +package app.revanced.extension.shared; + +public final class ConversionContext { + /** + * Interface to use obfuscated methods. + */ + public interface ContextInterface { + // Methods implemented by patch. + StringBuilder patch_getPathBuilder(); + + String patch_getIdentifier(); + } +} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java index 18c6959aa4..6f90da0a8a 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -376,7 +376,7 @@ public class Utils { /** * Checks if a specific app package is installed and enabled on the device. * - * @param packageName The application package name to check (e.g., "app.morphe.android.apps.youtube.music"). + * @param packageName The application package name to check (e.g., "app.revanced.android.apps.youtube.music"). * @return True if the package is installed and enabled, false otherwise. */ public static boolean isPackageEnabled(String packageName) { @@ -396,6 +396,18 @@ public class Utils { return false; } } + + public static boolean startsWithAny(String value, String...targets) { + if (isNotEmpty(value)) { + for (String string : targets) { + if (isNotEmpty(string) && value.startsWith(string)) { + return true; + } + } + } + return false; + } + public interface MatchFilter { boolean matches(T object); } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java index dc60f5d7da..1af697236c 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java @@ -5,12 +5,10 @@ import androidx.annotation.Nullable; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.Collections; import java.util.List; -import java.util.Map; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.StringTrieSearch; @@ -123,11 +121,6 @@ public final class LithoFilterPatch { */ private static final boolean EXTRACT_IDENTIFIER_FROM_BUFFER = false; - /** - * Turns on additional logging, used for development purposes only. - */ - public static final boolean DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER = false; - /** * String suffix for components. * Can be any of: ".eml", ".eml-fe", ".e-b", ".eml-js", "e-js-b" @@ -146,19 +139,6 @@ public final class LithoFilterPatch { */ private static final ThreadLocal bufferThreadLocal = new ThreadLocal<>(); - /** - * Identifier to protocol buffer mapping. Only used for 20.22+. - * Thread local is needed because filtering is multithreaded and each thread can load - * a different component with the same identifier. - */ - private static final ThreadLocal> identifierToBufferThread = new ThreadLocal<>(); - - /** - * Global shared buffer. Used only if the buffer is not found in the ThreadLocal. - */ - private static final Map identifierToBufferGlobal - = Collections.synchronizedMap(createIdentifierToBufferMap()); - private static final StringTrieSearch pathSearchTree = new StringTrieSearch(); private static final StringTrieSearch identifierSearchTree = new StringTrieSearch(); @@ -211,126 +191,11 @@ public final class LithoFilterPatch { } } - private static Map createIdentifierToBufferMap() { - // It's unclear how many items should be cached. This is a guess. - return Utils.createSizeRestrictedMap(100); - } - - /** - * Helper function that differs from {@link Character#isDigit(char)} - * as this only matches ascii and not Unicode numbers. - */ - private static boolean isAsciiNumber(byte character) { - return '0' <= character && character <= '9'; - } - - private static boolean isAsciiLowerCaseLetter(byte character) { - return 'a' <= character && character <= 'z'; - } - - /** - * Injection point. Called off the main thread. - * Targets 20.22+ - */ - public static void setProtoBuffer(byte[] buffer) { - if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) { - StringBuilder builder = new StringBuilder(); - LithoFilterParameters.findAsciiStrings(builder, buffer); - Logger.printDebug(() -> "New buffer: " + builder); - } - - // The identifier always seems to start very close to the buffer start. - // Highest identifier start index ever observed is 50, with most around 30 to 40. - // The buffer can be very large with up to 200kb has been observed, - // so the search is restricted to only the start. - final int maxBufferStartIndex = 500; // 10x expected upper bound. - - // Could use Boyer-Moore-Horspool since the string is ASCII and has a limited number of - // unique characters, but it seems to be slower since the extra overhead of checking the - // bad character array negates any performance gain of skipping a few extra subsearches. - int emlIndex = -1; - final int emlStringLength = LITHO_COMPONENT_EXTENSION_BYTES.length; - final int lastBufferIndexToCheckFrom = Math.min(maxBufferStartIndex, buffer.length - emlStringLength); - for (int i = 0; i < lastBufferIndexToCheckFrom; i++) { - boolean match = true; - for (int j = 0; j < emlStringLength; j++) { - if (buffer[i + j] != LITHO_COMPONENT_EXTENSION_BYTES[j]) { - match = false; - break; - } - } - if (match) { - emlIndex = i; - break; - } - } - - if (emlIndex < 0) { - // Buffer is not used for creating a new litho component. - if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) { - Logger.printDebug(() -> "Could not find eml index"); - } - return; - } - - int startIndex = emlIndex - 1; - while (startIndex > 0) { - final byte character = buffer[startIndex]; - int startIndexFinal = startIndex; - if (isAsciiLowerCaseLetter(character) || isAsciiNumber(character) || character == '_') { - // Valid character for the first path element. - startIndex--; - } else { - startIndex++; - break; - } - } - - // Strip away any numbers on the start of the identifier, which can - // be from random data in the buffer before the identifier starts. - while (true) { - final byte character = buffer[startIndex]; - if (isAsciiNumber(character)) { - startIndex++; - } else { - break; - } - } - - // Find the pipe character after the identifier. - int endIndex = -1; - for (int i = emlIndex, length = buffer.length; i < length; i++) { - if (buffer[i] == '|') { - endIndex = i; - break; - } - } - if (endIndex < 0) { - if (BaseSettings.DEBUG.get()) { - Logger.printException(() -> "Debug: Could not find buffer identifier"); - } - return; - } - - String identifier = new String(buffer, startIndex, endIndex - startIndex, StandardCharsets.US_ASCII); - if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) { - Logger.printDebug(() -> "Found buffer for identifier: " + identifier); - } - identifierToBufferGlobal.put(identifier, buffer); - - Map map = identifierToBufferThread.get(); - if (map == null) { - map = createIdentifierToBufferMap(); - identifierToBufferThread.set(map); - } - map.put(identifier, buffer); - } - /** * Injection point. Called off the main thread. * Targets 20.21 and lower. */ - public static void setProtoBuffer(@Nullable ByteBuffer buffer) { + public static void setProtobufBuffer(@Nullable ByteBuffer buffer) { if (buffer == null || !buffer.hasArray()) { // It appears the buffer can be cleared out just before the call to #filter() // Ignore this null value and retain the last buffer that was set. @@ -347,44 +212,18 @@ public final class LithoFilterPatch { /** * Injection point. */ - public static boolean isFiltered(String identifier, @Nullable String accessibilityId, - @Nullable String accessibilityText, StringBuilder pathBuilder) { + public static boolean isFiltered(ContextInterface contextInterface, @Nullable byte[] bytes, + @Nullable String accessibilityId, @Nullable String accessibilityText) { try { + String identifier = contextInterface.patch_getIdentifier(); + StringBuilder pathBuilder = contextInterface.patch_getPathBuilder(); if (identifier.isEmpty() || pathBuilder.length() == 0) { return false; } - byte[] buffer = null; - if (EXTRACT_IDENTIFIER_FROM_BUFFER) { - final int pipeIndex = identifier.indexOf('|'); - if (pipeIndex >= 0) { - // If the identifier contains no pipe, then it's not an ".eml" identifier - // and the buffer is not uniquely identified. Typically, this only happens - // for subcomponents where buffer filtering is not used. - String identifierKey = identifier.substring(0, pipeIndex); - - var map = identifierToBufferThread.get(); - if (map != null) { - buffer = map.get(identifierKey); - } - - if (buffer == null) { - // Buffer for thread local not found. Use the last buffer found from any thread. - buffer = identifierToBufferGlobal.get(identifierKey); - - if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER && buffer == null) { - // No buffer is found for some components, such as - // shorts_lockup_cell.eml on channel profiles. - // For now, just ignore this and filter without a buffer. - if (BaseSettings.DEBUG.get()) { - Logger.printException(() -> "Debug: Could not find buffer for identifier: " + identifier); - } - } - } - } - } else { - buffer = bufferThreadLocal.get(); - } + byte[] buffer = EXTRACT_IDENTIFIER_FROM_BUFFER + ? bytes + : bufferThreadLocal.get(); // Potentially the buffer may have been null or never set up until now. // Use an empty buffer so the litho id/path filters that do not use a buffer still work. diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LayoutReloadObserverPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LayoutReloadObserverPatch.java new file mode 100644 index 0000000000..95108882c6 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LayoutReloadObserverPatch.java @@ -0,0 +1,49 @@ +package app.revanced.extension.youtube.patches; + +import androidx.annotation.NonNull; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import app.revanced.extension.shared.Utils; +import app.revanced.extension.youtube.shared.PlayerType; + +/** + * Here is an unintended behavior: + *

+ * 1. The user does not hide Shorts in the Subscriptions tab, but hides them otherwise. + * 2. Goes to the Subscriptions tab and scrolls to where Shorts is. + * 3. Opens a regular video. + * 4. Minimizes the video and turns off the screen. + * 5. Turns the screen on and maximizes the video. + * 6. Shorts belonging to related videos are not hidden. + *

+ * Here is an explanation of this special issue: + *

+ * When the user minimizes the video, turns off the screen, and then turns it back on, + * the components below the player are reloaded, and at this moment the PlayerType is [WATCH_WHILE_MINIMIZED]. + * (Shorts belonging to related videos are also reloaded) + * Since the PlayerType is [WATCH_WHILE_MINIMIZED] at this moment, the navigation tab is checked. + * (Even though PlayerType is [WATCH_WHILE_MINIMIZED], this is a Shorts belonging to a related video) + *

+ * As a workaround for this special issue, if a video actionbar is detected, which is one of the components below the player, + * it is treated as being in the same state as [WATCH_WHILE_MAXIMIZED]. + */ +@SuppressWarnings("unused") +public class LayoutReloadObserverPatch { + private static final String COMPACTIFY_VIDEO_ACTION_BAR_PREFIX = "compactify_video_action_bar.e"; + private static final String VIDEO_ACTION_BAR_PREFIX = "video_action_bar.e"; + public static final AtomicBoolean isActionBarVisible = new AtomicBoolean(false); + + public static void onLazilyConvertedElementLoaded(@NonNull String identifier, + @NonNull List treeNodeResultList) { + if (!Utils.startsWithAny(identifier, COMPACTIFY_VIDEO_ACTION_BAR_PREFIX, VIDEO_ACTION_BAR_PREFIX)) { + return; + } + + if (PlayerType.getCurrent() == PlayerType.WATCH_WHILE_MINIMIZED && + isActionBarVisible.compareAndSet(false, true)) { + Utils.runOnMainThreadDelayed(() -> isActionBarVisible.compareAndSet(true, false), 500); + } + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LazilyConvertedElementPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LazilyConvertedElementPatch.java new file mode 100644 index 0000000000..1fb4737ab6 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LazilyConvertedElementPatch.java @@ -0,0 +1,33 @@ +package app.revanced.extension.youtube.patches; + +import java.util.List; + +import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.ConversionContext.ContextInterface; + + +@SuppressWarnings("unused") +public class LazilyConvertedElementPatch { + private static final String LAZILY_CONVERTED_ELEMENT = "LazilyConvertedElement"; + + /** + * Injection point. + */ + public static void onTreeNodeResultLoaded(ContextInterface contextInterface, List treeNodeResultList) { + if (treeNodeResultList == null || treeNodeResultList.isEmpty()) { + return; + } + String firstElement = treeNodeResultList.get(0).toString(); + if (!LAZILY_CONVERTED_ELEMENT.equals(firstElement)) { + return; + } + String identifier = contextInterface.patch_getIdentifier(); + if (Utils.isNotEmpty(identifier)) { + onLazilyConvertedElementLoaded(identifier, treeNodeResultList); + } + } + + private static void onLazilyConvertedElementLoaded(String identifier, List treeNodeResultList) { + // Code added by patch. + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerControlsPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerControlsPatch.java index 482c0c3c49..8f5ed8cc8b 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerControlsPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerControlsPatch.java @@ -54,6 +54,6 @@ public class PlayerControlsPatch { // noinspection EmptyMethod private static void fullscreenButtonVisibilityChanged(boolean isVisible) { - // Code added during patching. + // Code added by patch. } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ReturnYouTubeDislikePatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ReturnYouTubeDislikePatch.java index beaa3eac82..4f351c1e77 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ReturnYouTubeDislikePatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ReturnYouTubeDislikePatch.java @@ -19,24 +19,25 @@ import app.revanced.extension.shared.Utils; import app.revanced.extension.youtube.patches.litho.ReturnYouTubeDislikeFilter; import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike; import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.youtube.shared.PlayerType; /** * Handles all interaction of UI patch components. - * + *

* Known limitation: * The implementation of Shorts litho requires blocking the loading the first Short until RYD has completed. * This is because it modifies the dislikes text synchronously, and if the RYD fetch has * not completed yet then the UI will be temporarily frozen. - * + *

* A (yet to be implemented) solution that fixes this problem. Any one of: * - Modify patch to hook onto the Shorts Litho TextView, and update the dislikes text asynchronously. * - Find a way to force Litho to rebuild it's component tree, - * and use that hook to force the shorts dislikes to update after the fetch is completed. + * and use that hook to force the shorts dislikes to update after the fetch is completed. * - Hook into the dislikes button image view, and replace the dislikes thumb down image with a - * generated image of the number of dislikes, then update the image asynchronously. This Could - * also be used for the regular video player to give a better UI layout and completely remove - * the need for the Rolling Number patches. + * generated image of the number of dislikes, then update the image asynchronously. This Could + * also be used for the regular video player to give a better UI layout and completely remove + * the need for the Rolling Number patches. */ @SuppressWarnings("unused") public class ReturnYouTubeDislikePatch { @@ -100,7 +101,7 @@ public class ReturnYouTubeDislikePatch { /** * Injection point. - * + *

* Logs if new litho text layout is used. */ public static boolean useNewLithoTextCreation(boolean useNewLithoTextCreation) { @@ -113,28 +114,28 @@ public class ReturnYouTubeDislikePatch { /** * Injection point. - * + *

* For Litho segmented buttons and Litho Shorts player. */ @NonNull - public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, - @NonNull CharSequence original) { - return onLithoTextLoaded(conversionContext, original, false); + public static CharSequence onLithoTextLoaded(ContextInterface contextInterface, + CharSequence original) { + return onLithoTextLoaded(contextInterface, original, false); } /** * Called when a litho text component is initially created, * and also when a Span is later reused again (such as scrolling off/on screen). - * + *

* This method is sometimes called on the main thread, but it is usually called _off_ the main thread. * This method can be called multiple times for the same UI element (including after dislikes was added). * - * @param original Original char sequence was created or reused by Litho. + * @param original Original char sequence was created or reused by Litho. * @param isRollingNumber If the span is for a Rolling Number. * @return The original char sequence (if nothing should change), or a replacement char sequence that contains dislikes. */ @NonNull - private static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, + private static CharSequence onLithoTextLoaded(ContextInterface contextInterface, @NonNull CharSequence original, boolean isRollingNumber) { try { @@ -142,17 +143,16 @@ public class ReturnYouTubeDislikePatch { return original; } - String conversionContextString = conversionContext.toString(); - - if (Settings.RYD_ENABLED.get()) { // FIXME: Remove this. - Logger.printDebug(() -> "RYD conversion context: " + conversionContext); - } - - if (isRollingNumber && !conversionContextString.contains("video_action_bar.e")) { + String identifier = contextInterface.patch_getIdentifier(); + if (isRollingNumber && (identifier == null || !identifier.contains("video_action_bar.e"))) { return original; } - if (conversionContextString.contains("segmented_like_dislike_button.e")) { + StringBuilder pathBuilder = contextInterface.patch_getPathBuilder(); + String path = pathBuilder.toString(); + + if (path.contains("segmented_like_dislike_button.e")) { + // Regular video. ReturnYouTubeDislike videoData = currentVideoData; if (videoData == null) { @@ -169,13 +169,11 @@ public class ReturnYouTubeDislikePatch { return original; // No need to check for Shorts in the context. } - if (Utils.containsAny(conversionContextString, - "|shorts_dislike_button.e", "|reel_dislike_button.e")) { + if (Utils.containsAny(path, "|shorts_dislike_button.e", "|reel_dislike_button.e")) { return getShortsSpan(original, true); } - if (Utils.containsAny(conversionContextString, - "|shorts_like_button.e", "|reel_like_button.e")) { + if (Utils.containsAny(path, "|shorts_like_button.e", "|reel_like_button.e")) { if (!Utils.containsNumber(original)) { Logger.printDebug(() -> "Replacing hidden likes count"); return getShortsSpan(original, false); @@ -230,10 +228,9 @@ public class ReturnYouTubeDislikePatch { /** * Injection point. */ - public static String onRollingNumberLoaded(@NonNull Object conversionContext, - @NonNull String original) { + public static String onRollingNumberLoaded(ContextInterface contextInterface, String original) { try { - CharSequence replacement = onLithoTextLoaded(conversionContext, original, true); + CharSequence replacement = onLithoTextLoaded(contextInterface, original, true); String replacementString = replacement.toString(); if (!replacementString.equals(original)) { @@ -248,7 +245,7 @@ public class ReturnYouTubeDislikePatch { /** * Injection point. - * + *

* Called for all usage of Rolling Number. * Modifies the measured String text width to include the left separator and padding, if needed. */ @@ -489,7 +486,7 @@ public class ReturnYouTubeDislikePatch { /** * Injection point. - * + *

* Called when the user likes or dislikes. * * @param vote int that matches {@link Vote#value} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java index d2288867f0..6b2590e904 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java @@ -139,22 +139,14 @@ public final class DescriptionComponentsFilter extends Filter { // If the description panel is opened in a Shorts, PlayerType is 'HIDDEN', // so 'PlayerType.getCurrent().isMaximizedOrFullscreen()' does not guarantee that the description panel is open. // Instead, use the engagement id to check if the description panel is opened. - if (!EngagementPanel.isDescription() - // The user can minimize the player while the engagement panel is open. - // - // In this case, the engagement panel is treated as open. - // (If the player is dismissed, the engagement panel is considered closed) - // - // Therefore, the following exceptions can occur: - // 1. The user opened a regular video and opened the description panel. - // 2. The 'horizontalShelf' elements were hidden. - // 3. The user minimized the player. - // 4. The user manually refreshed the library tab without dismissing the player. - // 5. Since the engagement panel is treated as open, the history shelf is filtered. - // - // To handle these exceptions, filtering is not performed even when the player is minimized. - || PlayerType.getCurrent() == PlayerType.WATCH_WHILE_MINIMIZED - ) { + if (!EngagementPanel.isDescription()) { + return false; + } + + // PlayerType when the description panel is opened: NONE, HIDDEN, + // WATCH_WHILE_MAXIMIZED, WATCH_WHILE_FULLSCREEN, WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN. + PlayerType playerType = PlayerType.getCurrent(); + if (!playerType.isNoneOrHidden() && !playerType.isMaximizedOrFullscreen()) { return false; } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java new file mode 100644 index 0000000000..9e99122778 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java @@ -0,0 +1,102 @@ +package app.revanced.extension.youtube.patches.litho; + +import static app.revanced.extension.youtube.patches.LayoutReloadObserverPatch.isActionBarVisible; + +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; +import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; +import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList; +import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.youtube.shared.EngagementPanel; +import app.revanced.extension.youtube.shared.NavigationBar; +import app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; +import app.revanced.extension.youtube.shared.PlayerType; + +@SuppressWarnings("unused") +final class HorizontalShelvesFilter extends Filter { + private final ByteArrayFilterGroupList descriptionBuffers = new ByteArrayFilterGroupList(); + private final ByteArrayFilterGroupList generalBuffers = new ByteArrayFilterGroupList(); + + public HorizontalShelvesFilter() { + StringFilterGroup horizontalShelves = new StringFilterGroup(null, "horizontal_shelf.e"); + addPathCallbacks(horizontalShelves); + + descriptionBuffers.addAll( + new ByteArrayFilterGroup( + Settings.HIDE_ATTRIBUTES_SECTION, + // May no longer work on v20.31+, even though the component is still there. + "cell_video_attribute" + ), + new ByteArrayFilterGroup( + Settings.HIDE_FEATURED_PLACES_SECTION, + "yt_fill_experimental_star", + "yt_fill_star" + ), + new ByteArrayFilterGroup( + Settings.HIDE_GAMING_SECTION, + "yt_outline_experimental_gaming", + "yt_outline_gaming" + ), + new ByteArrayFilterGroup( + Settings.HIDE_MUSIC_SECTION, + "yt_outline_experimental_audio", + "yt_outline_audio" + ), + new ByteArrayFilterGroup( + Settings.HIDE_QUIZZES_SECTION, + "post_base_wrapper_slim" + ) + ); + + generalBuffers.addAll( + new ByteArrayFilterGroup( + Settings.HIDE_CREATOR_STORE_SHELF, + "shopping_item_card_list" + ), + new ByteArrayFilterGroup( + Settings.HIDE_PLAYABLES, + "FEmini_app_destination" + ), + new ByteArrayFilterGroup( + Settings.HIDE_TICKET_SHELF, + "ticket_item.e" + ) + ); + } + + private boolean hideShelves() { + if (!Settings.HIDE_HORIZONTAL_SHELVES.get()) { + return false; + } + // Must check player type first, as search bar can be active behind the player. + if (PlayerType.getCurrent().isMaximizedOrFullscreen() || isActionBarVisible.get()) { + return false; + } + // Must check second, as search can be from any tab. + if (NavigationBar.isSearchBarActive()) { + return true; + } + return NavigationButton.getSelectedNavigationButton() != NavigationButton.LIBRARY; + } + + @Override + public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + if (contentIndex != 0) { + return false; + } + if (generalBuffers.check(buffer).isFiltered()) { + return true; + } + if (EngagementPanel.isDescription()) { + PlayerType playerType = PlayerType.getCurrent(); + // PlayerType when the description panel is opened: NONE, HIDDEN, + // WATCH_WHILE_MAXIMIZED, WATCH_WHILE_FULLSCREEN, WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN. + if (!playerType.isMaximizedOrFullscreen() && !playerType.isNoneOrHidden()) { + return false; + } + return descriptionBuffers.check(buffer).isFiltered(); + } + return hideShelves(); + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java index dda1f46e95..5a00090230 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java @@ -7,7 +7,6 @@ import android.graphics.drawable.Drawable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.TextUtils; -import android.util.Pair; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -16,16 +15,12 @@ import android.widget.TextView; import androidx.annotation.Nullable; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import app.revanced.extension.shared.ByteTrieSearch; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; @@ -38,10 +33,6 @@ import app.revanced.extension.youtube.shared.PlayerType; @SuppressWarnings("unused") public final class LayoutComponentsFilter extends Filter { - private static final StringTrieSearch mixPlaylistsContextExceptions = new StringTrieSearch( - "V.ED", // Playlist browseId. - "java.lang.ref.WeakReference" - ); private static final ByteArrayFilterGroup mixPlaylistsBufferExceptions = new ByteArrayFilterGroup( null, "cell_description_body", @@ -84,11 +75,6 @@ public final class LayoutComponentsFilter extends Filter { private final StringFilterGroup chipBar; private final StringFilterGroup channelProfile; private final StringFilterGroupList channelProfileGroupList; - private final StringFilterGroup horizontalShelves; - private final ByteArrayFilterGroup playablesBuffer; - private final ByteArrayFilterGroup ticketShelfBuffer; - private final ByteArrayFilterGroup playerShoppingShelfBuffer; - private final ByteTrieSearch descriptionSearch; public LayoutComponentsFilter() { exceptions.addPatterns( @@ -264,12 +250,6 @@ public final class LayoutComponentsFilter extends Filter { "mini_game_card.e" ); - // Playable horizontal shelf header. - playablesBuffer = new ByteArrayFilterGroup( - null, - "FEmini_app_destination" - ); - final var quickActions = new StringFilterGroup( Settings.HIDE_QUICK_ACTIONS, "quick_actions" @@ -317,7 +297,7 @@ public final class LayoutComponentsFilter extends Filter { ); final var forYouShelf = new StringFilterGroup( - Settings.HIDE_FOR_YOU_SHELF, + Settings.HIDE_HORIZONTAL_SHELVES, "mixed_content_shelf" ); @@ -361,51 +341,6 @@ public final class LayoutComponentsFilter extends Filter { ) ); - horizontalShelves = new StringFilterGroup( - null, // Setting is checked in isFiltered() - "horizontal_video_shelf.e", - "horizontal_shelf.e", - "horizontal_shelf_inline.e", - "horizontal_tile_shelf.e" - ); - - ticketShelfBuffer = new ByteArrayFilterGroup( - null, - "ticket_item.e" - ); - - playerShoppingShelfBuffer = new ByteArrayFilterGroup( - null, - "shopping_item_card_list" - ); - - // Work around for unique situation where filtering is based on the setting, - // but it must not fall over to other filters if the setting is _not_ enabled. - // This is only needed for the horizontal shelf that is used so extensively everywhere. - descriptionSearch = new ByteTrieSearch(); - List.of( - new Pair<>(Settings.HIDE_FEATURED_PLACES_SECTION, "yt_fill_star"), - new Pair<>(Settings.HIDE_FEATURED_PLACES_SECTION, "yt_fill_experimental_star"), - new Pair<>(Settings.HIDE_GAMING_SECTION, "yt_outline_gaming"), - new Pair<>(Settings.HIDE_GAMING_SECTION, "yt_outline_experimental_gaming"), - new Pair<>(Settings.HIDE_MUSIC_SECTION, "yt_outline_audio"), - new Pair<>(Settings.HIDE_MUSIC_SECTION, "yt_outline_experimental_audio"), - new Pair<>(Settings.HIDE_QUIZZES_SECTION, "post_base_wrapper_slim"), - // May no longer work on v20.31+, even though the component is still there. - new Pair<>(Settings.HIDE_ATTRIBUTES_SECTION, "cell_video_attribute") - ).forEach(pair -> { - BooleanSetting setting = pair.first; - descriptionSearch.addPattern(pair.second.getBytes(StandardCharsets.UTF_8), - (textSearched, matchedStartIndex, matchedLength, callbackParameter) -> { - //noinspection unchecked - AtomicReference hide = (AtomicReference) callbackParameter; - hide.set(setting.get()); - return true; - } - ); - } - ); - addPathCallbacks( artistCard, audioTrackButton, @@ -422,7 +357,6 @@ public final class LayoutComponentsFilter extends Filter { emergencyBox, expandableMetadata, forYouShelf, - horizontalShelves, imageShelf, infoPanel, latestPosts, @@ -484,47 +418,6 @@ public final class LayoutComponentsFilter extends Filter { && joinMembershipButton.check(buffer).isFiltered(); } - // Horizontal shelves are used everywhere in the app. And to prevent the generic "hide shelves" - // from incorrectly hiding other stuff that has its own hide filters, - // the more specific shelf filters must check first _and_ they must halt falling over - // to other filters if the buffer matches but the setting is off. - if (matchedGroup == horizontalShelves) { - if (contentIndex != 0) return false; - - AtomicReference descriptionFilterResult = new AtomicReference<>(null); - if (descriptionSearch.matches(buffer, descriptionFilterResult)) { - return descriptionFilterResult.get(); - } - - // Check if others are off before searching. - final boolean hideShelves = Settings.HIDE_HORIZONTAL_SHELVES.get(); - final boolean hideTickets = Settings.HIDE_TICKET_SHELF.get(); - final boolean hidePlayables = Settings.HIDE_PLAYABLES.get(); - final boolean hidePlayerShoppingShelf = Settings.HIDE_CREATOR_STORE_SHELF.get(); - if (!hideShelves && !hideTickets && !hidePlayables && !hidePlayerShoppingShelf) - return false; - - if (ticketShelfBuffer.check(buffer).isFiltered()) return hideTickets; - if (playablesBuffer.check(buffer).isFiltered()) return hidePlayables; - if (playerShoppingShelfBuffer.check(buffer).isFiltered()) - return hidePlayerShoppingShelf; - - // 20.31+ when exiting fullscreen after watching for a while or when resuming the app, - // then sometimes the buffer isn't correct and the player shopping shelf is shown. - // If filtering reaches this point then there are no more shelves that could be in the player. - // If shopping shelves are set to hidden and the player is active, then assume - // it's the shopping shelf. - if (hidePlayerShoppingShelf) { - PlayerType type = PlayerType.getCurrent(); - if (type == PlayerType.WATCH_WHILE_MAXIMIZED || type == PlayerType.WATCH_WHILE_FULLSCREEN - || type == PlayerType.WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN) { - return true; - } - } - - return hideShelves && hideShelves(); - } - if (matchedGroup == chipBar) { return contentIndex == 0 && NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY; } @@ -536,7 +429,7 @@ public final class LayoutComponentsFilter extends Filter { * Injection point. * Called from a different place then the other filters. */ - public static boolean filterMixPlaylists(Object conversionContext, @Nullable byte[] buffer) { + public static boolean filterMixPlaylists(@Nullable byte[] buffer) { // Edit: This hook may no longer be needed, and mix playlist filtering // might be possible using the existing litho filters. try { @@ -551,13 +444,7 @@ public final class LayoutComponentsFilter extends Filter { if (mixPlaylists.check(buffer).isFiltered() // Prevent hiding the description of some videos accidentally. - && !mixPlaylistsBufferExceptions.check(buffer).isFiltered() - // Prevent playlist items being hidden, if a mix playlist is present in it. - // Check last since it requires creating a context string. - // - // FIXME: The conversion context passed in does not always generate a valid toString. - // This string check may no longer be needed, or the patch may be broken. - && !mixPlaylistsContextExceptions.matches(conversionContext.toString())) { + && !mixPlaylistsBufferExceptions.check(buffer).isFiltered()) { Logger.printDebug(() -> "Filtered mix playlist"); return true; } @@ -757,31 +644,6 @@ public final class LayoutComponentsFilter extends Filter { : original; } - private static boolean hideShelves() { - // Horizontal shelves are used for music/game links in video descriptions, - // such as https://youtube.com/watch?v=W8kI1na3S2M - if (PlayerType.getCurrent().isMaximizedOrFullscreen()) { - return false; - } - - // Must check search bar after player type, since search results - // can be in the background behind an open player. - if (NavigationBar.isSearchBarActive()) { - return true; - } - - // Do not hide if the navigation back button is visible, - // otherwise the content shelves in the explore/music/courses pages are hidden. - if (NavigationBar.isBackButtonVisible()) { - return false; - } - - // Check navigation button last. - // Only filter if the library tab is not selected. - // This check is important as the shelf layout is used for the library tab playlists. - return NavigationButton.getSelectedNavigationButton() != NavigationButton.LIBRARY; - } - /** * Injection point. */ @@ -919,8 +781,8 @@ public final class LayoutComponentsFilter extends Filter { /** * Injection point. * - * @param typedString Keywords typed in the search bar. - * @return Whether the setting is enabled and the typed string is empty. + * @param typedString Keywords typed in the search bar. + * @return Whether the setting is enabled and the typed string is empty. */ public static boolean hideYouMayLikeSection(String typedString) { return Settings.HIDE_YOU_MAY_LIKE_SECTION.get() @@ -932,13 +794,13 @@ public final class LayoutComponentsFilter extends Filter { /** * Injection point. * - * @param searchTerm This class contains information related to search terms. - * The {@code toString()} method of this class overrides the search term. - * @param endpoint Endpoint related with the search term. - * For search history, this value is: - * '/complete/deleteitems?client=youtube-android-pb&delq=${searchTerm}&deltok=${token}'. - * For search suggestions, this value is null or empty. - * @return Whether search term is a search history or not. + * @param searchTerm This class contains information related to search terms. + * The {@code toString()} method of this class overrides the search term. + * @param endpoint Endpoint related with the search term. + * For search history, this value is: + * '/complete/deleteitems?client=youtube-android-pb&delq=${searchTerm}&deltok=${token}'. + * For search suggestions, this value is null or empty. + * @return Whether search term is a search history or not. */ public static boolean isSearchHistory(Object searchTerm, String endpoint) { boolean isSearchHistory = endpoint != null && endpoint.contains("/delete"); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java index b01d8db393..ecbe75a259 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java @@ -1,26 +1,22 @@ package app.revanced.extension.youtube.patches.litho; +import static app.revanced.extension.youtube.patches.LayoutReloadObserverPatch.isActionBarVisible; import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; import android.view.View; -import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.FilterGroup.*; import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList; -import app.revanced.extension.shared.patches.litho.LithoFilterPatch; -import app.revanced.extension.shared.settings.BooleanSetting; +import app.revanced.extension.shared.patches.litho.FilterGroupList.StringFilterGroupList; import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar; import java.lang.ref.WeakReference; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import app.revanced.extension.shared.Logger; -import app.revanced.extension.youtube.patches.VersionCheckPatch; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.EngagementPanel; import app.revanced.extension.youtube.shared.NavigationBar; @@ -34,16 +30,6 @@ public final class ShortsFilter extends Filter { "reel_action_bar.", // Regular Shorts. "reels_player_overlay_layout." // Shorts ads. }; - private static final Map REEL_ACTION_BUTTONS_MAP = new HashMap<>() { - { - // Like button and Dislike button can be hidden with Litho filter. - // put(0, Settings.HIDE_SHORTS_LIKE_BUTTON); - // put(1, Settings.HIDE_SHORTS_DISLIKE_BUTTON); - put(2, Settings.HIDE_SHORTS_COMMENTS_BUTTON); - put(3, Settings.HIDE_SHORTS_SHARE_BUTTON); - put(4, Settings.HIDE_SHORTS_REMIX_BUTTON); - } - }; private final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.e"; /** @@ -90,8 +76,8 @@ public final class ShortsFilter extends Filter { private final ByteArrayFilterGroupList suggestedActionsBuffer = new ByteArrayFilterGroupList(); private final StringFilterGroup shortsActionBar; - private final StringFilterGroup videoActionButton; - private final ByteArrayFilterGroupList videoActionButtonBuffer = new ByteArrayFilterGroupList(); + private final StringFilterGroup shortsActionButton; + private final StringFilterGroupList shortsActionButtonGroupList = new StringFilterGroupList(); public ShortsFilter() { // @@ -289,7 +275,7 @@ public final class ShortsFilter extends Filter { "yt_outline_template_add_" ); - videoActionButton = new StringFilterGroup( + shortsActionButton = new StringFilterGroup( null, // Can be any of: // button.eml @@ -308,36 +294,26 @@ public final class ShortsFilter extends Filter { shortsCompactFeedVideo, shelfHeaderPath, joinButton, subscribeButton, paidPromotionLabel, livePreview, suggestedAction, pausedOverlayButtons, channelBar, infoPanel, previewComment, autoDubbedLabel, fullVideoLinkLabel, videoTitle, useSoundButton, soundButton, stickers, - reelCarousel, reelSoundMetadata, likeFountain, likeButton, dislikeButton + reelCarousel, reelSoundMetadata, likeFountain, likeButton, dislikeButton, shortsActionBar ); - // Legacy hiding of Shorts action buttons. Because of 20.31+ buffer changes - // it's currently not possible to hide these using buffer filtering. - // See alternative hiding strategy in hideActionButtons(). - if (!VersionCheckPatch.IS_20_22_OR_GREATER) { - addPathCallbacks(shortsActionBar); - - // - // All other action buttons. - // - videoActionButtonBuffer.addAll( - new ByteArrayFilterGroup( - Settings.HIDE_SHORTS_COMMENTS_BUTTON, - "reel_comment_button", - "youtube_shorts_comment_outline" - ), - new ByteArrayFilterGroup( - Settings.HIDE_SHORTS_SHARE_BUTTON, - "reel_share_button", - "youtube_shorts_share_outline" - ), - new ByteArrayFilterGroup( - Settings.HIDE_SHORTS_REMIX_BUTTON, - "reel_remix_button", - "youtube_shorts_remix_outline" - ) - ); - } + // + // All other action buttons. + // + shortsActionButtonGroupList.addAll( + new StringFilterGroup( + Settings.HIDE_SHORTS_COMMENTS_BUTTON, + "id.reel_comment_button" + ), + new StringFilterGroup( + Settings.HIDE_SHORTS_SHARE_BUTTON, + "id.reel_share_button" + ), + new StringFilterGroup( + Settings.HIDE_SHORTS_REMIX_BUTTON, + "id.reel_remix_button" + ) + ); // // Suggested actions. @@ -482,8 +458,10 @@ public final class ShortsFilter extends Filter { // Video action buttons (comment, share, remix) have the same path. // Like and dislike are separate path filters and don't require buffer searching. if (matchedGroup == shortsActionBar) { - return videoActionButton.check(path).isFiltered() - && videoActionButtonBuffer.check(buffer).isFiltered(); + if (shortsActionButton.check(path).isFiltered()) { + return shortsActionButtonGroupList.check(accessibility).isFiltered(); + } + return false; } if (matchedGroup == suggestedAction) { @@ -525,7 +503,7 @@ public final class ShortsFilter extends Filter { } // Must check player type first, as search bar can be active behind the player. - if (PlayerType.getCurrent().isMaximizedOrFullscreen()) { + if (PlayerType.getCurrent().isMaximizedOrFullscreen() || isActionBarVisible.get()) { return EngagementPanel.isDescription() ? hideVideoDescription // Player video description panel opened. : hideHome; // For now, consider Shorts under video player the same as the home feed. @@ -555,58 +533,6 @@ public final class ShortsFilter extends Filter { }; } - /** - * Injection point. - *

- * Hide action buttons by index. - *

- * Regular video action buttons vary in order by video, country, and account. - * Therefore, hiding buttons by index may hide unintended buttons. - *

- * Shorts action buttons are almost always in the same order. - * (From top to bottom: Like, Dislike, Comment, Share, Remix). - * Therefore, we can hide Shorts action buttons by index. - * - * @param pathBuilder Same as pathBuilder used in {@link LithoFilterPatch}. - * @param treeNodeResultList List containing Litho components. - */ - public static void hideActionButtons(StringBuilder pathBuilder, List treeNodeResultList) { - try { - if (pathBuilder == null || pathBuilder.length() == 0 || treeNodeResultList == null) { - return; - } - int size = treeNodeResultList.size(); - - // The minimum size of the target List is 4. - if (size < 4) { - return; - } - String path = pathBuilder.toString(); - - if (!Utils.containsAny(path, REEL_ACTION_BAR_PATHS) - // Regular Shorts: [ComponentType, ComponentType, ComponentType, ComponentType, ComponentType] - // Shorts ads: [ComponentType, ComponentType, ComponentType, ComponentType] (No Remix button) - || !COMPONENT_TYPE.equals(treeNodeResultList.get(0).toString())) { - return; - } - // Removing elements without iterating through the list in reverse order will throw an exception. - for (int i = size - 1; i > -1; i--) { - // treeNodeResult is each button. - Object treeNodeResult = treeNodeResultList.get(i); - if (treeNodeResult != null) { - BooleanSetting setting = REEL_ACTION_BUTTONS_MAP.get(i); - if (setting != null && setting.get()) { - int finalI = i; - Logger.printDebug(() -> "Hiding action button by index: " + finalI + ", key: " + setting.key); - treeNodeResultList.remove(i); - } - } - } - } catch (Exception ex) { - Logger.printException(() -> "hideActionButtons failed", ex); - } - } - /** * Injection point. */ diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 5bc9bfc6e4..89ba33d934 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -147,7 +147,6 @@ public class Settings extends YouTubeAndMusicSettings { public static final BooleanSetting HIDE_CHANNEL_TAB = new BooleanSetting("revanced_hide_channel_tab", FALSE); public static final StringSetting HIDE_CHANNEL_TAB_FILTER_STRINGS = new StringSetting("revanced_hide_channel_tab_filter_strings", "", true, parent(HIDE_CHANNEL_TAB)); public static final BooleanSetting HIDE_COMMUNITY_BUTTON = new BooleanSetting("revanced_hide_community_button", TRUE); - public static final BooleanSetting HIDE_FOR_YOU_SHELF = new BooleanSetting("revanced_hide_for_you_shelf", FALSE); public static final BooleanSetting HIDE_JOIN_BUTTON = new BooleanSetting("revanced_hide_join_button", FALSE); public static final BooleanSetting HIDE_LINKS_PREVIEW = new BooleanSetting("revanced_hide_links_preview", TRUE); public static final BooleanSetting HIDE_MEMBERS_SHELF = new BooleanSetting("revanced_hide_members_shelf", TRUE); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt index 555dbfaeaa..95315a86ed 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt @@ -138,5 +138,6 @@ enum class PlayerType { fun isMaximizedOrFullscreen(): Boolean { return this == WATCH_WHILE_MAXIMIZED || this == WATCH_WHILE_FULLSCREEN + || this == WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN } } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/litho/filter/LithoFilterPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/litho/filter/LithoFilterPatch.kt index ebb0edc8a6..8e14dcfcb8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/litho/filter/LithoFilterPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/litho/filter/LithoFilterPatch.kt @@ -1,25 +1,16 @@ package app.revanced.patches.music.misc.litho.filter import app.revanced.patcher.extensions.addInstruction -import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patches.music.misc.extension.sharedExtensionPatch -import app.revanced.patches.music.shared.conversionContextToStringMethod import app.revanced.patches.shared.misc.litho.filter.EXTENSION_CLASS_DESCRIPTOR import app.revanced.patches.shared.misc.litho.filter.lithoFilterPatch -import app.revanced.patches.shared.misc.litho.filter.protobufBufferReferenceLegacyMethod -import app.revanced.util.indexOfFirstInstructionOrThrow -import com.android.tools.smali.dexlib2.Opcode +import app.revanced.patches.shared.misc.litho.filter.protobufBufferReferenceMethod val lithoFilterPatch = lithoFilterPatch( - componentCreateInsertionIndex = { - // No supported version clobbers p2 so we can just do our things before the return instruction. - indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT) - }, - getConversionContextToStringMethod = BytecodePatchContext::conversionContextToStringMethod::get, - insertProtobufHook = { - protobufBufferReferenceLegacyMethod.addInstruction( + insertLegacyProtobufHook = { + protobufBufferReferenceMethod.addInstruction( 0, - "invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V", + "invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtobufBuffer(Ljava/nio/ByteBuffer;)V", ) }, ) { diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/context/ConversionContextPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/context/ConversionContextPatch.kt new file mode 100644 index 0000000000..f5752a1a1c --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/context/ConversionContextPatch.kt @@ -0,0 +1,154 @@ +@file:Suppress("SpellCheckingInspection") + +package app.revanced.patches.shared.misc.litho.context + + +import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableClassDef +import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable +import app.revanced.patcher.after +import app.revanced.patcher.allOf +import app.revanced.patcher.classDef +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.field +import app.revanced.patcher.firstClassDef +import app.revanced.patcher.firstMethodDeclaratively +import app.revanced.patcher.instructions +import app.revanced.patcher.invoke +import app.revanced.patcher.parameterTypes +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.returnType +import app.revanced.patches.shared.misc.extension.sharedExtensionPatch +import app.revanced.util.findFieldFromToString +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation +import com.android.tools.smali.dexlib2.immutable.ImmutableMethod + +internal const val EXTENSION_CONTEXT_INTERFACE = + $$"Lapp/revanced/extension/shared/ConversionContext$ContextInterface;" + +internal lateinit var conversionContextClassDef: MutableClassDef + +val conversionContextPatch = bytecodePatch( + description = "Hooks the method to use the conversion context in an extension.", +) { + dependsOn(sharedExtensionPatch()) + + apply { + conversionContextClassDef = conversionContextToStringMethod.classDef + + val identifierField = conversionContextToStringMethod + .findFieldFromToString(IDENTIFIER_PROPERTY) + val stringBuilderField = conversionContextClassDef + .fields.single { field -> field.type == "Ljava/lang/StringBuilder;" } + + // The conversionContext class can be used as is in most versions. + if (conversionContextClassDef.superclass == "Ljava/lang/Object;") { + arrayOf( + identifierField, + stringBuilderField + ).map { + + } + conversionContextClassDef.apply { + // Add interface and helper methods to allow extension code to call obfuscated methods. + interfaces += EXTENSION_CONTEXT_INTERFACE + + arrayOf( + Triple( + "patch_getIdentifier", + "Ljava/lang/String;", + identifierField + ), + Triple( + "patch_getPathBuilder", + "Ljava/lang/StringBuilder;", + stringBuilderField + ) + ).forEach { (interfaceMethodName, interfaceMethodReturnType, classFieldReference) -> + ImmutableMethod( + type, + interfaceMethodName, + listOf(), + interfaceMethodReturnType, + AccessFlags.PUBLIC.value or AccessFlags.FINAL.value, + null, + null, + MutableMethodImplementation(2), + ).toMutable().apply { + addInstructions( + 0, + """ + iget-object v0, p0, $classFieldReference + return-object v0 + """ + ) + }.let(methods::add) + + } + } + } else { + // In some special versions, such as YouTube 20.41, it inherits from an abstract class, + // in which case a helper method is added to the abstract class. + + // Since fields cannot be accessed directly in an abstract class, abstract methods are linked. + val stringBuilderMethodName = conversionContextClassDef.firstMethodDeclaratively { + parameterTypes() + returnType("Ljava/lang/String;") + instructions( + allOf(Opcode.IGET_OBJECT(), field { this == identifierField }), + after(Opcode.RETURN_OBJECT()), + ) + }.name + + val identifierMethodName = conversionContextClassDef.firstMethodDeclaratively { + parameterTypes() + returnType("Ljava/lang/StringBuilder;") + instructions( + allOf(Opcode.IGET_OBJECT(), field { this == stringBuilderField }), + after(Opcode.RETURN_OBJECT()), + ) + }.name + + conversionContextClassDef = firstClassDef(conversionContextClassDef.superclass!!) + + conversionContextClassDef.apply { + // Add interface and helper methods to allow extension code to call obfuscated methods. + interfaces += EXTENSION_CONTEXT_INTERFACE + + arrayOf( + Triple( + "patch_getIdentifier", + "Ljava/lang/String;", + identifierMethodName + ), + Triple( + "patch_getPathBuilder", + "Ljava/lang/StringBuilder;", + stringBuilderMethodName + ) + ).forEach { (interfaceMethodName, interfaceMethodReturnType, classMethodName) -> + ImmutableMethod( + type, + interfaceMethodName, + listOf(), + interfaceMethodReturnType, + AccessFlags.PUBLIC.value or AccessFlags.FINAL.value, + null, + null, + MutableMethodImplementation(2), + ).toMutable().apply { + addInstructions( + 0, + """ + invoke-virtual {p0}, $type->$classMethodName()$interfaceMethodReturnType + move-result-object v0 + return-object v0 + """ + ) + }.let(methods::add) + } + } + } + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/context/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/context/Fingerprints.kt new file mode 100644 index 0000000000..a45f71ec89 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/context/Fingerprints.kt @@ -0,0 +1,25 @@ +package app.revanced.patches.shared.misc.litho.context + +import app.revanced.patcher.gettingFirstImmutableMethodDeclaratively +import app.revanced.patcher.instructions +import app.revanced.patcher.invoke +import app.revanced.patcher.name +import app.revanced.patcher.parameterTypes +import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.returnType + +internal const val IDENTIFIER_PROPERTY = ", identifierProperty=" + + +internal val BytecodePatchContext.conversionContextToStringMethod by gettingFirstImmutableMethodDeclaratively( + ", widthConstraint=", + ", heightConstraint=", + ", templateLoggerFactory=", + ", rootDisposableContainer=", + IDENTIFIER_PROPERTY +) { + name("toString") + parameterTypes() + returnType("Ljava/lang/String;") + instructions("ConversionContext{"(String::contains)) +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt index 9e128382e8..b559e0bac1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt @@ -1,9 +1,13 @@ package app.revanced.patches.shared.misc.litho.filter import app.revanced.patcher.* +import app.revanced.patcher.firstMethodComposite +import app.revanced.patcher.instructions import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.returnType import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.ClassDef import com.android.tools.smali.dexlib2.iface.reference.MethodReference internal val BytecodePatchContext.accessibilityIdMethodMatch by composingFirstMethod { @@ -16,29 +20,59 @@ internal val BytecodePatchContext.accessibilityIdMethodMatch by composingFirstMe ) } -internal fun BytecodePatchContext.getAccessibilityTextMethodMatch(accessibilityIdMethod: MethodReference) = firstMethodComposite { - returnType("V") - custom { - // 'public final synthetic' or 'public final bridge synthetic'. - AccessFlags.SYNTHETIC.isSet(accessFlags) +internal fun BytecodePatchContext.getAccessibilityTextMethodMatch(accessibilityIdMethod: MethodReference) = + firstMethodComposite { + returnType("V") + custom { + // 'public final synthetic' or 'public final bridge synthetic'. + AccessFlags.SYNTHETIC.isSet(accessFlags) + } + instructions( + allOf( + Opcode.INVOKE_INTERFACE(), + method { parameterTypes.isEmpty() && returnType == "Ljava/lang/String;" } + ), + afterAtMost(5, method { this == accessibilityIdMethod }) + ) } + + +context(_: BytecodePatchContext) +internal fun ClassDef.getEmptyComponentMethod() = firstMethodDeclaratively { + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + returnType("L") + parameterTypes("L") +} + +internal val BytecodePatchContext.emptyComponentParentMethod by gettingFirstMethodDeclaratively { + accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR) + parameterTypes() + instructions("EmptyComponent"()) +} + +fun BytecodePatchContext.getComponentCreateMethodMatch(accessibilityIdMethod: MethodReference) = firstMethodComposite { + returnType("L") instructions( - allOf( - Opcode.INVOKE_INTERFACE(), - method { parameterTypes.isEmpty() && returnType == "Ljava/lang/String;" } + Opcode.IF_EQZ(), + afterAtMost( + 5, + allOf(Opcode.CHECK_CAST(), type(accessibilityIdMethod.definingClass)) ), - afterAtMost(5, method { this == accessibilityIdMethod }) + Opcode.RETURN_OBJECT(), + "Element missing correct type extension"(), + "Element missing type"() ) } + internal val BytecodePatchContext.lithoFilterInitMethod by gettingFirstMethodDeclaratively { definingClass("/LithoFilterPatch;") accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR) } -internal val BytecodePatchContext.protobufBufferReferenceMethodMatch by composingFirstMethod { +internal val BytecodePatchContext.protobufBufferEncodeMethod by gettingFirstMethodDeclaratively { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) - returnType("V") - parameterTypes("[B") + returnType("[B") + parameterTypes() var methodDefiningClass = "" custom { @@ -51,32 +85,17 @@ internal val BytecodePatchContext.protobufBufferReferenceMethodMatch by composin Opcode.IGET_OBJECT(), field { definingClass == methodDefiningClass && type == "Lcom/google/android/libraries/elements/adl/UpbMessage;" }, ), - method { definingClass == "Lcom/google/android/libraries/elements/adl/UpbMessage;" && name == "jniDecode" }, + method { definingClass == "Lcom/google/android/libraries/elements/adl/UpbMessage;" && name == "jniEecode" }, ) } -/** - * Matches a method that use the protobuf of our component. - */ -internal val BytecodePatchContext.protobufBufferReferenceLegacyMethod by gettingFirstMethodDeclaratively { +internal val BytecodePatchContext.protobufBufferReferenceMethod by gettingFirstMethodDeclaratively { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) returnType("V") parameterTypes("I", "Ljava/nio/ByteBuffer;") opcodes(Opcode.IPUT, Opcode.INVOKE_VIRTUAL, Opcode.MOVE_RESULT, Opcode.SUB_INT_2ADDR) } -internal val BytecodePatchContext.emptyComponentMethod by gettingFirstImmutableMethodDeclaratively { - accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR) - parameterTypes() - instructions("EmptyComponent"()) - custom { immutableClassDef.methods.filter { AccessFlags.STATIC.isSet(it.accessFlags) }.size == 1 } -} - -internal val BytecodePatchContext.componentCreateMethod by gettingFirstMethod( - "Element missing correct type extension", - "Element missing type", -) - internal val BytecodePatchContext.lithoThreadExecutorMethod by gettingFirstMethodDeclaratively { accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) parameterTypes("I", "I", "I") diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/LithoFilterPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/LithoFilterPatch.kt index 29548aa469..b68ec03a2f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/LithoFilterPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/LithoFilterPatch.kt @@ -2,34 +2,22 @@ package app.revanced.patches.shared.misc.litho.filter -import app.revanced.util.getFreeRegisterProvider import app.revanced.com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable -import app.revanced.patcher.afterAtMost -import app.revanced.patcher.allOf import app.revanced.patcher.classDef -import app.revanced.patcher.custom +import app.revanced.util.getFreeRegisterProvider import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.methodReference import app.revanced.patcher.extensions.removeInstructions -import app.revanced.patcher.extensions.typeReference -import app.revanced.patcher.firstImmutableClassDef -import app.revanced.patcher.firstMethodComposite +import app.revanced.patcher.firstClassDef import app.revanced.patcher.immutableClassDef -import app.revanced.patcher.instructions -import app.revanced.patcher.invoke -import app.revanced.patcher.method import app.revanced.patcher.patch.BytecodePatchBuilder import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patcher.returnType import app.revanced.patches.shared.misc.extension.sharedExtensionPatch +import app.revanced.patches.shared.misc.litho.context.EXTENSION_CONTEXT_INTERFACE +import app.revanced.patches.shared.misc.litho.context.conversionContextPatch import app.revanced.util.addInstructionsAtControlFlowLabel -import app.revanced.util.findFieldFromToString -import app.revanced.util.indexOfFirstInstructionReversedOrThrow -import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.immutable.value.ImmutableBooleanEncodedValue @@ -51,26 +39,20 @@ internal const val EXTENSION_CLASS_DESCRIPTOR = /** * A patch that allows to filter Litho components based on their identifier or path. * - * @param componentCreateInsertionIndex The index to insert the filtering code in the component create method. - * @param insertProtobufHook This method injects a setProtoBuffer call in the protobuf decoding logic. - * @param getConversionContextToStringMethod The getter of the conversion context toString method. + * @param insertLegacyProtobufHook Hook legacy protobuf buffer into the extension to be used for filtering for older versions of the app. * @param getExtractIdentifierFromBuffer Whether to extract the identifier from the protobuf buffer. * @param executeBlock The additional execution block of the patch. * @param block The additional block to build the patch. */ internal fun lithoFilterPatch( - componentCreateInsertionIndex: Method.() -> Int, - insertProtobufHook: BytecodePatchContext.() -> Unit, + insertLegacyProtobufHook: BytecodePatchContext.() -> Unit, executeBlock: BytecodePatchContext.() -> Unit = {}, - getConversionContextToStringMethod: BytecodePatchContext.() -> Method, getExtractIdentifierFromBuffer: () -> Boolean = { false }, block: BytecodePatchBuilder.() -> Unit = {}, ) = bytecodePatch( description = "Hooks the method which parses the bytes into a ComponentContext to filter components.", ) { - dependsOn( - sharedExtensionPatch(), - ) + dependsOn(sharedExtensionPatch(), conversionContextPatch) /** * The following patch inserts a hook into the method that parses the bytes into a ComponentContext. @@ -92,7 +74,7 @@ internal fun lithoFilterPatch( * class SomeOtherClass { * // Called before ComponentContextParser.parseComponent() method. * public void someOtherMethod(ByteBuffer byteBuffer) { - * ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch. + * ExtensionClass.setProtobugBuffer(byteBuffer); // Inserted by this patch. * ... * } * } @@ -134,45 +116,23 @@ internal fun lithoFilterPatch( } } - // Tell the extension whether to extract the identifier from the buffer. - if (getExtractIdentifierFromBuffer()) { - lithoFilterInitMethod.classDef.fields.first { it.name == "EXTRACT_IDENTIFIER_FROM_BUFFER" } - .initialValue = ImmutableBooleanEncodedValue.forBoolean(true).toMutable() - } + // region Pass the buffer into extension. - // Add an interceptor to steal the protobuf of our component. - insertProtobufHook() + insertLegacyProtobufHook() - // Hook the method that parses bytes into a ComponentContext. - // Allow the method to run to completion, and override the - // return value with an empty component if it should be filtered. - // It is important to allow the original code to always run to completion, - // otherwise high memory usage and poor app performance can occur. + // endregion - val conversionContextToStringMethod = getConversionContextToStringMethod() + // region Modify the create component method and + // if the component is filtered then return an empty component. - // Find the identifier/path fields of the conversion context. - val conversionContextIdentifierField = conversionContextToStringMethod - .findFieldFromToString("identifierProperty=") + val builderMethodDescriptor = + emptyComponentParentMethod.immutableClassDef.getEmptyComponentMethod() - val conversionContextPathBuilderField = conversionContextToStringMethod.immutableClassDef - .fields.single { field -> field.type == "Ljava/lang/StringBuilder;" } - - // Find class and methods to create an empty component. - val builderMethodDescriptor = emptyComponentMethod.immutableClassDef.methods.single { - // The only static method in the class. - method -> - AccessFlags.STATIC.isSet(method.accessFlags) - } - - val emptyComponentField = firstImmutableClassDef { - // Only one field that matches. - type == builderMethodDescriptor.returnType - }.fields.single() + val emptyComponentField = firstClassDef(builderMethodDescriptor.returnType).fields.single() // Find the method call that gets the value of 'buttonViewModel.accessibilityId'. val accessibilityIdMethod = accessibilityIdMethodMatch.let { - it.immutableMethod.getInstruction(it[0]).methodReference!! + it.method.getInstruction(it[0]).methodReference!! } // There's a method in the same class that gets the value of 'buttonViewModel.accessibilityText'. @@ -182,35 +142,26 @@ internal fun lithoFilterPatch( it.method.getInstruction(it[0]).methodReference } - componentCreateMethod.apply { - val insertIndex = componentCreateInsertionIndex() + getComponentCreateMethodMatch(accessibilityIdMethod).let { + val insertIndex = it[2] + val buttonViewModelIndex = it[1] + val nullCheckIndex = it[0] - // Directly access the class related with the buttonViewModel from this method. - // This is within 10 lines of insertIndex. - val buttonViewModelIndex = indexOfFirstInstructionReversedOrThrow(insertIndex) { - opcode == Opcode.CHECK_CAST && - typeReference?.type == accessibilityIdMethod.definingClass - } val buttonViewModelRegister = - getInstruction(buttonViewModelIndex).registerA + it.method.getInstruction(buttonViewModelIndex).registerA val accessibilityIdIndex = buttonViewModelIndex + 2 - // This is an index that checks if there is accessibility-related text. - // This is within 10 lines of buttonViewModelIndex. - val nullCheckIndex = indexOfFirstInstructionReversedOrThrow( - buttonViewModelIndex, Opcode.IF_EQZ - ) - - val registerProvider = getFreeRegisterProvider( + val registerProvider = it.method.getFreeRegisterProvider( insertIndex, 3, buttonViewModelRegister ) + val contextRegister = registerProvider.getFreeRegister() + val bufferRegister = registerProvider.getFreeRegister() val freeRegister = registerProvider.getFreeRegister() - val identifierRegister = registerProvider.getFreeRegister() - val pathRegister = registerProvider.getFreeRegister() + // Find a free register to store the accessibilityId and accessibilityText. // This is before the insertion index. - val accessibilityRegisterProvider = getFreeRegisterProvider( + val accessibilityRegisterProvider = it.method.getFreeRegisterProvider( nullCheckIndex, 2, registerProvider.getUsedAndUnAvailableRegisters() @@ -218,23 +169,31 @@ internal fun lithoFilterPatch( val accessibilityIdRegister = accessibilityRegisterProvider.getFreeRegister() val accessibilityTextRegister = accessibilityRegisterProvider.getFreeRegister() - addInstructionsAtControlFlowLabel( + it.method.addInstructionsAtControlFlowLabel( insertIndex, """ - move-object/from16 v$freeRegister, p2 # ConversionContext parameter - - # In YouTube 20.41 the field is the abstract superclass. - # Verify it's the expected subclass just in case. - instance-of v$identifierRegister, v$freeRegister, ${conversionContextToStringMethod.immutableClassDef.type} - if-eqz v$identifierRegister, :unfiltered - - iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField - iget-object v$pathRegister, v$freeRegister, $conversionContextPathBuilderField - invoke-static { v$identifierRegister, v$accessibilityIdRegister, v$accessibilityTextRegister, v$pathRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->isFiltered(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/StringBuilder;)Z + move-object/from16 v$bufferRegister, p3 + + # Verify it's the expected subclass just in case. + instance-of v$freeRegister, v$bufferRegister, ${protobufBufferEncodeMethod.definingClass} + if-eqz v$freeRegister, :empty_buffer + + check-cast v$bufferRegister, ${protobufBufferEncodeMethod.definingClass} + invoke-virtual { v$bufferRegister }, $protobufBufferEncodeMethod + move-result-object v$bufferRegister + goto :hook + + :empty_buffer + const/4 v$freeRegister, 0x0 + new-array v$bufferRegister, v$freeRegister, [B + + :hook + move-object/from16 v$contextRegister, p2 + invoke-static { v$contextRegister, v$bufferRegister, v$accessibilityIdRegister, v$accessibilityTextRegister }, $EXTENSION_CLASS_DESCRIPTOR->isFiltered(${EXTENSION_CONTEXT_INTERFACE}[BLjava/lang/String;Ljava/lang/String;)Z move-result v$freeRegister if-eqz v$freeRegister, :unfiltered - # Return an empty component + # Return an empty component. move-object/from16 v$freeRegister, p1 invoke-static { v$freeRegister }, $builderMethodDescriptor move-result-object v$freeRegister @@ -247,7 +206,7 @@ internal fun lithoFilterPatch( ) // If there is text related to accessibility, get the accessibilityId and accessibilityText. - addInstructions( + it.method.addInstructions( accessibilityIdIndex, """ # Get accessibilityId @@ -262,7 +221,7 @@ internal fun lithoFilterPatch( // If there is no accessibility-related text, // both accessibilityId and accessibilityText use empty values. - addInstructions( + it.method.addInstructions( nullCheckIndex, """ const-string v$accessibilityIdRegister, "" @@ -271,6 +230,13 @@ internal fun lithoFilterPatch( ) } + if (getExtractIdentifierFromBuffer()) { + lithoFilterInitMethod.classDef.fields.first { it.name == "EXTRACT_IDENTIFIER_FROM_BUFFER" } + .initialValue = ImmutableBooleanEncodedValue.forBoolean(true).toMutable() + } + + // endregion + // TODO: Check if needed in music. // Change Litho thread executor to 1 thread to fix layout issue in unpatched YouTube. lithoThreadExecutorMethod.addInstructions( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt index c9239da9ba..dad11bba62 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt @@ -18,6 +18,7 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.contexthook.Endpoint import app.revanced.patches.youtube.misc.contexthook.addOSNameHook import app.revanced.patches.shared.misc.litho.filter.addLithoFilter +import app.revanced.patches.youtube.layout.hide.shelves.hideHorizontalShelvesPatch import app.revanced.patches.youtube.misc.contexthook.hookClientContextPatch import app.revanced.patches.youtube.misc.engagement.addEngagementPanelIdHook import app.revanced.patches.youtube.misc.engagement.engagementPanelHookPatch @@ -50,6 +51,7 @@ private val hideAdsResourcePatch = resourcePatch { addResourcesPatch, hookClientContextPatch, engagementPanelHookPatch, + hideHorizontalShelvesPatch ) apply { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/Fingerprints.kt index c88d7a8bec..b950a21e67 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/Fingerprints.kt @@ -102,6 +102,14 @@ internal val BytecodePatchContext.parseElementFromBufferMethodMatch by composing afterAtMost(1, Opcode.INVOKE_INTERFACE()), after(Opcode.MOVE_RESULT_OBJECT()), "Failed to parse Element"(String::startsWith), + allOf( + Opcode.INVOKE_STATIC(), + method { + returnType.startsWith("L") && parameterTypes.size == 1 + && parameterTypes[0].startsWith("L") + } + ), + afterAtMost(4, Opcode.RETURN_OBJECT()) ) } @@ -215,7 +223,9 @@ internal val BytecodePatchContext.searchBoxTypingStringMethodMatch by composingF parameterTypes("L") instructions( allOf(Opcode.IGET_OBJECT(), field { type == "Ljava/util/Collection;" }), - afterAtMost(5, method { toString() == "Ljava/util/ArrayList;->(Ljava/util/Collection;)V" }), + afterAtMost( + 5, + method { toString() == "Ljava/util/ArrayList;->(Ljava/util/Collection;)V" }), allOf(Opcode.IGET_OBJECT(), field { type == "Ljava/lang/String;" }), afterAtMost(5, method { toString() == "Ljava/lang/String;->isEmpty()Z" }), ResourceType.DIMEN("suggestion_category_divider_height") diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt index ea7fd4c919..63a6275d31 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt @@ -12,6 +12,7 @@ import app.revanced.patches.shared.misc.mapping.ResourceType import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.settings.preference.* import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting +import app.revanced.patches.youtube.layout.hide.shelves.hideHorizontalShelvesPatch import app.revanced.patches.youtube.misc.engagement.engagementPanelHookPatch import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch @@ -19,6 +20,7 @@ import app.revanced.patches.youtube.misc.playservice.is_20_21_or_greater import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch +import app.revanced.util.addInstructionsAtControlFlowLabel import app.revanced.util.findFreeRegister import app.revanced.util.findInstructionIndicesReversedOrThrow import app.revanced.util.getReference @@ -83,6 +85,7 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( versionCheckPatch, engagementPanelHookPatch, resourceMappingPatch, + hideHorizontalShelvesPatch, ), filterClasses = setOf( LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR, @@ -215,7 +218,6 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( ) ), SwitchPreference("revanced_hide_community_button"), - SwitchPreference("revanced_hide_for_you_shelf"), SwitchPreference("revanced_hide_join_button"), SwitchPreference("revanced_hide_links_preview"), SwitchPreference("revanced_hide_members_shelf"), @@ -273,27 +275,20 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( parseElementFromBufferMethodMatch.let { it.method.apply { - val startIndex = it[0] - val insertIndex = startIndex + 1 + val insertIndex = it[0] val byteArrayParameter = "p3" - val conversionContextRegister = - getInstruction(startIndex).registerA - val returnEmptyComponentInstruction = - instructions.last { it.opcode == Opcode.INVOKE_STATIC } + val returnEmptyComponentIndex = it[4] + val returnEmptyComponentInstruction = getInstruction(returnEmptyComponentIndex) + val returnEmptyComponentRegister = (returnEmptyComponentInstruction as FiveRegisterInstruction).registerC - val freeRegister = - findFreeRegister( - insertIndex, - conversionContextRegister, - returnEmptyComponentRegister - ) + val freeRegister = findFreeRegister(insertIndex, returnEmptyComponentRegister) - addInstructionsWithLabels( + addInstructionsAtControlFlowLabel( insertIndex, """ - invoke-static { v$conversionContextRegister, $byteArrayParameter }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z + invoke-static { $byteArrayParameter }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists([B)Z move-result v$freeRegister if-eqz v$freeRegister, :show move-object v$returnEmptyComponentRegister, p1 # Required for 19.47 diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shelves/HideHorizontalShelvesPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shelves/HideHorizontalShelvesPatch.kt new file mode 100644 index 0000000000..0e07c459be --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shelves/HideHorizontalShelvesPatch.kt @@ -0,0 +1,28 @@ +package app.revanced.patches.youtube.layout.hide.shelves + +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.shared.misc.litho.filter.addLithoFilter +import app.revanced.patches.youtube.misc.engagement.engagementPanelHookPatch +import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch +import app.revanced.patches.youtube.misc.litho.observer.layoutReloadObserverPatch +import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch +import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch + +private const val FILTER_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter;" + +internal val hideHorizontalShelvesPatch = bytecodePatch { + dependsOn( + sharedExtensionPatch, + lithoFilterPatch, + playerTypeHookPatch, + navigationBarHookPatch, + engagementPanelHookPatch, + layoutReloadObserverPatch, + ) + + apply { + addLithoFilter(FILTER_CLASS_DESCRIPTOR) + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/Fingerprints.kt index d730022f92..6a63dee5cf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/Fingerprints.kt @@ -7,21 +7,6 @@ import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.ClassDef -internal val BytecodePatchContext.componentContextParserMethod by gettingFirstImmutableMethodDeclaratively { - returnType("L") - instructions( - "Failed to parse Element proto."(), - "Cannot read theme key from model."() - ) -} - -context(_: BytecodePatchContext) -internal fun ClassDef.getTreeNodeResultListMethod() = firstMethodDeclaratively { - accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL) - returnType("Ljava/util/List;") - instructions(allOf(Opcode.INVOKE_STATIC(), method("nCopies"))) -} - internal val BytecodePatchContext.shortsBottomBarContainerMethodMatch by composingFirstMethod { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) returnType("V") diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt index be6e539361..5672db45ab 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt @@ -12,7 +12,6 @@ import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch -import app.revanced.patches.music.shared.conversionContextToStringMethod import app.revanced.patches.shared.misc.mapping.ResourceType import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference @@ -21,6 +20,7 @@ import app.revanced.patches.shared.misc.litho.filter.addLithoFilter import app.revanced.patches.youtube.misc.engagement.engagementPanelHookPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch +import app.revanced.patches.youtube.misc.litho.observer.layoutReloadObserverPatch import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch import app.revanced.patches.youtube.misc.playservice.* import app.revanced.patches.youtube.misc.settings.PreferenceScreen @@ -146,11 +146,11 @@ private const val FILTER_CLASS_DESCRIPTOR = @Suppress("unused") val hideShortsComponentsPatch = bytecodePatch( name = "Hide Shorts components", - description = "Adds options to hide components related to Shorts. " + - "Patching version 20.21.37 or lower can hide more Shorts player button types." + description = "Adds options to hide components related to Shorts." ) { dependsOn( sharedExtensionPatch, + layoutReloadObserverPatch, lithoFilterPatch, hideShortsComponentsResourcePatch, resourceMappingPatch, @@ -204,42 +204,6 @@ val hideShortsComponentsPatch = bytecodePatch( // endregion - // region Hide action buttons. - - if (is_20_22_or_greater) { - componentContextParserMethod.immutableClassDef.getTreeNodeResultListMethod().apply { - val conversionContextPathBuilderField = - conversionContextToStringMethod.immutableClassDef - .fields.single { field -> field.type == "Ljava/lang/StringBuilder;" } - - val insertIndex = implementation!!.instructions.lastIndex - val listRegister = getInstruction(insertIndex).registerA - - val registerProvider = getFreeRegisterProvider(insertIndex, 2) - val freeRegister = registerProvider.getFreeRegister() - val pathRegister = registerProvider.getFreeRegister() - - addInstructionsAtControlFlowLabel( - insertIndex, - """ - move-object/from16 v$freeRegister, p2 - - # In YouTube 20.41 field is the abstract superclass. - # Verify it's the expected subclass just in case. - instance-of v$pathRegister, v$freeRegister, ${conversionContextToStringMethod.immutableClassDef} - if-eqz v$pathRegister, :ignore - - iget-object v$pathRegister, v$freeRegister, $conversionContextPathBuilderField - invoke-static { v$pathRegister, v$listRegister }, ${FILTER_CLASS_DESCRIPTOR}->hideActionButtons(Ljava/lang/StringBuilder;Ljava/util/List;)V - :ignore - nop - """ - ) - } - } - - // endregion - // region Hide the navigation bar. // Hook to get the pivotBar view. diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt index b216bcb201..dc3bb3f103 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt @@ -16,12 +16,14 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPref import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.litho.filter.addLithoFilter import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.patches.shared.misc.litho.context.EXTENSION_CONTEXT_INTERFACE +import app.revanced.patches.shared.misc.litho.context.conversionContextClassDef +import app.revanced.patches.shared.misc.litho.context.conversionContextPatch import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch import app.revanced.patches.youtube.misc.playservice.* import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch -import app.revanced.patches.youtube.shared.conversionContextToStringMethod import app.revanced.patches.youtube.shared.rollingNumberTextViewAnimationUpdateMethodMatch import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId import app.revanced.patches.youtube.video.videoid.hookVideoId @@ -43,7 +45,6 @@ private const val EXTENSION_CLASS_DESCRIPTOR = private const val FILTER_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/litho/ReturnYouTubeDislikeFilter;" -@Suppress("ObjectPropertyName") val returnYouTubeDislikePatch = bytecodePatch( name = "Return YouTube Dislike", description = "Adds an option to show the dislike count of videos with Return YouTube Dislike.", @@ -52,6 +53,7 @@ val returnYouTubeDislikePatch = bytecodePatch( settingsPatch, sharedExtensionPatch, addResourcesPatch, + conversionContextPatch, lithoFilterPatch, videoIdPatch, playerTypeHookPatch, @@ -126,16 +128,9 @@ val returnYouTubeDislikePatch = bytecodePatch( // This hook handles all situations, as it's where the created Spans are stored and later reused. // Find the field name of the conversion context. - val conversionContextClass = conversionContextToStringMethod.immutableClassDef - val textComponentConversionContextField = - textComponentConstructorMethod.immutableClassDef.fields.find { - it.type == conversionContextClass.type || - // 20.41+ uses superclass field type. - it.type == conversionContextClass.superclass - } ?: throw PatchException("Could not find conversion context field") - - val conversionContextPathBuilderField = conversionContextToStringMethod.immutableClassDef - .fields.single { field -> field.type == "Ljava/lang/StringBuilder;" } + val textComponentConversionContextField = textComponentConstructorMethod.immutableClassDef.fields.find { + it.type == conversionContextClassDef.type + } ?: throw PatchException("Could not find conversion context field") // Old pre 20.40 and lower hook. // 21.05 clobbers p0 (this) register. @@ -184,7 +179,7 @@ val returnYouTubeDislikePatch = bytecodePatch( # Copy conversion context. move-object/from16 v$conversionContext, p0 iget-object v$conversionContext, v$conversionContext, $textComponentConversionContextField - invoke-static { v$conversionContext, v$charSequenceRegister }, $EXTENSION_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence; + invoke-static { v$conversionContext, v$charSequenceRegister }, $EXTENSION_CLASS_DESCRIPTOR->onLithoTextLoaded(${EXTENSION_CONTEXT_INTERFACE}Ljava/lang/CharSequence;)Ljava/lang/CharSequence; move-result-object v$charSequenceRegister :ignore @@ -211,16 +206,15 @@ val returnYouTubeDislikePatch = bytecodePatch( val insertIndex = it[1] val charSequenceRegister = getInstruction(insertIndex).registerD - val conversionContextPathRegister = + val conversionContextRegister = findFreeRegister(insertIndex, charSequenceRegister) addInstructions( insertIndex, """ - move-object/from16 v$conversionContextPathRegister, p0 - iget-object v$conversionContextPathRegister, v$conversionContextPathRegister, $conversionContextField - iget-object v$conversionContextPathRegister, v$conversionContextPathRegister, $conversionContextPathBuilderField - invoke-static { v$conversionContextPathRegister, v$charSequenceRegister }, $EXTENSION_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence; + move-object/from16 v$conversionContextRegister, p0 + iget-object v$conversionContextRegister, v$conversionContextRegister, $conversionContextField + invoke-static { v$conversionContextRegister, v$charSequenceRegister }, $EXTENSION_CLASS_DESCRIPTOR->onLithoTextLoaded(${EXTENSION_CONTEXT_INTERFACE}Ljava/lang/CharSequence;)Ljava/lang/CharSequence; move-result-object v$charSequenceRegister """ ) @@ -261,7 +255,7 @@ val returnYouTubeDislikePatch = bytecodePatch( insertIndex, """ iget-object v$freeRegister, v$charSequenceInstanceRegister, $charSequenceFieldReference - invoke-static { v$conversionContextRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->onRollingNumberLoaded(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/String; + invoke-static { v$conversionContextRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->onRollingNumberLoaded(${EXTENSION_CONTEXT_INTERFACE}Ljava/lang/String;)Ljava/lang/String; move-result-object v$freeRegister iput-object v$freeRegister, v$charSequenceInstanceRegister, $charSequenceFieldReference """, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/Fingerprints.kt index 4813ed99ca..ca51c638bd 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/Fingerprints.kt @@ -3,6 +3,9 @@ package app.revanced.patches.youtube.misc.litho.filter import app.revanced.patcher.* import app.revanced.patcher.patch.BytecodePatchContext import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.iface.ClassDef +import com.android.tools.smali.dexlib2.iface.Method +import kotlin.properties.ReadOnlyProperty internal val BytecodePatchContext.lithoComponentNameUpbFeatureFlagMethod by gettingFirstMethodDeclaratively { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt index 772e5b6efc..3fc0a4bcce 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt @@ -3,49 +3,25 @@ package app.revanced.patches.youtube.misc.litho.filter import app.revanced.patcher.extensions.addInstruction -import app.revanced.patcher.patch.BytecodePatchContext -import app.revanced.patches.youtube.shared.conversionContextToStringMethod import app.revanced.patches.shared.misc.litho.filter.EXTENSION_CLASS_DESCRIPTOR import app.revanced.patches.shared.misc.litho.filter.lithoFilterPatch -import app.revanced.patches.shared.misc.litho.filter.protobufBufferReferenceLegacyMethod -import app.revanced.patches.shared.misc.litho.filter.protobufBufferReferenceMethodMatch +import app.revanced.patches.shared.misc.litho.filter.protobufBufferReferenceMethod import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.playservice.* -import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.insertLiteralOverride import app.revanced.util.returnLate -import com.android.tools.smali.dexlib2.Opcode val lithoFilterPatch = lithoFilterPatch( - componentCreateInsertionIndex = { - if (is_19_17_or_greater) { - indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT) - } else { - // 19.16 clobbers p2 so must check at start of the method and not at the return index. - 0 + insertLegacyProtobufHook = { + if (!is_20_22_or_greater) { + // Non-native buffer. + protobufBufferReferenceMethod.addInstruction( + 0, + "invoke-static { p2 }, ${EXTENSION_CLASS_DESCRIPTOR}->setProtobufBuffer(Ljava/nio/ByteBuffer;)V", + ) } }, - insertProtobufHook = { - if (is_20_22_or_greater) { - // Hook method that bridges between UPB buffer native code and FB Litho. - // Method is found in 19.25+, but is forcefully turned off for 20.21 and lower. - protobufBufferReferenceMethodMatch.let { - // Hook the buffer after the call to jniDecode(). - it.method.addInstruction( - it[-1] + 1, - "invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer([B)V", - ) - } - } - - // Legacy non-native buffer. - protobufBufferReferenceLegacyMethod.addInstruction( - 0, - "invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V", - ) - }, - getConversionContextToStringMethod = BytecodePatchContext::conversionContextToStringMethod::get, - getExtractIdentifierFromBuffer = { is_20_21_or_greater }, + getExtractIdentifierFromBuffer = { is_20_22_or_greater }, executeBlock = { // region A/B test of new Litho native code. diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/lazily/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/lazily/Fingerprints.kt new file mode 100644 index 0000000000..3da09f9ebc --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/lazily/Fingerprints.kt @@ -0,0 +1,39 @@ +package app.revanced.patches.youtube.misc.litho.lazily + +import app.revanced.patcher.accessFlags +import app.revanced.patcher.allOf +import app.revanced.patcher.definingClass +import app.revanced.patcher.firstMethodDeclaratively +import app.revanced.patcher.gettingFirstMethodDeclaratively +import app.revanced.patcher.instructions +import app.revanced.patcher.invoke +import app.revanced.patcher.method +import app.revanced.patcher.name +import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.returnType +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.ClassDef + +internal val BytecodePatchContext.componentContextParserMethod by gettingFirstMethodDeclaratively { + returnType("L") + instructions( + "Failed to parse Element proto."(), + "Cannot read theme key from model."() + ) +} + +context(_: BytecodePatchContext) +internal fun ClassDef.getTreeNodeResultListMethod() = firstMethodDeclaratively { + accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL) + returnType("Ljava/util/List;") + instructions( + allOf(Opcode.INVOKE_STATIC(), method { name == "nCopies" }) + ) +} + +internal val BytecodePatchContext.lazilyConvertedElementPatchMethod by gettingFirstMethodDeclaratively { + name("onLazilyConvertedElementLoaded") + definingClass(EXTENSION_CLASS_DESCRIPTOR) + accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC) +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/lazily/LazilyConvertedElementHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/lazily/LazilyConvertedElementHookPatch.kt new file mode 100644 index 0000000000..a08f1bf510 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/lazily/LazilyConvertedElementHookPatch.kt @@ -0,0 +1,54 @@ +package app.revanced.patches.youtube.misc.litho.lazily + +import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod +import app.revanced.patcher.extensions.addInstruction +import app.revanced.patcher.extensions.getInstruction +import app.revanced.patcher.extensions.instructions +import app.revanced.patcher.immutableClassDef +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.patches.shared.misc.litho.context.EXTENSION_CONTEXT_INTERFACE +import app.revanced.patches.shared.misc.litho.context.conversionContextPatch +import app.revanced.util.addInstructionsAtControlFlowLabel +import app.revanced.util.getFreeRegisterProvider +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction + +internal const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/LazilyConvertedElementPatch;" + +private lateinit var lazilyConvertedElementLoadedMethod: MutableMethod + +internal val lazilyConvertedElementHookPatch = bytecodePatch( + description = "Hooks the LazilyConvertedElement tree node lists to the extension." +) { + dependsOn( + sharedExtensionPatch, + conversionContextPatch + ) + + apply { + componentContextParserMethod.immutableClassDef.getTreeNodeResultListMethod().apply { + val insertIndex = instructions.lastIndex + val listRegister = getInstruction(insertIndex).registerA + + val registerProvider = getFreeRegisterProvider(insertIndex, 1) + val freeRegister = registerProvider.getFreeRegister() + + addInstructionsAtControlFlowLabel( + insertIndex, + """ + move-object/from16 v$freeRegister, p2 + invoke-static { v$freeRegister, v$listRegister }, $EXTENSION_CLASS_DESCRIPTOR->onTreeNodeResultLoaded(${EXTENSION_CONTEXT_INTERFACE}Ljava/util/List;)V + """ + ) + } + + lazilyConvertedElementLoadedMethod = lazilyConvertedElementPatchMethod + } +} + +internal fun hookTreeNodeResult(descriptor: String) = + lazilyConvertedElementLoadedMethod.addInstruction( + 0, + "invoke-static { p0, p1 }, $descriptor(Ljava/lang/String;Ljava/util/List;)V" + ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/observer/LayoutReloadObserverPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/observer/LayoutReloadObserverPatch.kt new file mode 100644 index 0000000000..98ed92f7a2 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/observer/LayoutReloadObserverPatch.kt @@ -0,0 +1,24 @@ +@file:Suppress("SpellCheckingInspection") + +package app.revanced.patches.youtube.misc.litho.observer + +import app.revanced.patches.youtube.misc.litho.lazily.hookTreeNodeResult +import app.revanced.patches.youtube.misc.litho.lazily.lazilyConvertedElementHookPatch +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch + +internal const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/LayoutReloadObserverPatch;" + +val layoutReloadObserverPatch = bytecodePatch( + description = "Hooks a method to detect in the extension when the RecyclerView at the bottom of the player is redrawn.", +) { + dependsOn( + sharedExtensionPatch, + lazilyConvertedElementHookPatch + ) + + apply { + hookTreeNodeResult("$EXTENSION_CLASS_DESCRIPTOR->onLazilyConvertedElementLoaded") + } +} diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 6c05ad9dae..aa0a6cd445 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -271,6 +271,7 @@ However, enabling this will also log some user data such as your IP address." @@ -468,10 +469,6 @@ However, enabling this will also log some user data such as your IP address."Hide Community button Community button is hidden Community button is shown - - Hide \'For You\' shelf - For You shelf is hidden - For You shelf is shown Hide Join button Join button is hidden @@ -1856,7 +1853,6 @@ Video playback with AV1 may stutter or drop frames." Library Liked music Playlists - Podcasts Search Subscriptions From 81b24642bed653463ef51419ed27ea877f3dbc73 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 11 Mar 2026 02:10:08 +0100 Subject: [PATCH 10/41] refactor: Rename option --- .../patches/HideRelatedVideoOverlayPatch.java | 2 +- .../patches/litho/LayoutComponentsFilter.java | 2 +- .../extension/youtube/settings/Settings.java | 4 ++-- .../hide/general/HideLayoutComponentsPatch.kt | 2 +- .../HideRelatedVideoOverlayPatch.kt | 2 +- .../main/resources/addresources/values/strings.xml | 14 +++++++------- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/HideRelatedVideoOverlayPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/HideRelatedVideoOverlayPatch.java index 3d891a803b..00c5369b60 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/HideRelatedVideoOverlayPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/HideRelatedVideoOverlayPatch.java @@ -8,6 +8,6 @@ public final class HideRelatedVideoOverlayPatch { * Injection point. */ public static boolean hideRelatedVideoOverlay() { - return Settings.HIDE_RELATED_VIDEOS_OVERLAY.get(); + return Settings.HIDE_PLAYER_RELATED_VIDEOS_OVERLAY.get(); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java index 5a00090230..4bf3bd2054 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java @@ -240,7 +240,7 @@ public final class LayoutComponentsFilter extends Filter { ); final var relatedVideos = new StringFilterGroup( - Settings.HIDE_RELATED_VIDEOS, + Settings.HIDE_QUICK_ACTIONS_RELATED_VIDEOS, "fullscreen_related_videos" ); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 89ba33d934..2dcc27b1dc 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -187,8 +187,8 @@ public class Settings extends YouTubeAndMusicSettings { public static final BooleanSetting HIDE_PLAYER_CONTROL_BUTTONS_BACKGROUND = new BooleanSetting("revanced_hide_player_control_buttons_background", FALSE, true); public static final BooleanSetting HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS = new BooleanSetting("revanced_hide_player_previous_next_buttons", FALSE, true); public static final BooleanSetting HIDE_QUICK_ACTIONS = new BooleanSetting("revanced_hide_quick_actions", FALSE); - public static final BooleanSetting HIDE_RELATED_VIDEOS_OVERLAY = new BooleanSetting("revanced_hide_related_videos_overlay", FALSE, true); - public static final BooleanSetting HIDE_RELATED_VIDEOS = new BooleanSetting("revanced_hide_related_videos", FALSE); + public static final BooleanSetting HIDE_PLAYER_RELATED_VIDEOS_OVERLAY = new BooleanSetting("revanced_hide_player_related_videos_overlay", FALSE, true); + public static final BooleanSetting HIDE_QUICK_ACTIONS_RELATED_VIDEOS = new BooleanSetting("revanced_hide_quick_actions_related_videos", FALSE); public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE); public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE); public static final BooleanSetting HIDE_VIDEO_TITLE = new BooleanSetting("revanced_hide_video_title", FALSE); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt index 63a6275d31..1ee74b571c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt @@ -165,7 +165,7 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( SwitchPreference("revanced_hide_live_chat_replay_button"), SwitchPreference("revanced_hide_medical_panels"), SwitchPreference("revanced_hide_quick_actions"), - SwitchPreference("revanced_hide_related_videos"), + SwitchPreference("revanced_hide_quick_actions_related_videos"), SwitchPreference("revanced_hide_subscribers_community_guidelines"), SwitchPreference("revanced_hide_timed_reactions"), SwitchPreference("revanced_hide_video_title") diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt index 3ebbb974f4..c2b27abc06 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt @@ -43,7 +43,7 @@ val hideRelatedVideoOverlayPatch = bytecodePatch( addResources("youtube", "layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch") PreferenceScreen.PLAYER.addPreferences( - SwitchPreference("revanced_hide_related_videos_overlay"), + SwitchPreference("revanced_hide_player_related_videos_overlay"), ) val relatedEndScreenResultsMethod = diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index aa0a6cd445..9a7d953248 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -359,10 +359,10 @@ However, enabling this will also log some user data such as your IP address."Medical panels are shown Hide quick actions Quick actions in fullscreen are hidden - Quick actions in fullscreen are shown - Hide related videos - Related videos in quick actions are hidden - Related videos in quick actions are shown + Quick actions in fullscreen are shown + Hide quick actions related videos + Related videos in quick actions are hidden + Related videos in quick actions are shown Hide subscribers guidelines Subscribers community guidelines are hidden Subscribers community guidelines are shown @@ -1112,9 +1112,9 @@ To show the Audio track menu, change \'Spoof video streams\' to \'Android No SDK End screen suggested video is shown - Hide related videos overlay - Related videos overlay in fullscreen is hidden - Related videos overlay in fullscreen is shown + Hide related videos overlay + Related videos overlay in fullscreen is hidden + Related videos overlay in fullscreen is shown Hide video timestamp From 636698c96e4a06c8574218d5fa014e5542e348aa Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 11 Mar 2026 02:25:27 +0100 Subject: [PATCH 11/41] feat(YouTube Music): Add support for `8.44.54` --- .../app/revanced/patches/music/ad/video/HideVideoAds.kt | 1 + .../audio/exclusiveaudio/EnableExclusiveAudioPlayback.kt | 1 + .../interaction/permanentrepeat/PermanentRepeatPatch.kt | 1 + .../patches/music/layout/branding/CustomBrandingPatch.kt | 1 + .../music/layout/branding/header/ChangeHeaderPatch.kt | 1 + .../revanced/patches/music/layout/buttons/HideButtons.kt | 1 + .../patches/music/layout/compactheader/HideCategoryBar.kt | 1 + .../music/layout/hide/general/HideLayoutComponentsPatch.kt | 3 ++- .../music/layout/miniplayercolor/ChangeMiniplayerColor.kt | 1 + .../music/layout/navigationbar/NavigationBarPatch.kt | 1 + .../patches/music/layout/premium/HideGetPremiumPatch.kt | 1 + .../patches/music/layout/startpage/ChangeStartPagePatch.kt | 1 + .../app/revanced/patches/music/layout/theme/ThemePatch.kt | 6 ++++-- .../revanced/patches/music/misc/androidauto/Fingerprints.kt | 4 +--- .../misc/androidauto/UnlockAndroidAutoMediaBrowserPatch.kt | 1 + .../patches/music/misc/audio/ForceOriginalAudioPatch.kt | 3 ++- .../misc/backgroundplayback/BackgroundPlaybackPatch.kt | 1 + .../patches/music/misc/debugging/EnableDebuggingPatch.kt | 1 + .../misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt | 3 ++- .../revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt | 4 +++- .../patches/music/misc/privacy/SanitizeSharingLinksPatch.kt | 3 ++- .../patches/music/misc/spoof/SpoofVideoStreamsPatch.kt | 3 ++- 22 files changed, 32 insertions(+), 11 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/ad/video/HideVideoAds.kt b/patches/src/main/kotlin/app/revanced/patches/music/ad/video/HideVideoAds.kt index daa52011fb..816c0db484 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/ad/video/HideVideoAds.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/ad/video/HideVideoAds.kt @@ -29,6 +29,7 @@ val hideMusicVideoAdsPatch = bytecodePatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/EnableExclusiveAudioPlayback.kt b/patches/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/EnableExclusiveAudioPlayback.kt index 96aac10c51..bcf633cc67 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/EnableExclusiveAudioPlayback.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/EnableExclusiveAudioPlayback.kt @@ -21,6 +21,7 @@ val enableExclusiveAudioPlaybackPatch = bytecodePatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/interaction/permanentrepeat/PermanentRepeatPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/interaction/permanentrepeat/PermanentRepeatPatch.kt index 2e227b60b1..17cc829921 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/interaction/permanentrepeat/PermanentRepeatPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/interaction/permanentrepeat/PermanentRepeatPatch.kt @@ -32,6 +32,7 @@ val permanentRepeatPatch = bytecodePatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt index d1b17e2e3f..5b4ab33734 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt @@ -74,6 +74,7 @@ val customBrandingPatch = baseCustomBrandingPatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt index d6c7886266..40c6bb9422 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt @@ -69,6 +69,7 @@ val changeHeaderPatch = changeHeaderPatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ), resourcesAppId = "music", diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/buttons/HideButtons.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/buttons/HideButtons.kt index 17a544f89f..3e0bd9b714 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/buttons/HideButtons.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/buttons/HideButtons.kt @@ -52,6 +52,7 @@ val hideButtonsPatch = bytecodePatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/compactheader/HideCategoryBar.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/compactheader/HideCategoryBar.kt index aa944fee58..adfb03adba 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/compactheader/HideCategoryBar.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/compactheader/HideCategoryBar.kt @@ -35,6 +35,7 @@ val hideCategoryBarPatch = bytecodePatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/hide/general/HideLayoutComponentsPatch.kt index e44f4cc0f5..67c64dbcf9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/hide/general/HideLayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/hide/general/HideLayoutComponentsPatch.kt @@ -15,7 +15,8 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( "7.29.52", "8.10.52", "8.37.56", - "8.40.54" + "8.40.54", + "8.44.54" ) ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColor.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColor.kt index d8f78efe2f..34bb8e04f1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColor.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColor.kt @@ -43,6 +43,7 @@ val changeMiniplayerColorPatch = bytecodePatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/navigationbar/NavigationBarPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/navigationbar/NavigationBarPatch.kt index 5ff1997f61..b6bf36caec 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/navigationbar/NavigationBarPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/navigationbar/NavigationBarPatch.kt @@ -57,6 +57,7 @@ val navigationBarPatch = bytecodePatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/premium/HideGetPremiumPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/premium/HideGetPremiumPatch.kt index 3ec4d3af44..9b2ad25548 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/premium/HideGetPremiumPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/premium/HideGetPremiumPatch.kt @@ -33,6 +33,7 @@ val hideGetMusicPremiumPatch = bytecodePatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/ChangeStartPagePatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/ChangeStartPagePatch.kt index 9703780202..db6eaa806d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/ChangeStartPagePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/startpage/ChangeStartPagePatch.kt @@ -41,6 +41,7 @@ val changeStartPagePatch = bytecodePatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ) ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/ThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/ThemePatch.kt index 39a78c3d30..4ef55d417d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/ThemePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/ThemePatch.kt @@ -7,7 +7,8 @@ import app.revanced.patches.shared.layout.theme.baseThemeResourcePatch import app.revanced.patches.shared.layout.theme.darkThemeBackgroundColorOption import app.revanced.patches.shared.misc.settings.overrideThemeColors -private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/theme/ThemePatch;" +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/music/patches/theme/ThemePatch;" @Suppress("unused") val themePatch = baseThemePatch( @@ -33,7 +34,8 @@ val themePatch = baseThemePatch( "7.29.52", "8.10.52", "8.37.56", - "8.40.54" + "8.40.54", + "8.44.54" ) ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/Fingerprints.kt index ecd2f905a0..7f8f0577e8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/Fingerprints.kt @@ -10,9 +10,7 @@ import app.revanced.patcher.returnType import app.revanced.patcher.strings import com.android.tools.smali.dexlib2.iface.ClassDef -internal val BytecodePatchContext.checkCertificateMethod by gettingFirstMethodDeclaratively( - "X509", -) { +internal val BytecodePatchContext.checkCertificateMethod by gettingFirstMethodDeclaratively { returnType("Z") parameterTypes("L") strings("X509", "isPartnerSHAFingerprint") diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/UnlockAndroidAutoMediaBrowserPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/UnlockAndroidAutoMediaBrowserPatch.kt index e5bb4eed22..d074d39dd6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/UnlockAndroidAutoMediaBrowserPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/UnlockAndroidAutoMediaBrowserPatch.kt @@ -20,6 +20,7 @@ val unlockAndroidAutoMediaBrowserPatch = bytecodePatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/audio/ForceOriginalAudioPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/audio/ForceOriginalAudioPatch.kt index 4fae08ecfb..6913e0c959 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/audio/ForceOriginalAudioPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/audio/ForceOriginalAudioPatch.kt @@ -22,7 +22,8 @@ val forceOriginalAudioPatch = forceOriginalAudioPatch( "7.29.52", "8.10.52", "8.37.56", - "8.40.54" + "8.40.54", + "8.44.54" ) ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt index cbe36736ec..80034583d5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt @@ -21,6 +21,7 @@ val removeBackgroundPlaybackRestrictionsPatch = bytecodePatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/debugging/EnableDebuggingPatch.kt index 77edc32b42..8007a5e6d8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/debugging/EnableDebuggingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/debugging/EnableDebuggingPatch.kt @@ -21,6 +21,7 @@ val enableDebuggingPatch = enableDebuggingPatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ) ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt index f3eb9b08ee..86ae70b8e8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt @@ -15,7 +15,8 @@ val checkWatchHistoryDomainNameResolutionPatch = checkWatchHistoryDomainNameReso "7.29.52", "8.10.52", "8.37.56", - "8.40.54" + "8.40.54", + "8.44.54" ) ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt index d424f1685e..57f5894cd4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt @@ -10,6 +10,7 @@ import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME import app.revanced.patches.music.misc.settings.PreferenceScreen import app.revanced.patches.music.misc.settings.settingsPatch +import app.revanced.patches.music.shared.mainActivityOnCreateMethod import app.revanced.patches.shared.castContextFetchMethod import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch import app.revanced.patches.shared.misc.settings.preference.IntentPreference @@ -23,7 +24,7 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch( toPackageName = REVANCED_MUSIC_PACKAGE_NAME, getPrimeMethod = { primeMethod }, getEarlyReturnMethods = setOf(BytecodePatchContext::castContextFetchMethod::get), - getMainActivityOnCreateMethodToGetInsertIndex = BytecodePatchContext::musicActivityOnCreateMethod::get to { 0 }, + getMainActivityOnCreateMethodToGetInsertIndex = BytecodePatchContext::mainActivityOnCreateMethod::get to { 0 }, extensionPatch = sharedExtensionPatch, gmsCoreSupportResourcePatchFactory = ::gmsCoreSupportResourcePatch, ) { @@ -34,6 +35,7 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch( "8.10.52", "8.37.56", "8.40.54", + "8.44.54" ), ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/privacy/SanitizeSharingLinksPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/privacy/SanitizeSharingLinksPatch.kt index 6aa5863fa2..47e21cdb42 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/privacy/SanitizeSharingLinksPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/privacy/SanitizeSharingLinksPatch.kt @@ -18,7 +18,8 @@ val sanitizeSharingLinksPatch = sanitizeSharingLinksPatch( "7.29.52", "8.10.52", "8.37.56", - "8.40.54" + "8.40.54", + "8.44.54" ) ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt index c4c6764e54..a7c04efff6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt @@ -33,7 +33,8 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch( "7.29.52", "8.10.52", "8.37.56", - "8.40.54" + "8.40.54", + "8.44.54" ) ) }, From af95a580099fa47c334aac9aad4a04c794e61c8d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 11 Mar 2026 02:26:25 +0100 Subject: [PATCH 12/41] feat(YouTube): Add support for `20.44.38` --- .../patches/youtube/ad/general/HideAdsPatch.kt | 3 ++- .../patches/youtube/ad/video/VideoAdsPatch.kt | 3 ++- .../interaction/copyvideourl/CopyVideoURLPatch.kt | 3 ++- .../dialog/RemoveViewerDiscretionDialogPatch.kt | 3 ++- .../AddMoreDoubleTapToSeekLengthOptionsPatch.kt | 3 ++- .../doubletap/DisableChapterSkipDoubleTapPatch.kt | 3 ++- .../youtube/interaction/downloads/DownloadsPatch.kt | 3 ++- .../hapticfeedback/DisableHapticFeedbackPatch.kt | 3 ++- .../youtube/interaction/seekbar/SeekbarPatch.kt | 3 ++- .../interaction/swipecontrols/SwipeControlsPatch.kt | 3 ++- .../youtube/layout/autocaptions/AutoCaptionsPatch.kt | 3 ++- .../youtube/layout/branding/CustomBrandingPatch.kt | 3 ++- .../layout/branding/header/ChangeHeaderPatch.kt | 3 ++- .../buttons/action/HideVideoActionsButtonsPatch.kt | 3 ++- .../music/OverrideOpenInYouTubeMusicButtonPatch.kt | 3 ++- .../layout/buttons/navigation/NavigationBarPatch.kt | 3 ++- .../buttons/overlay/HidePlayerOverlayButtonsPatch.kt | 12 ++++++++---- .../layout/formfactor/ChangeFormFactorPatch.kt | 3 ++- .../hide/autoplaypreview/HideAutoplayPreviewPatch.kt | 3 ++- .../hide/endscreencards/HideEndScreenCardsPatch.kt | 3 ++- .../HideEndScreenSuggestedVideoPatch.kt | 3 ++- .../DisableFullscreenAmbientModePatch.kt | 3 ++- .../layout/hide/general/HideLayoutComponentsPatch.kt | 3 ++- .../layout/hide/infocards/HideInfoCardsPatch.kt | 3 ++- .../player/flyoutmenu/HidePlayerFlyoutMenuPatch.kt | 3 ++- .../hide/player/popup/PlayerPopupPanelsPatch.kt | 3 ++- .../HideRelatedVideoOverlayPatch.kt | 3 ++- .../DisableRollingNumberAnimationPatch.kt | 3 ++- .../layout/hide/shorts/HideShortsComponentsPatch.kt | 6 ++++-- .../signintotvpopup/DisableSignInToTVPopupPatch.kt | 3 ++- .../youtube/layout/hide/time/HideTimestampPatch.kt | 3 ++- .../youtube/layout/miniplayer/MiniplayerPatch.kt | 3 ++- .../layout/player/fullscreen/ExitFullscreenPatch.kt | 5 +++-- .../player/fullscreen/OpenVideosFullscreenPatch.kt | 3 ++- .../overlay/CustomPlayerOverlayOpacityPatch.kt | 3 ++- .../ReturnYouTubeDislikePatch.kt | 10 ++++++---- .../layout/shortsautoplay/ShortsAutoplayPatch.kt | 3 ++- .../shortsplayer/OpenShortsInRegularPlayerPatch.kt | 3 ++- .../youtube/layout/sponsorblock/SponsorBlockPatch.kt | 3 ++- .../layout/spoofappversion/SpoofAppVersionPatch.kt | 3 ++- .../youtube/layout/startpage/ChangeStartPagePatch.kt | 3 ++- .../DisableResumingShortsOnStartupPatch.kt | 3 ++- .../patches/youtube/layout/theme/ThemePatch.kt | 3 ++- .../layout/thumbnails/AlternativeThumbnailsPatch.kt | 3 ++- .../thumbnails/BypassImageRegionRestrictionsPatch.kt | 3 ++- .../youtube/misc/announcements/AnnouncementsPatch.kt | 3 ++- .../misc/audiofocus/PauseOnAudioInterruptPatch.kt | 6 ++++-- .../backgroundplayback/BackgroundPlaybackPatch.kt | 3 ++- .../youtube/misc/debugging/EnableDebuggingPatch.kt | 3 ++- .../dimensions/spoof/SpoofDeviceDimensionsPatch.kt | 3 ++- .../CheckWatchHistoryDomainNameResolutionPatch.kt | 3 ++- .../patches/youtube/misc/gms/GmsCoreSupportPatch.kt | 3 ++- .../youtube/misc/links/BypassURLRedirectsPatch.kt | 3 ++- .../youtube/misc/links/OpenLinksExternallyPatch.kt | 3 ++- .../patches/youtube/misc/loopvideo/LoopVideoPatch.kt | 3 ++- .../misc/privacy/SanitizeSharingLinksPatch.kt | 3 ++- .../youtube/misc/spoof/SpoofVideoStreamsPatch.kt | 3 ++- .../youtube/video/audio/ForceOriginalAudioPatch.kt | 3 ++- .../youtube/video/codecs/DisableVideoCodecsPatch.kt | 3 ++- .../youtube/video/quality/VideoQualityPatch.kt | 3 ++- .../youtube/video/speed/PlaybackSpeedPatch.kt | 3 ++- 61 files changed, 137 insertions(+), 70 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt index dad11bba62..584c8bfe56 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt @@ -96,7 +96,8 @@ val hideAdsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt index f6d244f353..8cf3d57d6e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt @@ -29,7 +29,8 @@ val videoAdsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoURLPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoURLPatch.kt index a93b0a4788..9369cfb03b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoURLPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoURLPatch.kt @@ -58,7 +58,8 @@ val copyVideoURLPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt index a1260cdd0c..04ef01dbdb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt @@ -45,7 +45,8 @@ val removeViewerDiscretionDialogPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt index c2b1686567..19c767c9ed 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt @@ -22,7 +22,8 @@ val addMoreDoubleTapToSeekLengthOptionsPatch = resourcePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ) ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/DisableChapterSkipDoubleTapPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/DisableChapterSkipDoubleTapPatch.kt index fce736f556..27836e47a5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/DisableChapterSkipDoubleTapPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/DisableChapterSkipDoubleTapPatch.kt @@ -37,7 +37,8 @@ val disableDoubleTapActionsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt index 337dea4666..3199426ac5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt @@ -80,7 +80,8 @@ val downloadsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/hapticfeedback/DisableHapticFeedbackPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/hapticfeedback/DisableHapticFeedbackPatch.kt index 9084e593d9..73f198c4a7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/hapticfeedback/DisableHapticFeedbackPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/hapticfeedback/DisableHapticFeedbackPatch.kt @@ -58,7 +58,8 @@ val disableHapticFeedbackPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/SeekbarPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/SeekbarPatch.kt index 934442eeaf..9882ec2716 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/SeekbarPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/SeekbarPatch.kt @@ -23,7 +23,8 @@ val seekbarPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsPatch.kt index 13c90762b4..7e85047d9b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsPatch.kt @@ -109,7 +109,8 @@ val swipeControlsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt index b5f90ac566..da94bc10e0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt @@ -31,7 +31,8 @@ val disableAutoCaptionsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt index 36c6aa2653..2b9a8488b0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt @@ -31,7 +31,8 @@ val customBrandingPatch = baseCustomBrandingPatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt index b7f1e6ad25..e2666e8af7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt @@ -88,7 +88,8 @@ val changeHeaderPatch = changeHeaderPatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ) ), variants = variants, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideVideoActionsButtonsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideVideoActionsButtonsPatch.kt index a869906f37..7a68945b08 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideVideoActionsButtonsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideVideoActionsButtonsPatch.kt @@ -33,7 +33,8 @@ val hideVideoActionButtonsPatch = resourcePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt index 014472b9e7..22eb11d900 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt @@ -69,7 +69,8 @@ val overrideOpenInYouTubeMusicButtonPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationBarPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationBarPatch.kt index 64df88bb37..50c204a593 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationBarPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationBarPatch.kt @@ -60,7 +60,8 @@ val navigationBarPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt index 2af8a1177a..6d2adc6567 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt @@ -43,7 +43,8 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) @@ -146,7 +147,8 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch( titleAnchorMethodMatch.let { it.method.apply { val titleAnchorIndex = it[-1] - val titleAnchorRegister = getInstruction(titleAnchorIndex).registerA + val titleAnchorRegister = + getInstruction(titleAnchorIndex).registerA addInstruction( titleAnchorIndex + 1, @@ -154,7 +156,8 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch( ) val playerCollapseButtonIndex = it[1] - val playerCollapseButtonRegister = getInstruction(playerCollapseButtonIndex).registerA + val playerCollapseButtonRegister = + getInstruction(playerCollapseButtonIndex).registerA addInstruction( playerCollapseButtonIndex + 1, @@ -173,7 +176,8 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch( // so match on move-result-object after findViewById instead of check-cast. val moveResultIndex = it[2] val insertIndex = moveResultIndex + 1 - val insertRegister = getInstruction(moveResultIndex).registerA + val insertRegister = + getInstruction(moveResultIndex).registerA addInstructionsWithLabels( insertIndex, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt index c1ca314270..83e753720c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt @@ -37,7 +37,8 @@ val changeFormFactorPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/autoplaypreview/HideAutoplayPreviewPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/autoplaypreview/HideAutoplayPreviewPatch.kt index 4417a906cc..e28d5f2743 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/autoplaypreview/HideAutoplayPreviewPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/autoplaypreview/HideAutoplayPreviewPatch.kt @@ -39,7 +39,8 @@ val hideAutoplayPreviewPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ) ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndScreenCardsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndScreenCardsPatch.kt index e6f36cd84f..ebf432492c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndScreenCardsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndScreenCardsPatch.kt @@ -68,7 +68,8 @@ val hideEndScreenCardsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt index e667966c5e..1eac8158e9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt @@ -35,7 +35,8 @@ val hideEndScreenSuggestedVideoPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt index 00cf27fe76..b044efe7fe 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt @@ -35,7 +35,8 @@ val disableFullscreenAmbientModePatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt index 1ee74b571c..0cd27f8a64 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt @@ -101,7 +101,8 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ), ) { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt index ad8a320178..51fb2ff7a1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt @@ -52,7 +52,8 @@ val hideInfoCardsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenu/HidePlayerFlyoutMenuPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenu/HidePlayerFlyoutMenuPatch.kt index abbe5f023d..33d41155e4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenu/HidePlayerFlyoutMenuPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenu/HidePlayerFlyoutMenuPatch.kt @@ -30,7 +30,8 @@ val hidePlayerFlyoutMenuItemsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/popup/PlayerPopupPanelsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/popup/PlayerPopupPanelsPatch.kt index 71d0888516..c6d8adef9d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/popup/PlayerPopupPanelsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/popup/PlayerPopupPanelsPatch.kt @@ -31,7 +31,8 @@ val disablePlayerPopupPanelsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt index c2b27abc06..297c004d15 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt @@ -35,7 +35,8 @@ val hideRelatedVideoOverlayPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt index 7700ff0a57..48d418d23d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt @@ -35,7 +35,8 @@ val disableRollingNumberAnimationsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt index 5672db45ab..df4aaa702c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt @@ -166,7 +166,8 @@ val hideShortsComponentsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) @@ -188,7 +189,8 @@ val hideShortsComponentsPatch = bytecodePatch( methodReference?.name == "getDimensionPixelSize" } + 1 - val sizeRegister = method.getInstruction(targetIndex).registerA + val sizeRegister = + method.getInstruction(targetIndex).registerA return@forEachInstructionAsSequence targetIndex to sizeRegister }) { method, (targetIndex, sizeRegister) -> diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/signintotvpopup/DisableSignInToTVPopupPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/signintotvpopup/DisableSignInToTVPopupPatch.kt index 6195af9f4e..69ff48c876 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/signintotvpopup/DisableSignInToTVPopupPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/signintotvpopup/DisableSignInToTVPopupPatch.kt @@ -32,7 +32,8 @@ val disableSignInToTVPopupPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt index b9ae184e0b..75febb8e91 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt @@ -30,7 +30,8 @@ val hideTimestampPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt index 0ef561d714..2e29124ba5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt @@ -82,7 +82,8 @@ val miniplayerPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt index 236a6c05fe..27345b989c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt @@ -27,7 +27,8 @@ val exitFullscreenPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) @@ -61,7 +62,7 @@ val exitFullscreenPatch = bytecodePatch( insertIndex, "invoke-static/range { p1 .. p1 }, " + "$EXTENSION_CLASS_DESCRIPTOR->endOfVideoReached(Ljava/lang/Enum;)V", - ) + ) } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenPatch.kt index 79f044a6e7..dbc4094354 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenPatch.kt @@ -27,7 +27,8 @@ val openVideosFullscreenPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt index d4dd999ef5..dafe7b87ab 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt @@ -33,7 +33,8 @@ val customPlayerOverlayOpacityPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt index dc3bb3f103..3624a07c33 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt @@ -67,7 +67,8 @@ val returnYouTubeDislikePatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) @@ -128,9 +129,10 @@ val returnYouTubeDislikePatch = bytecodePatch( // This hook handles all situations, as it's where the created Spans are stored and later reused. // Find the field name of the conversion context. - val textComponentConversionContextField = textComponentConstructorMethod.immutableClassDef.fields.find { - it.type == conversionContextClassDef.type - } ?: throw PatchException("Could not find conversion context field") + val textComponentConversionContextField = + textComponentConstructorMethod.immutableClassDef.fields.find { + it.type == conversionContextClassDef.type + } ?: throw PatchException("Could not find conversion context field") // Old pre 20.40 and lower hook. // 21.05 clobbers p0 (this) register. diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt index 63fa21a216..395dcc4674 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt @@ -54,7 +54,8 @@ val shortsAutoplayPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt index 518902cab2..febc7dce74 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt @@ -57,7 +57,8 @@ val openShortsInRegularPlayerPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt index 41c049d821..2ced4b5037 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt @@ -134,7 +134,8 @@ val sponsorBlockPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt index 74e4d65c5d..991001d26b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt @@ -42,7 +42,8 @@ val spoofAppVersionPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt index 599a6e36ea..95d212df19 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt @@ -36,7 +36,8 @@ val changeStartPagePatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch.kt index c727aa8267..c7ca37a67f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch.kt @@ -42,7 +42,8 @@ val disableResumingShortsOnStartupPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" // This patch is obsolete with 21.03 because YT seems to have // removed resuming Shorts functionality. // TODO: Before adding 21.03+, merge this patch into `Hide Shorts component` diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt index 87a40c54dc..91479cb8e1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt @@ -195,7 +195,8 @@ val themePatch = baseThemePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt index 6051aa2e79..91346f97a0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt @@ -39,7 +39,8 @@ val alternativeThumbnailsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictionsPatch.kt index e785fcf199..540639de71 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictionsPatch.kt @@ -33,7 +33,8 @@ val bypassImageRegionRestrictionsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt index 11b2729a0e..5ca10fedfd 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt @@ -29,7 +29,8 @@ val announcementsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt index 53cc5beca7..94ce25eed2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt @@ -12,7 +12,8 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch -private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/PauseOnAudioInterruptPatch;" +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/PauseOnAudioInterruptPatch;" val pauseOnAudioInterruptPatch = bytecodePatch( name = "Pause on audio interrupt", @@ -31,7 +32,8 @@ val pauseOnAudioInterruptPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt index 6f4944eaa4..d6afb4b106 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt @@ -50,7 +50,8 @@ val removeBackgroundPlaybackRestrictionsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt index 3ac32bd420..f7d68184e5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt @@ -24,7 +24,8 @@ val enableDebuggingPatch = enableDebuggingPatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ) ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt index 4b1381b280..82b6eef219 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt @@ -32,7 +32,8 @@ val spoofDeviceDimensionsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt index 28125a0809..f22f18eb7b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt @@ -18,7 +18,8 @@ val checkWatchHistoryDomainNameResolutionPatch = checkWatchHistoryDomainNameReso "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt index edeeb237f9..10f20cc388 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt @@ -41,7 +41,8 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt index 7978e8e8b4..0baab7ecd7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt @@ -35,7 +35,8 @@ val bypassURLRedirectsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt index 0128184a68..fb753a1147 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt @@ -47,7 +47,8 @@ val openLinksExternallyPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt index e30ae91997..cef3477289 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt @@ -36,7 +36,8 @@ val loopVideoPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/SanitizeSharingLinksPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/SanitizeSharingLinksPatch.kt index a8c9549578..393f396506 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/SanitizeSharingLinksPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/SanitizeSharingLinksPatch.kt @@ -20,7 +20,8 @@ val sanitizeSharingLinksPatch = sanitizeSharingLinksPatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ) ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt index 980e64888e..9252101254 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt @@ -38,7 +38,8 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt index 2289a65dbd..33895724fc 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt @@ -25,7 +25,8 @@ val forceOriginalAudioPatch = forceOriginalAudioPatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/codecs/DisableVideoCodecsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/codecs/DisableVideoCodecsPatch.kt index 7e3360dde0..b028b393f2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/codecs/DisableVideoCodecsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/codecs/DisableVideoCodecsPatch.kt @@ -61,7 +61,8 @@ val disableVideoCodecsPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt index eac5428f97..72e94e3a61 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt @@ -30,7 +30,8 @@ val videoQualityPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt index 16b54823e2..c0faf98419 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt @@ -33,7 +33,8 @@ val playbackSpeedPatch = bytecodePatch( "20.26.46", "20.31.42", "20.37.48", - "20.40.45" + "20.40.45", + "20.44.38" ) ) From 20079d267a43ab48e153024e5ecb8b27b4d08207 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 11 Mar 2026 02:32:47 +0100 Subject: [PATCH 13/41] fix(YouTube - Hide player flyout menu items): Do not hide entire flyout menu for eperimental app targets Co-Authored-By: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> --- .../patches/litho/LithoFilterPatch.java | 39 +++++++++++++++---- .../litho/PlayerFlyoutMenuItemsFilter.java | 3 ++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java index 1af697236c..cf6c2c13f8 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java @@ -63,23 +63,48 @@ public final class LithoFilterPatch { final int minimumAscii = 32; // 32 = space character final int maximumAscii = 126; // 127 = delete character final int minimumAsciiStringLength = 4; // Minimum length of an ASCII string to include. + // Logger ignores text past 4096 bytes on each line. Must wrap lines otherwise logging is clipped. + final int preferredLineLength = 3000; // Preferred length before wrapping on next substring. + final int maxLineLength = 3300; // Hard limit to line wrap in the middle of substring. String delimitingCharacter = "❙"; // Non ascii character, to allow easier log filtering. final int length = buffer.length; + final int lastIndex = length - 1; int start = 0; - int end = 0; - while (end < length) { - int value = buffer[end]; - if (value < minimumAscii || value > maximumAscii || end == length - 1) { - if (end - start >= minimumAsciiStringLength) { - for (int i = start; i < end; i++) { + int currentLineLength = 0; + + for (int end = 0; end < length; end++) { + final int value = buffer[end]; + final boolean isAscii = (value >= minimumAscii && value <= maximumAscii); + final boolean atEnd = (end == lastIndex); + + if (!isAscii || atEnd) { + int wordEnd = end + ((atEnd && isAscii) ? 1 : 0); + + if (wordEnd - start >= minimumAsciiStringLength) { + for (int i = start; i < wordEnd; i++) { builder.append((char) buffer[i]); + currentLineLength++; + + // Hard line limit. Hard wrap the current substring to next logger line. + if (currentLineLength >= maxLineLength) { + builder.append('\n'); + currentLineLength = 0; + } } + + // Wrap after substring if over preferred limit. + if (currentLineLength >= preferredLineLength) { + builder.append('\n'); + currentLineLength = 0; + } + builder.append(delimitingCharacter); + currentLineLength++; } + start = end + 1; } - end++; } } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlayerFlyoutMenuItemsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlayerFlyoutMenuItemsFilter.java index c1edd9a0dc..e7d136a16e 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlayerFlyoutMenuItemsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlayerFlyoutMenuItemsFilter.java @@ -127,6 +127,9 @@ public final class PlayerFlyoutMenuItemsFilter extends Filter { return false; } + // 21.x+ fix. + if (path.contains("bottom_sheet_list_option.e")) return false; + return flyoutFilterGroupList.check(buffer).isFiltered(); } } From 7f52ec2ceac48380b4ef18524c021dc0f87e4c2b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 11 Mar 2026 10:44:18 +0100 Subject: [PATCH 14/41] feat(YouTube): Add experimental support for `21.20.493` Co-Authored-By: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> --- .../youtube/patches/ShortsAutoplayPatch.java | 15 +++++++++-- .../youtube/patches/VersionCheckPatch.java | 2 ++ .../shortsautoplay/ShortsAutoplayPatch.kt | 25 ++++++++++++++----- .../misc/playservice/VersionCheckPatch.kt | 3 +++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ShortsAutoplayPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ShortsAutoplayPatch.java index 905ca6dfa0..b73bff3b88 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ShortsAutoplayPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ShortsAutoplayPatch.java @@ -1,5 +1,7 @@ package app.revanced.extension.youtube.patches; +import static app.revanced.extension.youtube.patches.VersionCheckPatch.IS_21_10_OR_GREATER; + import android.app.Activity; import java.lang.ref.WeakReference; @@ -24,7 +26,12 @@ public class ShortsAutoplayPatch { /** * Pause playback after 1 play. */ - END_SCREEN; + END_SCREEN, + /** + * Play once, then advanced to the next Short. + * Only found in 21.10+ + */ + AUTO_ADVANCE; static void setYTEnumValue(Enum ytBehavior) { for (ShortsLoopBehavior rvBehavior : values()) { @@ -93,8 +100,12 @@ public class ShortsAutoplayPatch { autoplay = Settings.SHORTS_AUTOPLAY.get(); } - Enum overrideBehavior = (autoplay + ShortsLoopBehavior autoPlayBehavior = IS_21_10_OR_GREATER ? ShortsLoopBehavior.SINGLE_PLAY + : ShortsLoopBehavior.AUTO_ADVANCE; + + Enum overrideBehavior = (autoplay + ? autoPlayBehavior : ShortsLoopBehavior.REPEAT).ytEnumValue; if (overrideBehavior != null) { diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VersionCheckPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VersionCheckPatch.java index 77d85737b5..9c2986a2f1 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VersionCheckPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VersionCheckPatch.java @@ -27,4 +27,6 @@ public class VersionCheckPatch { public static final boolean IS_20_31_OR_GREATER = isVersionOrGreater("20.31.00"); public static final boolean IS_20_37_OR_GREATER = isVersionOrGreater("20.37.00"); + + public static final boolean IS_21_10_OR_GREATER = isVersionOrGreater("21.10.00"); } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt index 395dcc4674..da6d26dd6e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt @@ -15,6 +15,8 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater import app.revanced.patches.youtube.misc.playservice.is_20_09_or_greater +import app.revanced.patcher.method +import app.revanced.patches.youtube.misc.playservice.is_21_10_or_greater import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch @@ -99,12 +101,23 @@ val shortsAutoplayPatch = bytecodePatch( reelPlaybackRepeatMethod.apply { // The behavior enums are looked up from an ordinal value to an enum type. - findInstructionIndicesReversedOrThrow { - val reference = getReference() - reference?.definingClass == reelEnumClass && - reference.parameterTypes.firstOrNull() == "I" && - reference.returnType == reelEnumClass - }.forEach { index -> + + val match = if (is_21_10_or_greater) { + method { + returnType == reelEnumClass && + parameterTypes.size == 1 && + parameterTypes[0].startsWith("L") + } + } else { + method { + definingClass == "reelEnumClass" && + returnType == reelEnumClass && + parameterTypes.size == 1 && + parameterTypes[0].startsWith("L") + } + } + + findInstructionIndicesReversedOrThrow { match(0, 0) {} }.forEach { index -> val register = getInstruction(index + 1).registerA addInstructions( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt index f2e6b41ee3..0903a3a718 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt @@ -127,6 +127,8 @@ var is_21_07_or_greater : Boolean by Delegates.notNull() private set var is_21_08_or_greater : Boolean by Delegates.notNull() private set +var is_21_10_or_greater : Boolean by Delegates.notNull() + private set val versionCheckPatch = resourcePatch( description = "Uses the Play Store service version to find the major/minor version of the YouTube target app.", @@ -184,5 +186,6 @@ val versionCheckPatch = resourcePatch( is_21_06_or_greater = 260705000 <= playStoreServicesVersion is_21_07_or_greater = 260805000 <= playStoreServicesVersion is_21_08_or_greater = 260905000 <= playStoreServicesVersion + is_21_10_or_greater = 261080000 <= playStoreServicesVersion } } From e7196e54b06d52ae8fd3f759f570c31c457dc04d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 11 Mar 2026 11:02:56 +0100 Subject: [PATCH 15/41] feat(YouTube Music): Add `Forcibly enable miniplayer` patch Co-Authored-By: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../ForciblyEnableMiniplayerPatch.java | 13 ++++ .../extension/music/settings/Settings.java | 1 + .../ChangeMiniplayerColor.kt | 2 +- .../Fingerprints.kt | 9 ++- .../ForciblyEnableMiniplayerPatch.kt | 68 +++++++++++++++++++ .../resources/addresources/values/strings.xml | 7 +- 6 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 extensions/music/src/main/java/app/revanced/extension/music/patches/ForciblyEnableMiniplayerPatch.java rename patches/src/main/kotlin/app/revanced/patches/music/layout/{miniplayercolor => miniplayer}/ChangeMiniplayerColor.kt (98%) rename patches/src/main/kotlin/app/revanced/patches/music/layout/{miniplayercolor => miniplayer}/Fingerprints.kt (77%) create mode 100644 patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/ForciblyEnableMiniplayerPatch.kt diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/ForciblyEnableMiniplayerPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/ForciblyEnableMiniplayerPatch.java new file mode 100644 index 0000000000..8b6b9c10a1 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/ForciblyEnableMiniplayerPatch.java @@ -0,0 +1,13 @@ +package app.revanced.extension.music.patches; + +import app.revanced.extension.music.settings.Settings; + +@SuppressWarnings("unused") +public class ForciblyEnableMiniplayerPatch { + /** + * Injection point + */ + public static boolean enableForcedMiniplayerPatch(boolean original) { + return Settings.FORCIBLY_ENABLE_MINIPLAYER.get() || original; + } +} \ No newline at end of file diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java index 2e057681d6..395f4e7c21 100644 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java @@ -35,6 +35,7 @@ public class Settings extends YouTubeAndMusicSettings { // Player public static final BooleanSetting CHANGE_MINIPLAYER_COLOR = new BooleanSetting("revanced_music_change_miniplayer_color", FALSE, true); + public static final BooleanSetting FORCIBLY_ENABLE_MINIPLAYER = new BooleanSetting("revanced_music_forcibly_enable_miniplayer", FALSE, true); public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("revanced_music_play_permanent_repeat", FALSE, true); // Miscellaneous diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColor.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/ChangeMiniplayerColor.kt similarity index 98% rename from patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColor.kt rename to patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/ChangeMiniplayerColor.kt index 34bb8e04f1..608a33e319 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColor.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/ChangeMiniplayerColor.kt @@ -1,6 +1,6 @@ @file:Suppress("SpellCheckingInspection") -package app.revanced.patches.music.layout.miniplayercolor +package app.revanced.patches.music.layout.miniplayer import app.revanced.patcher.accessFlags import app.revanced.patcher.extensions.getInstruction diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayercolor/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/Fingerprints.kt similarity index 77% rename from patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayercolor/Fingerprints.kt rename to patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/Fingerprints.kt index cd621ba5ed..552dde0c3d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayercolor/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/Fingerprints.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.music.layout.miniplayercolor +package app.revanced.patches.music.layout.miniplayer import app.revanced.patcher.* import app.revanced.patcher.patch.BytecodePatchContext @@ -28,3 +28,10 @@ internal val ClassDef.switchToggleColorMethodMatch by ClassDefComposing.composin Opcode.IGET, ) } + +internal val BytecodePatchContext.minimizedPlayerMethod by gettingFirstMethodDeclaratively { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returnType("V") + parameterTypes("L", "L") + instructions("w_st"()) +} diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/ForciblyEnableMiniplayerPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/ForciblyEnableMiniplayerPatch.kt new file mode 100644 index 0000000000..4fa3fa0677 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/ForciblyEnableMiniplayerPatch.kt @@ -0,0 +1,68 @@ +@file:Suppress("SpellCheckingInspection") + +package app.revanced.patches.music.layout.miniplayer + +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.getInstruction +import app.revanced.patcher.extensions.methodReference +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.music.misc.settings.settingsPatch +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.util.indexOfFirstInstructionOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction + +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/music/patches/ForciblyEnableMiniplayerPatch;" + +@Suppress("unused") +val forciblyEnableMiniplayerPatch = bytecodePatch( + name = "Forcibly enable miniplayer", + description = "Adds an option to forcibly enable the miniplayer when switching between music videos, podcasts, or songs." +) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + ) + + compatibleWith( + "com.google.android.apps.youtube.music"( + "7.29.52", + "8.10.52", + "8.37.56", + "8.40.54", + "8.44.54" + ), + ) + + apply { + addResources("music", "layout.miniplayer.forciblyEnableMiniplayer") + + PreferenceScreen.PLAYER.addPreferences( + SwitchPreference("revanced_music_forcibly_enable_miniplayer") + ) + + minimizedPlayerMethod.apply { + val invokeIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && methodReference?.name == "booleanValue" + } + + val moveResultIndex = invokeIndex + 1 + val moveResultInstr = getInstruction(moveResultIndex) + val targetRegister = moveResultInstr.registerA + + addInstructions( + moveResultIndex + 1, + """ + invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->forciblyEnableMiniplayerPatch(Z)Z + move-result v$targetRegister + """ + ) + } + } +} \ No newline at end of file diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 9a7d953248..6bf4dba82d 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1862,11 +1862,16 @@ Video playback with AV1 may stutter or drop frames." Category bar is hidden Category bar is shown - + Change miniplayer color Miniplayer color matches fullscreen player Miniplayer uses default color + + Forcibly enable miniplayer + Miniplayer stays minimized when playback changes + Miniplayer expands to fullscreen when playback changes + Navigation bar Hide or change navigation bar buttons From 23676746a6a0f47ce4a9cf8c09e1c8bc2bf29062 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 11 Mar 2026 11:19:47 +0100 Subject: [PATCH 16/41] fix(YouTube - Hide layout components): Replace "Hide AI comments summary" with "Sanitize category bar" Co-Authored-By: inotia00 <108592928+inotia00@users.noreply.github.com> --- .../youtube/patches/litho/CommentsFilter.java | 50 +++++++++++-------- .../extension/youtube/settings/Settings.java | 1 + .../hide/general/HideLayoutComponentsPatch.kt | 8 ++- .../resources/addresources/values/strings.xml | 6 +-- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/CommentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/CommentsFilter.java index 70330c843f..48c5c284fe 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/CommentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/CommentsFilter.java @@ -1,5 +1,10 @@ package app.revanced.extension.youtube.patches.litho; +import androidx.annotation.NonNull; + +import java.util.List; + +import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.FilterGroup.*; import app.revanced.extension.youtube.settings.Settings; @@ -8,11 +13,10 @@ import app.revanced.extension.youtube.shared.PlayerType; @SuppressWarnings("unused") public final class CommentsFilter extends Filter { + private static final String CHIP_BAR_PATH_PREFIX = "chip_bar.e"; private static final String COMMENT_COMPOSER_PATH = "comment_composer.e"; private static final String VIDEO_LOCKUP_WITH_ATTACHMENT_PATH = "video_lockup_with_attachment.e"; - private final StringFilterGroup chipBar; - private final ByteArrayFilterGroup aiCommentsSummary; private final StringFilterGroup comments; private final StringFilterGroup emojiAndTimestampButtons; @@ -22,16 +26,6 @@ public final class CommentsFilter extends Filter { "live_chat_summary_banner.e" ); - chipBar = new StringFilterGroup( - Settings.HIDE_COMMENTS_AI_SUMMARY, - "chip_bar.e" - ); - - aiCommentsSummary = new ByteArrayFilterGroup( - null, - "yt_fill_spark_" - ); - var channelGuidelines = new StringFilterGroup( Settings.HIDE_COMMENTS_CHANNEL_GUIDELINES, "channel_guidelines_entry_banner" @@ -79,7 +73,6 @@ public final class CommentsFilter extends Filter { addPathCallbacks( channelGuidelines, chatSummary, - chipBar, commentsByMembers, comments, communityGuidelines, @@ -94,23 +87,36 @@ public final class CommentsFilter extends Filter { @Override public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { - if (matchedGroup == chipBar) { - // Playlist sort button uses same components and must only filter if the player is opened. - return PlayerType.getCurrent().isMaximizedOrFullscreen() - && aiCommentsSummary.check(buffer).isFiltered(); - } - if (matchedGroup == comments) { if (path.startsWith(VIDEO_LOCKUP_WITH_ATTACHMENT_PATH)) { return Settings.HIDE_COMMENTS_SECTION_IN_HOME_FEED.get(); } return Settings.HIDE_COMMENTS_SECTION.get(); - } - - if (matchedGroup == emojiAndTimestampButtons) { + } else if (matchedGroup == emojiAndTimestampButtons) { return path.startsWith(COMMENT_COMPOSER_PATH); } return true; } + + /** + * Injection point. + */ + public static void sanitizeCommentsCategoryBar(@NonNull String identifier, + @NonNull List treeNodeResultList) { + try { + if (Settings.SANITIZE_COMMENTS_CATEGORY_BAR.get() + && identifier.startsWith(CHIP_BAR_PATH_PREFIX) + // Playlist sort button uses same components and must only filter if the player is opened. + && PlayerType.getCurrent().isMaximizedOrFullscreen() + ) { + int treeNodeResultListSize = treeNodeResultList.size(); + if (treeNodeResultListSize > 2) { + treeNodeResultList.subList(1, treeNodeResultListSize - 1).clear(); + } + } + } catch (Exception ex) { + Logger.printException(() -> "Failed to sanitize comment category bar", ex); + } + } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 2dcc27b1dc..a85a13e4e3 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -227,6 +227,7 @@ public class Settings extends YouTubeAndMusicSettings { public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE); public static final BooleanSetting HIDE_COMMENTS_SECTION_IN_HOME_FEED = new BooleanSetting("revanced_hide_comments_section_in_home_feed", FALSE, parentNot(HIDE_COMMENTS_SECTION)); public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE); + public static final BooleanSetting SANITIZE_COMMENTS_CATEGORY_BAR = new BooleanSetting("revanced_sanitize_comments_category_bar", FALSE); // Description public static final BooleanSetting HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION = new BooleanSetting("revanced_hide_ai_generated_video_summary_section", FALSE); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt index 0cd27f8a64..0490544503 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt @@ -15,6 +15,8 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPref import app.revanced.patches.youtube.layout.hide.shelves.hideHorizontalShelvesPatch import app.revanced.patches.youtube.misc.engagement.engagementPanelHookPatch import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch +import app.revanced.patches.youtube.misc.litho.lazily.hookTreeNodeResult +import app.revanced.patches.youtube.misc.litho.lazily.lazilyConvertedElementHookPatch import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch import app.revanced.patches.youtube.misc.playservice.is_20_21_or_greater import app.revanced.patches.youtube.misc.playservice.versionCheckPatch @@ -86,6 +88,7 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( engagementPanelHookPatch, resourceMappingPatch, hideHorizontalShelvesPatch, + lazilyConvertedElementHookPatch ), filterClasses = setOf( LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR, @@ -154,7 +157,8 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( SwitchPreference("revanced_hide_comments_emoji_and_timestamp_buttons"), SwitchPreference("revanced_hide_comments_preview_comment"), SwitchPreference("revanced_hide_comments_thanks_button"), - ), + SwitchPreference("revanced_sanitize_comments_category_bar"), + ), sorting = Sorting.UNSORTED, ), SwitchPreference("revanced_hide_channel_bar"), @@ -272,6 +276,8 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( ) } + hookTreeNodeResult("$COMMENTS_FILTER_CLASS_NAME->sanitizeCommentsCategoryBar") + // region Hide mix playlists parseElementFromBufferMethodMatch.let { diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 6bf4dba82d..09034b534a 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -493,9 +493,6 @@ However, enabling this will also log some user data such as your IP address."Hide AI chat summary AI chat summary is hidden AI chat summary is shown - Hide AI comments summary - AI comments summary is hidden - AI comments summary is shown Hide channel guidelines Channel guidelines are hidden Channel guidelines are shown @@ -520,6 +517,9 @@ However, enabling this will also log some user data such as your IP address."Hide Thanks button Thanks button is hidden Thanks button is shown + Sanitize category bar + Category buttons except \'Top\' and \'Newest\' are hidden in the comment category bar + Category buttons are shown in the comment category bar Hide view count View count is hidden in feed and search results View count is shown in feed and search results From 15096257ca84803cd03a330ae317d450b579cc94 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 11 Mar 2026 11:24:05 +0100 Subject: [PATCH 17/41] fix(YouTube - Hide Shorts components): Hide Shorts shelf hides autoplaying videos in the feed Co-Authored-By: inotia00 <108592928+inotia00@users.noreply.github.com> --- .../youtube/patches/litho/ShortsFilter.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java index ecbe75a259..d4fd98f96d 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java @@ -26,10 +26,6 @@ import app.revanced.extension.youtube.shared.PlayerType; public final class ShortsFilter extends Filter { private static final boolean HIDE_SHORTS_NAVIGATION_BAR = Settings.HIDE_SHORTS_NAVIGATION_BAR.get(); private static final String COMPONENT_TYPE = "ComponentType"; - private static final String[] REEL_ACTION_BAR_PATHS = { - "reel_action_bar.", // Regular Shorts. - "reels_player_overlay_layout." // Shorts ads. - }; private final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.e"; /** @@ -498,6 +494,15 @@ public final class ShortsFilter extends Filter { if (!hideHome && !hideSubscriptions && !hideSearch && !hideVideoDescription && !hideHistory) { return false; } + // The Litho path of the feed video is 'video_lockup_with_attachment.e'. + // It appears shortsCompactFeedVideoBuffer is used after 20 seconds during autoplay in the feed in YouTube 20.44.38. + // If the Shorts shelf is hidden on the Home feed, the video in the feed will be hidden after 20 seconds have passed since autoplay began in the feed. + // + // When a video is autoplaying in the feed, no new components are drawn on the screen. + // Therefore, filtering is skipped when the current PlayerType is INLINE_MINIMAL. + if (PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL) { + return false; + } if (hideHome && hideSubscriptions && hideSearch && hideVideoDescription && hideHistory) { return true; } From 7fe7ce437c3ee2bb91b189c207048cf45f47b9bb Mon Sep 17 00:00:00 2001 From: drobotk Date: Fri, 13 Mar 2026 12:03:29 +0100 Subject: [PATCH 18/41] fix build and crashes --- .../youtube/patches/litho/HorizontalShelvesFilter.java | 2 +- .../patches/music/layout/branding/CustomBrandingPatch.kt | 4 ++-- .../patches/music/misc/spoof/SpoofVideoStreamsPatch.kt | 4 ++-- .../patches/shared/misc/litho/filter/Fingerprints.kt | 2 +- .../youtube/layout/branding/header/ChangeHeaderPatch.kt | 2 -- .../buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt | 2 ++ .../layout/hide/endscreensuggestedvideo/Fingerprints.kt | 2 +- .../layout/hide/general/HideLayoutComponentsPatch.kt | 1 - .../youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt | 6 +++--- patches/src/main/resources/addresources/values/strings.xml | 2 +- 10 files changed, 13 insertions(+), 14 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java index 9e99122778..78b3196237 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java @@ -13,7 +13,7 @@ import app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; import app.revanced.extension.youtube.shared.PlayerType; @SuppressWarnings("unused") -final class HorizontalShelvesFilter extends Filter { +public final class HorizontalShelvesFilter extends Filter { private final ByteArrayFilterGroupList descriptionBuffers = new ByteArrayFilterGroupList(); private final ByteArrayFilterGroupList generalBuffers = new ByteArrayFilterGroupList(); diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt index 5b4ab33734..da72569219 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt @@ -6,8 +6,8 @@ import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.music.misc.extension.sharedExtensionPatch import app.revanced.patches.music.misc.gms.Constants.MUSIC_MAIN_ACTIVITY_NAME import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME -import app.revanced.patches.music.misc.gms.musicActivityOnCreateMethod import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.music.shared.mainActivityOnCreateMethod import app.revanced.patches.shared.layout.branding.EXTENSION_CLASS_DESCRIPTOR import app.revanced.patches.shared.layout.branding.baseCustomBrandingPatch import app.revanced.patches.shared.misc.mapping.ResourceType @@ -61,7 +61,7 @@ val customBrandingPatch = baseCustomBrandingPatch( originalAppPackageName = MUSIC_PACKAGE_NAME, isYouTubeMusic = true, numberOfPresetAppNames = 5, - getMainActivityOnCreate = { musicActivityOnCreateMethod }, + getMainActivityOnCreate = { mainActivityOnCreateMethod }, mainActivityName = MUSIC_MAIN_ACTIVITY_NAME, activityAliasNameWithIntents = MUSIC_MAIN_ACTIVITY_NAME, preferenceScreen = PreferenceScreen.GENERAL, diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt index a7c04efff6..7eea0373e5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt @@ -3,7 +3,7 @@ package app.revanced.patches.music.misc.spoof import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.music.misc.extension.sharedExtensionPatch -import app.revanced.patches.music.misc.gms.musicActivityOnCreateMethod +import app.revanced.patches.music.shared.mainActivityOnCreateMethod import app.revanced.patches.music.misc.settings.PreferenceScreen import app.revanced.patches.music.misc.settings.settingsPatch import app.revanced.patches.music.playservice.* @@ -14,7 +14,7 @@ import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch val spoofVideoStreamsPatch = spoofVideoStreamsPatch( extensionClassDescriptor = "Lapp/revanced/extension/music/patches/spoof/SpoofVideoStreamsPatch;", - getMainActivityOnCreateMethod = { musicActivityOnCreateMethod }, + getMainActivityOnCreateMethod = { mainActivityOnCreateMethod }, fixMediaFetchHotConfig = { is_7_16_or_greater }, fixMediaFetchHotConfigAlternative = { is_8_11_or_greater && !is_8_15_or_greater }, fixParsePlaybackResponseFeatureFlag = { is_7_33_or_greater }, diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt index b559e0bac1..3999511e10 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt @@ -85,7 +85,7 @@ internal val BytecodePatchContext.protobufBufferEncodeMethod by gettingFirstMeth Opcode.IGET_OBJECT(), field { definingClass == methodDefiningClass && type == "Lcom/google/android/libraries/elements/adl/UpbMessage;" }, ), - method { definingClass == "Lcom/google/android/libraries/elements/adl/UpbMessage;" && name == "jniEecode" }, + method { definingClass == "Lcom/google/android/libraries/elements/adl/UpbMessage;" && name == "jniEncode" }, ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt index e2666e8af7..39206b8252 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt @@ -5,11 +5,9 @@ import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.wideLiteral import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.Document -import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.shared.layout.branding.addBrandLicensePatch import app.revanced.patches.shared.layout.branding.header.CUSTOM_HEADER_RESOURCE_NAME import app.revanced.patches.shared.layout.branding.header.changeHeaderPatch -import app.revanced.patches.shared.layout.branding.header.variants import app.revanced.patches.shared.misc.mapping.ResourceType import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt index 22eb11d900..5304f2ed08 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt @@ -104,6 +104,8 @@ val overrideOpenInYouTubeMusicButtonPatch = bytecodePatch( else -> null } }) { method, (index, methodDescriptor) -> + if (method.definingClass == EXTENSION_CLASS_DESCRIPTOR) return@forEachInstructionAsSequence + val invokeString = when (val instruction = method.getInstruction(index)) { is RegisterRangeInstruction -> "invoke-static/range { v${instruction.startRegister} .. v${instruction.startRegister + instruction.registerCount - 1} }" diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/Fingerprints.kt index 58d4e3f0f0..f57bc82e3d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/Fingerprints.kt @@ -46,7 +46,7 @@ internal val BytecodePatchContext.removeOnLayoutChangeListenerMethodMatch by com ), allOf(Opcode.INVOKE_VIRTUAL(), method { name == "removeOnLayoutChangeListener" && - returnType == "Z" && + returnType == "V" && definingClass == "Lcom/google/android/apps/youtube/app/common/" + "player/overlay/YouTubePlayerOverlaysLayout;" }), diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt index 0490544503..e4a1555a6d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt @@ -147,7 +147,6 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( "revanced_comments_screen", preferences = setOf( SwitchPreference("revanced_hide_comments_ai_chat_summary"), - SwitchPreference("revanced_hide_comments_ai_summary"), SwitchPreference("revanced_hide_comments_channel_guidelines"), SwitchPreference("revanced_hide_comments_by_members_header"), SwitchPreference("revanced_hide_comments_section"), diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt index da6d26dd6e..6886b72e10 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt @@ -37,7 +37,7 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/ShortsAutoplayPatch;" -@Suppress("ObjectPropertyName") +@Suppress("unused") val shortsAutoplayPatch = bytecodePatch( name = "Shorts autoplay", description = "Adds options to automatically play the next Short.", @@ -110,10 +110,10 @@ val shortsAutoplayPatch = bytecodePatch( } } else { method { - definingClass == "reelEnumClass" && + definingClass == reelEnumClass && returnType == reelEnumClass && parameterTypes.size == 1 && - parameterTypes[0].startsWith("L") + parameterTypes[0] == "I" } } diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 09034b534a..f9e7dbbfd4 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1108,7 +1108,7 @@ To show the Audio track menu, change \'Spoof video streams\' to \'Android No SDK Hide end screen suggested video - "End screen suggested video is hidden + End screen suggested video is hidden End screen suggested video is shown From b435564fd4c854482ec6a897b684426058bbe2bb Mon Sep 17 00:00:00 2001 From: drobotk Date: Fri, 13 Mar 2026 13:05:04 +0100 Subject: [PATCH 19/41] wip: music --- .../extension/music/patches/ChangeHeaderPatch.java | 13 +++++++++++++ .../revanced/extension/music/settings/Settings.java | 2 +- .../layout/branding/header/ChangeHeaderPatch.kt | 7 +++++-- .../layout/miniplayer/ChangeMiniplayerColor.kt | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeHeaderPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeHeaderPatch.java index 143bedd549..74d9f458d3 100644 --- a/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeHeaderPatch.java +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeHeaderPatch.java @@ -1,5 +1,7 @@ package app.revanced.extension.music.patches; +import java.util.Objects; + import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.ResourceType; import app.revanced.extension.shared.Utils; @@ -34,4 +36,15 @@ public class ChangeHeaderPatch { return id; } } + + /** + * Injection point. + */ + @SuppressWarnings("unused") + public static int getHeaderDrawableId(int original) { + return Objects.requireNonNullElse( + Settings.HEADER_LOGO.get().getDrawableId(), + original + ); + } } diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java index 395f4e7c21..eb6929d947 100644 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java @@ -31,7 +31,7 @@ public class Settings extends YouTubeAndMusicSettings { public static final BooleanSetting HIDE_NAVIGATION_BAR_UPGRADE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_upgrade_button", TRUE, true); public static final BooleanSetting HIDE_NAVIGATION_BAR = new BooleanSetting("revanced_music_hide_navigation_bar", FALSE, true); public static final BooleanSetting HIDE_NAVIGATION_BAR_LABEL = new BooleanSetting("revanced_music_hide_navigation_bar_labels", FALSE, true); - public static final EnumSetting HEADER_LOGO = new EnumSetting<>("revnaced_header_logo", HeaderLogo.DEFAULT, true); + public static final EnumSetting HEADER_LOGO = new EnumSetting<>("revanced_header_logo", HeaderLogo.DEFAULT, true); // Player public static final BooleanSetting CHANGE_MINIPLAYER_COLOR = new BooleanSetting("revanced_music_change_miniplayer_color", FALSE, true); diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt index 40c6bb9422..5952682118 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/header/ChangeHeaderPatch.kt @@ -4,7 +4,6 @@ import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.wideLiteral import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.music.misc.settings.PreferenceScreen import app.revanced.patches.shared.layout.branding.header.changeHeaderPatch import app.revanced.patches.shared.misc.mapping.ResourceType @@ -20,7 +19,11 @@ private val targetResourceDirectoryNames = mapOf( ) private val variants = arrayOf("dark") -private val logoResourceNames = arrayOf("revanced_header_dark") + +private val logoResourceNames = arrayOf( + "revanced_header_minimal", + "revanced_header_rounded", +) private val headerDrawableNames = arrayOf( "action_bar_logo_ringo2", diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/ChangeMiniplayerColor.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/ChangeMiniplayerColor.kt index 608a33e319..974130d15f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/ChangeMiniplayerColor.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/miniplayer/ChangeMiniplayerColor.kt @@ -48,7 +48,7 @@ val changeMiniplayerColorPatch = bytecodePatch( ) apply { - addResources("music", "layout.miniplayercolor.changeMiniplayerColor") + addResources("music", "layout.miniplayer.changeMiniplayerColor") PreferenceScreen.PLAYER.addPreferences( SwitchPreference("revanced_music_change_miniplayer_color"), From a9aeb325de1160262c4db9b4b60c6c5e39730620 Mon Sep 17 00:00:00 2001 From: Sayanth <13906889+SayanthD@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:53:38 +0530 Subject: [PATCH 20/41] fix(YouTube - Spoof video streams): Set `ANDROID_REEL` client as default (#6878) --- .../java/app/revanced/extension/youtube/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index d69ed54e2d..cfe0f927d3 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -391,7 +391,7 @@ public class Settings extends YouTubeAndMusicSettings { public static final BooleanSetting EXTERNAL_BROWSER = new BooleanSetting("revanced_external_browser", TRUE, true); public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true, "revanced_spoof_device_dimensions_user_dialog_message"); - public static final EnumSetting SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS)); + public static final EnumSetting SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_REEL, true, parent(SPOOF_VIDEO_STREAMS)); public static final BooleanSetting SPOOF_VIDEO_STREAMS_AV1 = new BooleanSetting("revanced_spoof_video_streams_av1", FALSE, true, "revanced_spoof_video_streams_av1_user_dialog_message", new SpoofClientAv1Availability()); From b1ae92cdddaa2c31e7d36b172a94105a3da4910a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 19 Mar 2026 19:26:26 +0000 Subject: [PATCH 21/41] chore: Release v6.1.1-dev.1 [skip ci] ## [6.1.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v6.1.0...v6.1.1-dev.1) (2026-03-19) ### Bug Fixes * **YouTube - Spoof video streams:** Set `ANDROID_REEL` client as default ([#6878](https://github.com/ReVanced/revanced-patches/issues/6878)) ([a9aeb32](https://github.com/ReVanced/revanced-patches/commit/a9aeb325de1160262c4db9b4b60c6c5e39730620)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfebce7e69..6ea2c0d35e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [6.1.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v6.1.0...v6.1.1-dev.1) (2026-03-19) + + +### Bug Fixes + +* **YouTube - Spoof video streams:** Set `ANDROID_REEL` client as default ([#6878](https://github.com/ReVanced/revanced-patches/issues/6878)) ([a9aeb32](https://github.com/ReVanced/revanced-patches/commit/a9aeb325de1160262c4db9b4b60c6c5e39730620)) + # [6.1.0](https://github.com/ReVanced/revanced-patches/compare/v6.0.1...v6.1.0) (2026-03-18) diff --git a/gradle.properties b/gradle.properties index d65f6857e0..abde35e11e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,4 @@ org.gradle.parallel = true android.useAndroidX = true android.uniquePackageNames = false kotlin.code.style = official -version = 6.1.0 +version = 6.1.1-dev.1 From 9b66637a58d4e4509389c2cced570b5ac2539fcb Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 18:52:20 +0100 Subject: [PATCH 22/41] fix(YouTube - Shorts autoplay): Shorts do not autoplay when using older app targets Co-authored-by: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> --- .../extension/youtube/patches/ShortsAutoplayPatch.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ShortsAutoplayPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ShortsAutoplayPatch.java index b73bff3b88..a6c2cab2c2 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ShortsAutoplayPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ShortsAutoplayPatch.java @@ -101,8 +101,8 @@ public class ShortsAutoplayPatch { } ShortsLoopBehavior autoPlayBehavior = IS_21_10_OR_GREATER - ? ShortsLoopBehavior.SINGLE_PLAY - : ShortsLoopBehavior.AUTO_ADVANCE; + ? ShortsLoopBehavior.AUTO_ADVANCE + : ShortsLoopBehavior.SINGLE_PLAY; Enum overrideBehavior = (autoplay ? autoPlayBehavior From 5c7fc97059f2a42972f1201fcca325d1322fb095 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 18:53:09 +0100 Subject: [PATCH 23/41] chore(YouTube): Update force original audio user dialog string Co-authored-by: BlackGold8282 --- patches/src/main/resources/addresources/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 684a19cb65..fd6d1a0a20 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -160,7 +160,7 @@ Playback may not work" Using original audio language Using default audio - To use this feature, change \'Spoof video streams\' to any client except Android Studio + To use this feature, change \'Spoof video streams\' to Android Reel Debugging From b71e4f95ffacbaeca552c032dae69acc2d7373b3 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 18:57:49 +0100 Subject: [PATCH 24/41] feat(YouTube - Playback speed): Allows disabling tap and hold speed Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com> --- .../speed/CustomPlaybackSpeedPatch.java | 20 ++++++++++++++++++- .../speed/custom/CustomPlaybackSpeedPatch.kt | 12 +++++++++++ .../resources/addresources/values/strings.xml | 4 +++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java index 767ea7d7c3..e2c2353783 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java @@ -60,6 +60,11 @@ public class CustomPlaybackSpeedPatch { */ private static final float PROGRESS_BAR_VALUE_SCALE = 100; + /** + * Disable tap and hold speed, true when TAP_AND_HOLD_SPEED is 0. + */ + private static final boolean DISABLE_TAP_AND_HOLD_SPEED; + /** * Tap and hold speed. */ @@ -96,7 +101,12 @@ public class CustomPlaybackSpeedPatch { speedFormatter.setMaximumFractionDigits(2); final float holdSpeed = Settings.SPEED_TAP_AND_HOLD.get(); - if (holdSpeed > 0 && holdSpeed <= PLAYBACK_SPEED_MAXIMUM) { + DISABLE_TAP_AND_HOLD_SPEED = holdSpeed == 0; + + if (DISABLE_TAP_AND_HOLD_SPEED) { + // A value for handling exceptions, but this is not used. + TAP_AND_HOLD_SPEED = Settings.SPEED_TAP_AND_HOLD.defaultValue; + } else if (holdSpeed > 0 && holdSpeed <= PLAYBACK_SPEED_MAXIMUM) { TAP_AND_HOLD_SPEED = holdSpeed; } else { showInvalidCustomSpeedToast(); @@ -108,6 +118,14 @@ public class CustomPlaybackSpeedPatch { customPlaybackSpeedsMax = customPlaybackSpeeds[customPlaybackSpeeds.length - 1]; } + /** + * Injection point. + * Called before {@link #getTapAndHoldSpeed()} + */ + public static boolean disableTapAndHoldSpeed(boolean original) { + return !DISABLE_TAP_AND_HOLD_SPEED && original; + } + /** * Injection point. */ diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt index 606c78c458..b048d53f0b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt @@ -186,6 +186,18 @@ internal val customPlaybackSpeedPatch = bytecodePatch( move-result v$speedRegister """ ) + + val enabledIndex = it[3].index + val enabledRegister = + getInstruction(enabledIndex).registerA + + addInstructions( + enabledIndex, + """ + invoke-static { v$enabledRegister }, $EXTENSION_CLASS_DESCRIPTOR->disableTapAndHoldSpeed(Z)Z + move-result v$enabledRegister + """ + ) } } } diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index fd6d1a0a20..48edc4f46c 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1742,7 +1742,9 @@ Enabling this can unlock higher video qualities" Invalid custom playback speeds Auto Custom tap and hold speed - Playback speed between 0-8 + "Playback speed between 0-8 + +Set to 0, to disable tap and hold speed" Remember playback speed changes From 14545c874ddad65e3151cce09a0b612f2e5a6fb2 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 18:58:08 +0100 Subject: [PATCH 25/41] fix(YouTube - Hide Shorts components): Resolve "Hide 'Use this sound' button" and "Hide 'Use this template' button" not working Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../youtube/patches/litho/ShortsFilter.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java index d4fd98f96d..44507e234f 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java @@ -252,23 +252,27 @@ public final class ShortsFilter extends Filter { // through the "Short remixing this video" section. "floating_action_button.e", // Second filter needed for "Use this sound" that can appear below the video title. - REEL_METAPANEL_PATH + REEL_METAPANEL_PATH, + REEL_PLAYER_OVERLAY_PATH ); useSoundButtonBuffer = new ByteArrayFilterGroup( null, - "yt_outline_camera_" + "yt_outline_camera_", + "yt_outline_experimental_camera_" ); useTemplateButton = new StringFilterGroup( Settings.HIDE_SHORTS_USE_TEMPLATE_BUTTON, // Second filter needed for "Use this template" that can appear below the video title. - REEL_METAPANEL_PATH + REEL_METAPANEL_PATH, + REEL_PLAYER_OVERLAY_PATH ); useTemplateButtonBuffer = new ByteArrayFilterGroup( null, - "yt_outline_template_add_" + "yt_outline_template_add_", + "yt_outline_experimental_template_add_" ); shortsActionButton = new StringFilterGroup( From 3514039e8a92cc9aa7999ae3544a2cca1fe67369 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 18:58:43 +0100 Subject: [PATCH 26/41] feat(Theme): Use dynamic system accents for Material You colors Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../revanced/patches/shared/layout/theme/BaseThemePatch.kt | 5 ++++- .../app/revanced/patches/youtube/layout/theme/ThemePatch.kt | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/BaseThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/BaseThemePatch.kt index 8c720c76e0..f8b14b13d8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/BaseThemePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/BaseThemePatch.kt @@ -62,7 +62,10 @@ internal val darkThemeBackgroundColorOption = stringOption( default = "@android:color/black", values = mapOf( "Pure black" to "@android:color/black", - "Material You" to "@android:color/system_neutral1_900", + "Material You (Neutral)" to "@android:color/system_neutral1_900", + "Material You - Primary" to "@android:color/system_accent1_800", + "Material You - Secondary" to "@android:color/system_accent2_800", + "Material You - Tertiary" to "@android:color/system_accent3_800", "Classic (old YouTube)" to "#212121", "Catppuccin (Mocha)" to "#181825", "Dark pink" to "#290025", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt index 91479cb8e1..8d5026b17d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt @@ -48,7 +48,10 @@ val themePatch = baseThemePatch( default = "@android:color/white", values = mapOf( "White" to "@android:color/white", - "Material You" to "@android:color/system_neutral1_50", + "Material You (Neutral)" to "@android:color/system_neutral1_100", + "Material You - Primary" to "@android:color/system_accent1_200", + "Material You - Secondary" to "@android:color/system_accent2_200", + "Material You - Tertiary" to "@android:color/system_accent3_200", "Catppuccin (Latte)" to "#E6E9EF", "Light pink" to "#FCCFF3", "Light blue" to "#D1E0FF", From 7b8a3061a25e7550c236e7fce9ac8d9eb0afc253 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 19:01:23 +0100 Subject: [PATCH 27/41] feat(YouTube): Add experimental support for `21.11.480` Co-authored-by: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> --- .../patches/litho/LayoutComponentsFilter.java | 1 + .../hide/general/HideLayoutComponentsPatch.kt | 30 ++++++++++++------- .../misc/playservice/VersionCheckPatch.kt | 3 ++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java index 4bf3bd2054..bda565a952 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java @@ -480,6 +480,7 @@ public final class LayoutComponentsFilter extends Filter { * Injection point. */ public static boolean hideFloatingMicrophoneButton(final boolean original) { + // FIXME? Is this feature still relevant? When/where does this microphone appear? return original || Settings.HIDE_FLOATING_MICROPHONE_BUTTON.get(); } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt index e4a1555a6d..83de3bddaf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt @@ -19,6 +19,7 @@ import app.revanced.patches.youtube.misc.litho.lazily.hookTreeNodeResult import app.revanced.patches.youtube.misc.litho.lazily.lazilyConvertedElementHookPatch import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch import app.revanced.patches.youtube.misc.playservice.is_20_21_or_greater +import app.revanced.patches.youtube.misc.playservice.is_21_11_or_greater import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch @@ -157,7 +158,7 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( SwitchPreference("revanced_hide_comments_preview_comment"), SwitchPreference("revanced_hide_comments_thanks_button"), SwitchPreference("revanced_sanitize_comments_category_bar"), - ), + ), sorting = Sorting.UNSORTED, ), SwitchPreference("revanced_hide_channel_bar"), @@ -247,7 +248,6 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( ), ) ), - SwitchPreference("revanced_hide_floating_microphone_button"), SwitchPreference( key = "revanced_hide_horizontal_shelves", tag = "app.revanced.extension.shared.settings.preference.BulletPointSwitchPreference", @@ -275,6 +275,12 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( ) } + if (!is_21_11_or_greater) { + PreferenceScreen.FEED.addPreferences( + SwitchPreference("revanced_hide_floating_microphone_button") + ) + } + hookTreeNodeResult("$COMMENTS_FILTER_CLASS_NAME->sanitizeCommentsCategoryBar") // region Hide mix playlists @@ -449,18 +455,22 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( // region Hide Floating microphone - showFloatingMicrophoneButtonMethodMatch.let { - it.method.apply { - val index = it[-1] - val register = getInstruction(index).registerA + if (!is_21_11_or_greater) { + // Code has moved in 21.11+, but it's not clear when/ where this + // floating microphone can show or if this patch is still relevant. + showFloatingMicrophoneButtonMethodMatch.let { + it.method.apply { + val index = it[-1] + val register = getInstruction(index).registerA - addInstructions( - index + 1, - """ + addInstructions( + index + 1, + """ invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideFloatingMicrophoneButton(Z)Z move-result v$register """, - ) + ) + } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt index 0903a3a718..0e75333421 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt @@ -129,6 +129,8 @@ var is_21_08_or_greater : Boolean by Delegates.notNull() private set var is_21_10_or_greater : Boolean by Delegates.notNull() private set +var is_21_11_or_greater : Boolean by Delegates.notNull() + private set val versionCheckPatch = resourcePatch( description = "Uses the Play Store service version to find the major/minor version of the YouTube target app.", @@ -187,5 +189,6 @@ val versionCheckPatch = resourcePatch( is_21_07_or_greater = 260805000 <= playStoreServicesVersion is_21_08_or_greater = 260905000 <= playStoreServicesVersion is_21_10_or_greater = 261080000 <= playStoreServicesVersion + is_21_11_or_greater = 261205000 <= playStoreServicesVersion } } From 9fa641ed8184d0921de0fbde8bdf3a5200038831 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 19:02:49 +0100 Subject: [PATCH 28/41] fix(Sanitize sharing links): Sanitize new `is` sharing parameter Co-authored-by: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> --- .../extension/shared/patches/SanitizeSharingLinksPatch.java | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/SanitizeSharingLinksPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/SanitizeSharingLinksPatch.java index b0bcbc6f04..32ff22c38c 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/SanitizeSharingLinksPatch.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/SanitizeSharingLinksPatch.java @@ -11,6 +11,7 @@ public final class SanitizeSharingLinksPatch { private static final LinkSanitizer sanitizer = new LinkSanitizer( "si", + "is", // New (localized?) tracking parameter. "feature" // Old tracking parameter name, and may be obsolete. ); From 733f3bb2cd64f8d7e559a2db5de5867d790a2af7 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 20:09:30 +0100 Subject: [PATCH 29/41] fix(YouTube - Change form factor): Explore button sometimes shows in Automotive layout Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com> --- .../patches/ChangeFormFactorPatch.java | 108 ++++++++++-------- .../youtube/patches/VideoAdsPatch.java | 15 ++- .../patches/litho/KeywordContentFilter.java | 2 +- .../youtube/patches/litho/ShortsFilter.java | 2 +- .../youtube/shared/NavigationBar.java | 4 - .../youtube/ad/general/HideAdsPatch.kt | 1 + .../patches/youtube/ad/video/Fingerprints.kt | 9 ++ .../patches/youtube/ad/video/VideoAdsPatch.kt | 47 ++++++-- .../formfactor/ChangeFormFactorPatch.kt | 20 +++- .../contexthook/ClientContextHookPatch.kt | 48 +++++--- .../youtube/misc/contexthook/Fingerprints.kt | 73 +++++++++--- .../resources/addresources/values/strings.xml | 8 +- 12 files changed, 222 insertions(+), 115 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java index d7cf0faba1..240e9b9b08 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java @@ -2,13 +2,10 @@ package app.revanced.extension.youtube.patches; import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; -import android.view.View; - import androidx.annotation.Nullable; -import java.util.Objects; +import java.util.Optional; -import app.revanced.extension.shared.Logger; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.NavigationBar; import app.revanced.extension.youtube.shared.PlayerType; @@ -20,7 +17,7 @@ public class ChangeFormFactorPatch { /** * Unmodified, and same as un-patched. */ - DEFAULT(null), + DEFAULT(null, false), /** *
          * Some changes include:
@@ -28,60 +25,56 @@ public class ChangeFormFactorPatch {
          * - watch history is missing.
          * - feed thumbnails fade in.
          */
-        UNKNOWN(0),
-        SMALL(1),
-        LARGE(2),
+        UNKNOWN(0, true),
+        SMALL(1, false),
+        LARGE(2, false),
         /**
          * Cars with 'Google built-in'.
          * Layout seems identical to {@link #UNKNOWN}
          * even when using an Android Automotive device.
          */
-        AUTOMOTIVE(3),
-        WEARABLE(4);
+        AUTOMOTIVE(3, true),
+        WEARABLE(4, true);
 
         @Nullable
         final Integer formFactorType;
+        final boolean isBroken;
 
-        FormFactor(@Nullable Integer formFactorType) {
+        FormFactor(@Nullable Integer formFactorType, boolean isBroken) {
             this.formFactorType = formFactorType;
+            this.isBroken = isBroken;
         }
     }
 
+    private static final FormFactor FORM_FACTOR = Settings.CHANGE_FORM_FACTOR.get();
+
     @Nullable
-    private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType;
-    private static final boolean USING_AUTOMOTIVE_TYPE = Objects.requireNonNull(
-            FormFactor.AUTOMOTIVE.formFactorType).equals(FORM_FACTOR_TYPE);
+    private static final Integer FORM_FACTOR_TYPE = FORM_FACTOR.formFactorType;
+    private static final boolean IS_BROKEN_FORM_FACTOR = FORM_FACTOR.isBroken;
 
     /**
      * Injection point.
+     * 

+ * Called before {@link #replaceBrokenFormFactor(int)}. + * Called from all endpoints. */ - public static int getFormFactor(int original) { - if (FORM_FACTOR_TYPE == null) return original; - - if (USING_AUTOMOTIVE_TYPE) { - // Do not change if the player is opening or is opened, - // otherwise the video description cannot be opened. - PlayerType current = PlayerType.getCurrent(); - if (current.isMaximizedOrFullscreen() || current == PlayerType.WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED) { - Logger.printDebug(() -> "Using original form factor for player"); - return original; - } - - if (!NavigationBar.isSearchBarActive()) { - // Automotive type shows error 400 when opening a channel page and using some explore tab. - // This is a bug in unpatched YouTube that occurs on actual Android Automotive devices. - // Work around the issue by using the original form factor if not in search and the - // navigation back button is present. - if (NavigationBar.isBackButtonVisible()) { - Logger.printDebug(() -> "Using original form factor, as back button is visible without search present"); - return original; - } - - // Do not change library tab otherwise watch history is hidden. - // Do this check last since the current navigation button is required. - if (NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY) { - return original; - } + public static int getUniversalFormFactor(int original) { + if (FORM_FACTOR_TYPE == null) { + return original; + } + if (IS_BROKEN_FORM_FACTOR + && !PlayerType.getCurrent().isMaximizedOrFullscreen() + && !NavigationBar.isSearchBarActive()) { + // Automotive type shows error 400 when opening a channel page and using some explore tab. + // This is a bug in unpatched YouTube that occurs on actual Android Automotive devices. + // Work around the issue by using the tablet form factor if not in search and the + // navigation back button is present. + if (NavigationBar.isBackButtonVisible() + // Do not change library tab otherwise watch history is hidden. + // Do this check last since the current navigation button is required. + || NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY) { + // The form factor most similar to AUTOMOTIVE is LARGE, so it is replaced with LARGE. + return Optional.ofNullable(FormFactor.LARGE.formFactorType).orElse(original); } } @@ -90,16 +83,31 @@ public class ChangeFormFactorPatch { /** * Injection point. + *

+ * Called after {@link #getUniversalFormFactor(int)}. + * Called from the '/get_watch', '/guide', '/next' and '/reel' endpoints. + *

+ * The '/guide' endpoint relates to navigation buttons. + * If {@link #IS_BROKEN_FORM_FACTOR} is true in this endpoint, + * the explore button (which is deprecated) will be shown. + *

+ * The '/get_watch' and '/next' endpoints relate to elements below the player (channel bar, comments, related videos). + * If {@link #IS_BROKEN_FORM_FACTOR} is true in this endpoint, + * the video description panel will not open. + *

+ * The '/reel' endpoint relates to Shorts player. + * If {@link #IS_BROKEN_FORM_FACTOR} is true in this endpoint, + * the Shorts comment panel will not open. */ - public static void navigationTabCreated(NavigationButton button, View tabView) { - // On first startup of the app the navigation buttons are fetched and updated. - // If the user immediately opens the 'You' or opens a video, then the call to - // update the navigation buttons will use the non-automotive form factor - // and the explore tab is missing. - // Fixing this is not so simple because of the concurrent calls for the player and You tab. - // For now, always hide the explore tab. - if (USING_AUTOMOTIVE_TYPE && button == NavigationButton.EXPLORE) { - tabView.setVisibility(View.GONE); + public static int replaceBrokenFormFactor(int original) { + if (FORM_FACTOR_TYPE == null) { + return original; + } + if (IS_BROKEN_FORM_FACTOR) { + // The form factor most similar to AUTOMOTIVE is LARGE, so it is replaced with LARGE. + return Optional.ofNullable(FormFactor.LARGE.formFactorType).orElse(original); + } else { + return original; } } } \ No newline at end of file diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VideoAdsPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VideoAdsPatch.java index 22447193c9..496ac92fbf 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VideoAdsPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VideoAdsPatch.java @@ -4,23 +4,22 @@ import app.revanced.extension.youtube.settings.Settings; @SuppressWarnings("unused") public class VideoAdsPatch { - - private static final boolean SHOW_VIDEO_ADS = !Settings.HIDE_VIDEO_ADS.get(); + private static final boolean HIDE_VIDEO_ADS = Settings.HIDE_VIDEO_ADS.get(); /** * Injection point. */ - public static boolean shouldShowAds() { - return SHOW_VIDEO_ADS; + public static boolean hideVideoAds() { + return HIDE_VIDEO_ADS; } /** * Injection point. */ - public static String hideShortsAds(String osName) { - return SHOW_VIDEO_ADS - ? osName - : "Android Automotive"; + public static String hideVideoAds(String osName) { + return HIDE_VIDEO_ADS + ? "Android Automotive" + : osName; } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java index 4f18eb412d..75c678c12e 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java @@ -528,7 +528,7 @@ public final class KeywordContentFilter extends Filter { } return switch (selectedNavButton) { - case HOME, EXPLORE -> hideHome; + case HOME -> hideHome; case SUBSCRIPTIONS -> hideSubscriptions; // User is in the Library or notifications. default -> false; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java index 44507e234f..f8e3f102b5 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java @@ -535,7 +535,7 @@ public final class ShortsFilter extends Filter { } return switch (selectedNavButton) { - case HOME, EXPLORE -> hideHome; + case HOME -> hideHome; case SUBSCRIPTIONS -> hideSubscriptions; case LIBRARY -> hideHistory; default -> false; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java index 8f15b79c51..6b06a76de7 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java @@ -310,10 +310,6 @@ public final class NavigationBar { * This tab will never be in a selected state, even if the Create video UI is on screen. */ CREATE("CREATION_TAB_LARGE", "CREATION_TAB_LARGE_CAIRO"), - /** - * Only shown to automotive layout. - */ - EXPLORE("TAB_EXPLORE"), SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", "TAB_SUBSCRIPTIONS_CAIRO"), /** * Notifications tab. Only present when diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt index 584c8bfe56..81e546ddfc 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt @@ -215,6 +215,7 @@ val hideAdsPatch = bytecodePatch( setOf( Endpoint.BROWSE, Endpoint.SEARCH, + Endpoint.NEXT, ).forEach { endpoint -> addOSNameHook( endpoint, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/Fingerprints.kt index 2565c0e79f..b4f7f04ce8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/Fingerprints.kt @@ -1,9 +1,18 @@ package app.revanced.patches.youtube.ad.video import app.revanced.patcher.gettingFirstMethodDeclaratively +import app.revanced.patcher.parameterTypes import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.returnType internal val BytecodePatchContext.loadVideoAdsMethod by gettingFirstMethodDeclaratively( "TriggerBundle doesn't have the required metadata specified by the trigger ", "Ping migration no associated ping bindings for activated trigger: ", ) + +internal val BytecodePatchContext.playerBytesAdLayoutMethod by gettingFirstMethodDeclaratively( + "Bootstrapped layout construction resulted in non PlayerBytesLayout. PlayerAds count: " +) { + returnType("V") + parameterTypes("L") +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt index 8cf3d57d6e..056faf82e9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt @@ -1,16 +1,20 @@ package app.revanced.patches.youtube.ad.video -import app.revanced.patcher.extensions.ExternalLabel import app.revanced.patcher.extensions.addInstructionsWithLabels -import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.youtube.misc.contexthook.Endpoint +import app.revanced.patches.youtube.misc.contexthook.addOSNameHook +import app.revanced.patches.youtube.misc.contexthook.hookClientContextPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/VideoAdsPatch;" + @Suppress("ObjectPropertyName") val videoAdsPatch = bytecodePatch( name = "Video ads", @@ -20,6 +24,7 @@ val videoAdsPatch = bytecodePatch( sharedExtensionPatch, settingsPatch, addResourcesPatch, + hookClientContextPatch ) compatibleWith( @@ -41,15 +46,33 @@ val videoAdsPatch = bytecodePatch( SwitchPreference("revanced_hide_video_ads"), ) - loadVideoAdsMethod.addInstructionsWithLabels( - 0, - """ - invoke-static { }, Lapp/revanced/extension/youtube/patches/VideoAdsPatch;->shouldShowAds()Z - move-result v0 - if-nez v0, :show_video_ads - return-void - """, - ExternalLabel("show_video_ads", loadVideoAdsMethod.getInstruction(0)), - ) + setOf( + loadVideoAdsMethod, + playerBytesAdLayoutMethod, + ).forEach { method -> + method.addInstructionsWithLabels( + 0, + """ + invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->hideVideoAds()Z + move-result v0 + if-eqz v0, :show_video_ads + return-void + :show_video_ads + nop + """ + ) + } + + + setOf( + Endpoint.GET_WATCH, + Endpoint.PLAYER, + Endpoint.REEL, + ).forEach { endpoint -> + addOSNameHook( + endpoint, + "$EXTENSION_CLASS_DESCRIPTOR->hideVideoAds(Ljava/lang/String;)Ljava/lang/String;", + ) + } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt index 83e753720c..042fa97ef9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt @@ -7,6 +7,9 @@ import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.shared.misc.settings.preference.ListPreference +import app.revanced.patches.youtube.misc.contexthook.Endpoint +import app.revanced.patches.youtube.misc.contexthook.addClientFormFactorHook +import app.revanced.patches.youtube.misc.contexthook.hookClientContextPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.navigation.hookNavigationButtonCreated import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch @@ -27,6 +30,7 @@ val changeFormFactorPatch = bytecodePatch( sharedExtensionPatch, settingsPatch, addResourcesPatch, + hookClientContextPatch, navigationBarHookPatch ) @@ -49,8 +53,6 @@ val changeFormFactorPatch = bytecodePatch( ListPreference("revanced_change_form_factor"), ) - hookNavigationButtonCreated(EXTENSION_CLASS_DESCRIPTOR) - val formFactorEnumConstructorClass = formFactorEnumConstructorMethod.definingClass val createPlayerRequestBodyWithModelMatch = firstMethodComposite { @@ -71,11 +73,23 @@ val changeFormFactorPatch = bytecodePatch( addInstructions( index + 1, """ - invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getFormFactor(I)I + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getUniversalFormFactor(I)I move-result v$register """, ) } } + + setOf( + Endpoint.GET_WATCH, + Endpoint.NEXT, + Endpoint.GUIDE, + Endpoint.REEL, + ).forEach { endpoint -> + addClientFormFactorHook( + endpoint, + "$EXTENSION_CLASS_DESCRIPTOR->replaceBrokenFormFactor(I)I", + ) + } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/ClientContextHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/ClientContextHookPatch.kt index ec82e4bdbe..7725d7e6a7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/ClientContextHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/ClientContextHookPatch.kt @@ -1,15 +1,21 @@ package app.revanced.patches.youtube.misc.contexthook -import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable import app.revanced.patcher.accessFlags +import app.revanced.patcher.allOf import app.revanced.patcher.classDef +import app.revanced.patcher.composingFirstMethod import app.revanced.patcher.extensions.addInstructionsWithLabels import app.revanced.patcher.extensions.fieldReference import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.methodReference +import app.revanced.patcher.field +import app.revanced.patcher.firstMethodComposite import app.revanced.patcher.firstMethodDeclaratively import app.revanced.patcher.immutableClassDef +import app.revanced.patcher.instructions +import app.revanced.patcher.invoke +import app.revanced.patcher.method import app.revanced.patcher.parameterTypes import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.bytecodePatch @@ -26,8 +32,9 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.immutable.ImmutableMethod +import java.lang.ref.WeakReference -private lateinit var browseIdField: FieldReference +private lateinit var clientFormFactorField: FieldReference private lateinit var clientInfoField: FieldReference private lateinit var clientVersionField: FieldReference private lateinit var messageLiteBuilderField: FieldReference @@ -41,9 +48,15 @@ enum class Endpoint( BROWSE( BytecodePatchContext::browseEndpointParentMethod::get ), + GET_WATCH( + BytecodePatchContext::getWatchEndpointConstructorPrimaryMethod::get, + BytecodePatchContext::getWatchEndpointConstructorSecondaryMethod::get, + ), GUIDE( BytecodePatchContext::guideEndpointConstructorMethod::get ), + NEXT(BytecodePatchContext::nextEndpointParentMethod::get), + PLAYER(BytecodePatchContext::playerEndpointParentMethod::get), REEL( BytecodePatchContext::reelCreateItemsEndpointConstructorMethod::get, BytecodePatchContext::reelItemWatchEndpointConstructorMethod::get, @@ -90,12 +103,8 @@ val hookClientContextPatch = bytecodePatch( } } - browseEndpointParentMethod.immutableClassDef.browseEndpointConstructorMethodMatch.let { - it.method.apply { - val browseIdIndex = it[-1] - browseIdField = - getInstruction(browseIdIndex).fieldReference!! - } + clientFormFactorField = getSetClientFormFactorMethodMatch().let { + it.method.getInstruction(it[0]).fieldReference!! } } @@ -140,7 +149,7 @@ val hookClientContextPatch = bytecodePatch( } ) - it.findInstructionIndicesReversedOrThrow(Opcode.RETURN_VOID).forEach { index -> + it.findInstructionIndicesReversedOrThrow(Opcode.RETURN_VOID).forEach { index -> it.addInstructionsAtControlFlowLabel( index, "invoke-direct/range { p0 .. p0 }, ${it.definingClass}->$helperMethodName()V" @@ -152,19 +161,22 @@ val hookClientContextPatch = bytecodePatch( } } +fun addClientFormFactorHook(endPoint: Endpoint, descriptor: String) { + endPoint.instructions += """ + iget v2, v1, $clientFormFactorField + invoke-static { v2 }, $descriptor + move-result v2 + iput v2, v1, $clientFormFactorField + """ +} + fun addClientVersionHook(endPoint: Endpoint, descriptor: String) { - endPoint.instructions += if (endPoint == Endpoint.BROWSE) """ - iget-object v3, p0, $browseIdField - iget-object v2, v1, $clientVersionField - invoke-static { v3, v2 }, $descriptor - move-result-object v2 - iput-object v2, v1, $clientVersionField - """ else """ + endPoint.instructions += """ iget-object v2, v1, $clientVersionField invoke-static { v2 }, $descriptor move-result-object v2 iput-object v2, v1, $clientVersionField - """ + """ } fun addOSNameHook(endPoint: Endpoint, descriptor: String) { @@ -173,5 +185,5 @@ fun addOSNameHook(endPoint: Endpoint, descriptor: String) { invoke-static { v2 }, $descriptor move-result-object v2 iput-object v2, v1, $osNameField - """ + """ } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/Fingerprints.kt index 9c52762689..749b577656 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/Fingerprints.kt @@ -1,6 +1,7 @@ package app.revanced.patches.youtube.misc.contexthook import app.revanced.patcher.ClassDefComposing +import app.revanced.patcher.CompositeMatch import app.revanced.patcher.accessFlags import app.revanced.patcher.after import app.revanced.patcher.afterAtMost @@ -9,8 +10,11 @@ import app.revanced.patcher.composingFirstMethod import app.revanced.patcher.custom import app.revanced.patcher.extensions.methodReference import app.revanced.patcher.field +import app.revanced.patcher.firstImmutableMethodDeclaratively +import app.revanced.patcher.firstMethodComposite import app.revanced.patcher.gettingFirstImmutableMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively +import app.revanced.patcher.immutableClassDef import app.revanced.patcher.instructions import app.revanced.patcher.invoke import app.revanced.patcher.method @@ -22,7 +26,6 @@ import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.ClassDef import com.android.tools.smali.dexlib2.iface.Method -import com.google.common.io.ByteArrayDataOutput internal const val CLIENT_INFO_CLASS_DESCRIPTOR = $$"Lcom/google/protos/youtube/api/innertube/InnertubeContext$ClientInfo;" @@ -86,25 +89,33 @@ internal val BytecodePatchContext.buildDummyClientContextBodyMethodMatch by comp ) } -internal val ClassDef.browseEndpointConstructorMethodMatch by ClassDefComposing.composingFirstMethod { - accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) - returnType("V") - var methodDefiningClass = "" - custom { - methodDefiningClass = this.definingClass - true + + +internal fun BytecodePatchContext.getSetClientFormFactorMethodMatch(): CompositeMatch { + val clientFormFactorEnumConstructorMethod = firstImmutableMethodDeclaratively( + "UNKNOWN_FORM_FACTOR", + "SMALL_FORM_FACTOR", + "LARGE_FORM_FACTOR", + "AUTOMOTIVE_FORM_FACTOR", + "WEARABLE_FORM_FACTOR", + ) { + accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR) + }.immutableClassDef.firstMethodComposite { + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + returnType("L") + parameterTypes("I") } - instructions( - ""(), - after( - allOf( - Opcode.IPUT_OBJECT(), - field { definingClass == methodDefiningClass && type == "Ljava/lang/String;" } - ) - ), - ) + return firstMethodComposite { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returnType("V") + parameterTypes("L") + instructions( + allOf(Opcode.IGET(), field { type == "I" && definingClass == CLIENT_INFO_CLASS_DESCRIPTOR }), + method { this == clientFormFactorEnumConstructorMethod } + ) + } } internal val BytecodePatchContext.browseEndpointParentMethod by gettingFirstImmutableMethodDeclaratively( @@ -113,6 +124,22 @@ internal val BytecodePatchContext.browseEndpointParentMethod by gettingFirstImmu returnType("Ljava/lang/String;") } +internal val BytecodePatchContext.getWatchEndpointConstructorPrimaryMethod by gettingFirstMethodDeclaratively( + "get_watch" +) { + accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) + returnType("V") + custom { immutableClassDef.fields.any { it.type == "Ljava/util/function/Consumer;" } } +} + +internal val BytecodePatchContext.getWatchEndpointConstructorSecondaryMethod by gettingFirstMethodDeclaratively( + "get_watch" +) { + accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) + returnType("V") + custom { immutableClassDef.fields.none { it.type == "Ljava/util/function/Consumer;" } } +} + internal val BytecodePatchContext.guideEndpointConstructorMethod by gettingFirstImmutableMethodDeclaratively( "guide" ) { @@ -120,6 +147,18 @@ internal val BytecodePatchContext.guideEndpointConstructorMethod by gettingFirst returnType("V") } +internal val BytecodePatchContext.nextEndpointParentMethod by gettingFirstImmutableMethodDeclaratively( + "watchNextType" +) { + returnType("Ljava/lang/String;") +} + +internal val BytecodePatchContext.playerEndpointParentMethod by gettingFirstImmutableMethodDeclaratively( + "dataExpiredForSeconds" +) { + returnType("Ljava/lang/String;") +} + internal val BytecodePatchContext.reelCreateItemsEndpointConstructorMethod by gettingFirstImmutableMethodDeclaratively( "reel/create_reel_items" ) { diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 48edc4f46c..47c483e5c5 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1428,10 +1428,16 @@ Ready to submit?" Tablet layout • Community posts are hidden +• Playback in feeds setting is not available +• Remix button and Sound button are not available in Shorts +• Video action bar is not collapsed Automotive layout +• Feed is organized by topics and channels +• Playback in feeds setting is not available +• Remix button and Sound button are not available in Shorts • Shorts open in the regular player -• Feed is organized by topics and channels" +• Video action bar is not collapsed" Spoof app version From 7f1874ebe04b7f9877b13a6c969a7fd025850c21 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 20:11:29 +0100 Subject: [PATCH 30/41] fix(YouTube): Do not show fullscreen black gradient with 21.03+ experimental app targets Co-authored-by: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> --- .../patches/youtube/misc/playercontrols/Fingerprints.kt | 7 +++++++ .../youtube/misc/playercontrols/PlayerControlsPatch.kt | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/Fingerprints.kt index f289bc9035..dcdec33876 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/Fingerprints.kt @@ -126,3 +126,10 @@ internal val BytecodePatchContext.playerControlsButtonStrokeFeatureFlagMethod by parameterTypes() instructions(45713296L()) } + +internal val BytecodePatchContext.playerOverlayOpacityGradientFeatureFlagMethod by gettingFirstMethodDeclaratively { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returnType("Z") + parameterTypes() + instructions(45729621L()) +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt index 3dde033313..e131eb5cf0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt @@ -307,5 +307,11 @@ val playerControlsPatch = bytecodePatch( playerControlsButtonStrokeFeatureFlagMethod.returnLate(false) } } + + if (is_21_03_or_greater) { + // If enabled it can show a black gradient on lower part of screen in fullscreen mode. + // This override may not be needed if the new bold player overlay icons are in use. + playerOverlayOpacityGradientFeatureFlagMethod.returnLate(false) + } } } From 136cecc290190668edc9e9282a112db8d57fe5c0 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 20:12:26 +0100 Subject: [PATCH 31/41] fix(YouTube - Hide Shorts components): Resolve "Hide 'Use this sound' button" and "Hide 'Use this template' button" breaking Shorts player Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../youtube/patches/litho/ShortsFilter.java | 70 ++++++++----------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java index f8e3f102b5..8eda2a3355 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java @@ -53,10 +53,6 @@ public final class ShortsFilter extends Filter { private final StringFilterGroup shortsCompactFeedVideo; private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer; private final StringFilterGroup channelProfile; - private final StringFilterGroup useSoundButton; - private final ByteArrayFilterGroup useSoundButtonBuffer; - private final StringFilterGroup useTemplateButton; - private final ByteArrayFilterGroup useTemplateButtonBuffer; private final StringFilterGroup autoDubbedLabel; private final StringFilterGroup subscribeButton; @@ -71,6 +67,9 @@ public final class ShortsFilter extends Filter { private final StringFilterGroup suggestedAction; private final ByteArrayFilterGroupList suggestedActionsBuffer = new ByteArrayFilterGroupList(); + private final StringFilterGroup useButtons; + private final ByteArrayFilterGroupList useButtonsBuffer = new ByteArrayFilterGroupList(); + private final StringFilterGroup shortsActionBar; private final StringFilterGroup shortsActionButton; private final StringFilterGroupList shortsActionButtonGroupList = new StringFilterGroupList(); @@ -246,35 +245,6 @@ public final class ShortsFilter extends Filter { ) ); - useSoundButton = new StringFilterGroup( - Settings.HIDE_SHORTS_USE_SOUND_BUTTON, - // First filter needed for "Use this sound" that can appear when viewing Shorts - // through the "Short remixing this video" section. - "floating_action_button.e", - // Second filter needed for "Use this sound" that can appear below the video title. - REEL_METAPANEL_PATH, - REEL_PLAYER_OVERLAY_PATH - ); - - useSoundButtonBuffer = new ByteArrayFilterGroup( - null, - "yt_outline_camera_", - "yt_outline_experimental_camera_" - ); - - useTemplateButton = new StringFilterGroup( - Settings.HIDE_SHORTS_USE_TEMPLATE_BUTTON, - // Second filter needed for "Use this template" that can appear below the video title. - REEL_METAPANEL_PATH, - REEL_PLAYER_OVERLAY_PATH - ); - - useTemplateButtonBuffer = new ByteArrayFilterGroup( - null, - "yt_outline_template_add_", - "yt_outline_experimental_template_add_" - ); - shortsActionButton = new StringFilterGroup( null, // Can be any of: @@ -285,6 +255,26 @@ public final class ShortsFilter extends Filter { "button.e" ); + useButtons = new StringFilterGroup( + null, + REEL_PLAYER_OVERLAY_PATH, + REEL_METAPANEL_PATH, + "floating_action_button.e" + ); + + useButtonsBuffer.addAll( + new ByteArrayFilterGroup( + Settings.HIDE_SHORTS_USE_SOUND_BUTTON, + "yt_outline_camera_", + "yt_outline_experimental_camera_" + ), + new ByteArrayFilterGroup( + Settings.HIDE_SHORTS_USE_TEMPLATE_BUTTON, + "yt_outline_template_add_", + "yt_outline_experimental_template_add_" + ) + ); + suggestedAction = new StringFilterGroup( null, "suggested_action.e" @@ -293,7 +283,7 @@ public final class ShortsFilter extends Filter { addPathCallbacks( shortsCompactFeedVideo, shelfHeaderPath, joinButton, subscribeButton, paidPromotionLabel, livePreview, suggestedAction, pausedOverlayButtons, channelBar, infoPanel, previewComment, - autoDubbedLabel, fullVideoLinkLabel, videoTitle, useSoundButton, soundButton, stickers, + autoDubbedLabel, fullVideoLinkLabel, videoTitle, soundButton, stickers, useButtons, reelCarousel, reelSoundMetadata, likeFountain, likeButton, dislikeButton, shortsActionBar ); @@ -433,14 +423,6 @@ public final class ShortsFilter extends Filter { return reelCarouselBuffer.check(buffer).isFiltered(); } - if (matchedGroup == useSoundButton) { - return useSoundButtonBuffer.check(buffer).isFiltered(); - } - - if (matchedGroup == useTemplateButton) { - return useTemplateButtonBuffer.check(buffer).isFiltered(); - } - if (matchedGroup == shortsCompactFeedVideo) { return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(buffer).isFiltered(); } @@ -464,6 +446,10 @@ public final class ShortsFilter extends Filter { return false; } + if (matchedGroup == useButtons) { + return path.contains("button.e") && useButtonsBuffer.check(buffer).isFiltered(); + } + if (matchedGroup == suggestedAction) { // Skip searching the buffer if all suggested actions are set to hidden. // This has a secondary effect of hiding all new un-identified actions From 009cf7146264f702018dcac366b3285ad23f6255 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 20:20:27 +0100 Subject: [PATCH 32/41] fix(YouTube - Hide layout components): Resolve "Hide community posts" not working when selecting a channel from subscribed channels bar in Subscriptions tab Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../shared/patches/litho/CustomFilter.java | 15 +++++++-- .../shared/patches/litho/Filter.java | 33 ++++++++++++++----- .../youtube/patches/litho/AdsFilter.java | 11 +++++-- .../litho/AdvancedVideoQualityMenuFilter.java | 12 +++++-- .../youtube/patches/litho/CommentsFilter.java | 11 +++++-- .../litho/DescriptionComponentsFilter.java | 11 +++++-- .../litho/HorizontalShelvesFilter.java | 11 +++++-- .../patches/litho/KeywordContentFilter.java | 28 ++++++++++------ .../patches/litho/LayoutComponentsFilter.java | 31 ++++++++++++----- .../litho/PlaybackSpeedMenuFilter.java | 11 +++++-- .../litho/PlayerFlyoutMenuItemsFilter.java | 11 +++++-- .../litho/ReturnYouTubeDislikeFilter.java | 18 +++++++--- .../youtube/patches/litho/ShortsFilter.java | 13 ++++++-- .../litho/VideoActionButtonsFilter.java | 12 +++++-- 14 files changed, 175 insertions(+), 53 deletions(-) diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/CustomFilter.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/CustomFilter.java index beb623a799..176414aaba 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/CustomFilter.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/CustomFilter.java @@ -12,6 +12,8 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import app.revanced.extension.shared.ConversionContext; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.Utils; @@ -172,14 +174,21 @@ public final class CustomFilter extends Filter { if (!groups.isEmpty()) { CustomFilterGroup[] groupsArray = groups.toArray(new CustomFilterGroup[0]); - Logger.printDebug(()-> "Using Custom filters: " + Arrays.toString(groupsArray)); + Logger.printDebug(() -> "Using Custom filters: " + Arrays.toString(groupsArray)); addPathCallbacks(groupsArray); } } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered( + ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { // All callbacks are custom filter groups. CustomFilterGroup custom = (CustomFilterGroup) matchedGroup; diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/Filter.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/Filter.java index b34ca9bdd7..1cdcb7387d 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/Filter.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/Filter.java @@ -1,5 +1,7 @@ package app.revanced.extension.shared.patches.litho; +import app.revanced.extension.shared.ConversionContext; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; @@ -9,27 +11,33 @@ import java.util.List; /** * Filters litho based components. - * + *

* Callbacks to filter content are added using {@link #addIdentifierCallbacks(StringFilterGroup...)} * and {@link #addPathCallbacks(StringFilterGroup...)}. - * + *

* To filter {@link FilterContentType#PROTOBUFFER} or {@link FilterContentType#ACCESSIBILITY}, first add a callback to * either an identifier or a path. - * Then inside {@link #isFiltered(String, String, String, byte[], StringFilterGroup, FilterContentType, int)} + * Then inside {@link #isFiltered(ContextInterface, String, String, String, byte[], StringFilterGroup, FilterContentType, int)} * search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern) * or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern). - * + *

* All callbacks must be registered before the constructor completes. */ public abstract class Filter { public enum FilterContentType { + CONTEXT, IDENTIFIER, PATH, ACCESSIBILITY, PROTOBUFFER } + /** + * Context callbacks. Do not add to this instance, + * and instead use {@link #addContextCallbacks(StringFilterGroup...)}. + */ + protected final List contextCallbacks = new ArrayList<>(); /** * Identifier callbacks. Do not add to this instance, * and instead use {@link #addIdentifierCallbacks(StringFilterGroup...)}. @@ -42,7 +50,15 @@ public abstract class Filter { public final List pathCallbacks = new ArrayList<>(); /** - * Adds callbacks to {@link #isFiltered(String, String, String, byte[], StringFilterGroup, FilterContentType, int)} + * Adds callbacks to {@link #isFiltered(ContextInterface, String, String, String, byte[], StringFilterGroup, FilterContentType, int)} + * if any of the groups are found. + */ + protected final void addContextCallbacks(StringFilterGroup... groups) { + contextCallbacks.addAll(Arrays.asList(groups)); + } + + /** + * Adds callbacks to {@link #isFiltered(ContextInterface, String, String, String, byte[], StringFilterGroup, FilterContentType, int)} * if any of the groups are found. */ protected final void addIdentifierCallbacks(StringFilterGroup... groups) { @@ -50,7 +66,7 @@ public abstract class Filter { } /** - * Adds callbacks to {@link #isFiltered(String, String, String, byte[], StringFilterGroup, FilterContentType, int)} + * Adds callbacks to {@link #isFiltered(ContextInterface, String, String, String, byte[], StringFilterGroup, FilterContentType, int)} * if any of the groups are found. */ protected final void addPathCallbacks(StringFilterGroup... groups) { @@ -64,6 +80,7 @@ public abstract class Filter { *

* Method is called off the main thread. * + * @param contextInterface The interface to get the Litho conversion context. * @param identifier Litho identifier. * @param accessibility Accessibility string, or an empty string if not present for the component. * @param buffer Protocol buffer. @@ -72,8 +89,8 @@ public abstract class Filter { * @param contentIndex Matched index of the identifier or path. * @return True if the litho component should be filtered out. */ - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, String identifier, String accessibility, String path, byte[] buffer, + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { return true; } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/AdsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/AdsFilter.java index cb262e0165..c30049cb33 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/AdsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/AdsFilter.java @@ -12,6 +12,7 @@ import androidx.annotation.Nullable; import java.util.List; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; @@ -167,8 +168,14 @@ public final class AdsFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { if (matchedGroup == buyMovieAd) { return contentIndex == 0 && buyMovieAdBuffer.check(buffer).isFiltered(); } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/AdvancedVideoQualityMenuFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/AdvancedVideoQualityMenuFilter.java index a969c9b0e0..24a3b243ea 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/AdvancedVideoQualityMenuFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/AdvancedVideoQualityMenuFilter.java @@ -1,5 +1,7 @@ package app.revanced.extension.youtube.patches.litho; +import app.revanced.extension.shared.ConversionContext; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQualityMenuPatch; @@ -21,8 +23,14 @@ public final class AdvancedVideoQualityMenuFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { isVideoQualityMenuVisible = true; return false; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/CommentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/CommentsFilter.java index 48c5c284fe..8afb75b5f9 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/CommentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/CommentsFilter.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; import java.util.List; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.FilterGroup.*; @@ -85,8 +86,14 @@ public final class CommentsFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { if (matchedGroup == comments) { if (path.startsWith(VIDEO_LOCKUP_WITH_ATTACHMENT_PATH)) { return Settings.HIDE_COMMENTS_SECTION_IN_HOME_FEED.get(); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java index 6b2590e904..9aedaa8f28 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java @@ -1,5 +1,6 @@ package app.revanced.extension.youtube.patches.litho; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.shared.patches.litho.FilterGroup.*; @@ -133,8 +134,14 @@ public final class DescriptionComponentsFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { // The description panel can be opened in both the regular player and Shorts. // If the description panel is opened in a Shorts, PlayerType is 'HIDDEN', // so 'PlayerType.getCurrent().isMaximizedOrFullscreen()' does not guarantee that the description panel is open. diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java index 78b3196237..49722901e8 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java @@ -2,6 +2,7 @@ package app.revanced.extension.youtube.patches.litho; import static app.revanced.extension.youtube.patches.LayoutReloadObserverPatch.isActionBarVisible; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; @@ -80,8 +81,14 @@ public final class HorizontalShelvesFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { if (contentIndex != 0) { return false; } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java index 75c678c12e..e9477a1902 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java @@ -12,6 +12,8 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import app.revanced.extension.shared.ConversionContext; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.ByteTrieSearch; @@ -47,7 +49,7 @@ public final class KeywordContentFilter extends Filter { /** * Strings found in the buffer for every video. Full strings should be specified. - * + *

* This list does not include every common buffer string, and this can be added/changed as needed. * Words must be entered with the exact casing as found in the buffer. */ @@ -122,7 +124,7 @@ public final class KeywordContentFilter extends Filter { /** * Path components to not filter. Cannot filter the buffer when these are present, * otherwise text in UI controls can be filtered as a keyword (such as using "Playlist" as a keyword). - * + *

* This is also a small performance improvement since * the buffer of the parent component was already searched and passed. */ @@ -156,10 +158,10 @@ public final class KeywordContentFilter extends Filter { * Rolling average of how many videos were filtered by a keyword. * Used to detect if a keyword passes the initial check against {@link #STRINGS_IN_EVERY_BUFFER} * but a keyword is still hiding all videos. - * + *

* This check can still fail if some extra UI elements pass the keywords, * such as the video chapter preview or any other elements. - * + *

* To test this, add a filter that appears in all videos (such as 'ovd='), * and open the subscription feed. In practice this does not always identify problems * in the home feed and search, because the home feed has a finite amount of content and @@ -226,7 +228,7 @@ public final class KeywordContentFilter extends Filter { * @return If the string contains any characters from languages that do not use spaces between words. */ private static boolean isLanguageWithNoSpaces(String text) { - for (int i = 0, length = text.length(); i < length;) { + for (int i = 0, length = text.length(); i < length; ) { final int codePoint = text.codePointAt(i); Character.UnicodeBlock block = Character.UnicodeBlock.of(codePoint); @@ -277,7 +279,7 @@ public final class KeywordContentFilter extends Filter { /** * @return If the start and end indexes are not surrounded by other letters. - * If the indexes are surrounded by numbers/symbols/punctuation it is considered a whole word. + * If the indexes are surrounded by numbers/symbols/punctuation it is considered a whole word. */ private static boolean keywordMatchIsWholeWord(byte[] text, int keywordStartIndex, int keywordLength) { final Integer codePointBefore = getUtf8CodePointBefore(text, keywordStartIndex); @@ -296,7 +298,7 @@ public final class KeywordContentFilter extends Filter { /** * @return The UTF8 character point immediately before the index, - * or null if the bytes before the index is not a valid UTF8 character. + * or null if the bytes before the index is not a valid UTF8 character. */ @Nullable private static Integer getUtf8CodePointBefore(byte[] data, int index) { @@ -312,7 +314,7 @@ public final class KeywordContentFilter extends Filter { /** * @return The UTF8 character point at the index, - * or null if the index holds no valid UTF8 character. + * or null if the index holds no valid UTF8 character. */ @Nullable private static Integer getUtf8CodePointAt(byte[] data, int index) { @@ -556,8 +558,14 @@ public final class KeywordContentFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { if (contentIndex != 0 && matchedGroup == startsWithFilter) { return false; } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java index bda565a952..ae243ad8af 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java @@ -18,6 +18,7 @@ import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.Utils; @@ -75,6 +76,7 @@ public final class LayoutComponentsFilter extends Filter { private final StringFilterGroup chipBar; private final StringFilterGroup channelProfile; private final StringFilterGroupList channelProfileGroupList; + private final StringFilterGroupList communityPostStringFilterGroup; public LayoutComponentsFilter() { exceptions.addPatterns( @@ -140,6 +142,16 @@ public final class LayoutComponentsFilter extends Filter { "poll_post_responsive_root.e", "shared_post_root.e" ); + communityPostStringFilterGroup = new StringFilterGroupList(); + communityPostStringFilterGroup.addAll( + new StringFilterGroup( + null, + // home + "horizontalCollectionSwipeProtector=null", + // subscriptions + "heightConstraint=null" + ) + ); final var subscribersCommunityGuidelines = new StringFilterGroup( Settings.HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES, @@ -379,8 +391,14 @@ public final class LayoutComponentsFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { // This identifier is used not only in players but also in search results: // https://github.com/ReVanced/revanced-patches/issues/3245 // Until 2024, medical information panels such as Covid-19 also used this identifier and were shown in the search results. @@ -400,13 +418,8 @@ public final class LayoutComponentsFilter extends Filter { return channelProfileGroupList.check(accessibility).isFiltered(); } - if (matchedGroup == communityPosts - && NavigationBar.isBackButtonVisible() - && !NavigationBar.isSearchBarActive() - && PlayerType.getCurrent() != PlayerType.WATCH_WHILE_MAXIMIZED) { - // Allow community posts on channel profile page, - // or if viewing an individual channel in the feed. - return false; + if (matchedGroup == communityPosts) { + return communityPostStringFilterGroup.check(contextInterface.toString()).isFiltered(); } if (exceptions.matches(path)) return false; // Exceptions are not filtered. diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlaybackSpeedMenuFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlaybackSpeedMenuFilter.java index e1752f184b..e78fb0235d 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlaybackSpeedMenuFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlaybackSpeedMenuFilter.java @@ -1,5 +1,6 @@ package app.revanced.extension.youtube.patches.litho; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.FilterGroup.*; import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch; @@ -38,8 +39,14 @@ public final class PlaybackSpeedMenuFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { if (matchedGroup == oldPlaybackMenuGroup) { isOldPlaybackSpeedMenuVisible = true; } else { diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlayerFlyoutMenuItemsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlayerFlyoutMenuItemsFilter.java index e7d136a16e..bcccb9da90 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlayerFlyoutMenuItemsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/PlayerFlyoutMenuItemsFilter.java @@ -1,5 +1,6 @@ package app.revanced.extension.youtube.patches.litho; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; @@ -112,8 +113,14 @@ public final class PlayerFlyoutMenuItemsFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { if (matchedGroup == videoQualityMenuFooter) { return true; } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ReturnYouTubeDislikeFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ReturnYouTubeDislikeFilter.java index a9513f28e0..6fabeaa8b4 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ReturnYouTubeDislikeFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ReturnYouTubeDislikeFilter.java @@ -7,6 +7,8 @@ import androidx.annotation.Nullable; import java.util.LinkedHashSet; import java.util.Map; +import app.revanced.extension.shared.ConversionContext; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.TrieSearch; import app.revanced.extension.shared.Utils; @@ -19,14 +21,14 @@ import app.revanced.extension.youtube.settings.Settings; /** * Searches for video IDs in the proto buffer of Shorts dislike. - * + *

* Because multiple litho dislike spans are created in the background * (and also anytime litho refreshes the components, which is somewhat arbitrary), * that makes the value of {@link VideoInformation#getVideoId()} and {@link VideoInformation#getPlayerResponseVideoId()} * unreliable to determine which video ID a Shorts litho span belongs to. - * + *

* But the correct video ID does appear in the protobuffer just before a Shorts litho span is created. - * + *

* Once a way to asynchronously update litho text is found, this strategy will no longer be needed. */ public final class ReturnYouTubeDislikeFilter extends Filter { @@ -88,8 +90,14 @@ public final class ReturnYouTubeDislikeFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) { return false; } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java index 8eda2a3355..0da4d4edfe 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java @@ -1,15 +1,18 @@ package app.revanced.extension.youtube.patches.litho; +import static app.revanced.extension.shared.ConversionContext.*; import static app.revanced.extension.youtube.patches.LayoutReloadObserverPatch.isActionBarVisible; import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; import android.view.View; +import app.revanced.extension.shared.ConversionContext; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.FilterGroup.*; import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList; import app.revanced.extension.shared.patches.litho.FilterGroupList.StringFilterGroupList; + import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar; import java.lang.ref.WeakReference; @@ -394,8 +397,14 @@ public final class ShortsFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { if (contentType == FilterContentType.IDENTIFIER) { if (matchedGroup == shelfHeaderIdentifier) { // Shelf header reused in history/channel/etc. diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/VideoActionButtonsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/VideoActionButtonsFilter.java index 7d1e1cca71..4b02de9a53 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/VideoActionButtonsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/VideoActionButtonsFilter.java @@ -1,5 +1,7 @@ package app.revanced.extension.youtube.patches.litho; +import app.revanced.extension.shared.ConversionContext; +import app.revanced.extension.shared.ConversionContext.ContextInterface; import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; @@ -158,8 +160,14 @@ public final class VideoActionButtonsFilter extends Filter { } @Override - public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { if (matchedGroup == likeSubscribeGlow) { return path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX) || path.startsWith(COMPACTIFY_VIDEO_ACTION_BAR_PATH); From 53318c48eea72ebef30bccbe15b15e80728e2c77 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 19 Mar 2026 20:41:02 +0100 Subject: [PATCH 33/41] fix(YouTube - Video quality): Initial video quality is not overridden Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com> --- .../quality/RememberVideoQualityPatch.java | 22 +++++++- .../stub/src/main/java/j$/util/Optional.java | 18 ++++++ .../youtube/video/quality/Fingerprints.kt | 28 ++++++++++ .../quality/RememberVideoQualityPatch.kt | 34 ++++++++++-- .../kotlin/app/revanced/util/BytecodeUtils.kt | 55 +++++++++++++++---- 5 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 extensions/youtube/stub/src/main/java/j$/util/Optional.java diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 1cd584d617..8ce3f2a1c0 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -11,8 +11,9 @@ import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.patches.VideoInformation.*; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.ShortsPlayerState; +import j$.util.Optional; -@SuppressWarnings("unused") +@SuppressWarnings({"rawtypes", "unused"}) public class RememberVideoQualityPatch { private static final IntegerSetting videoQualityWifi = Settings.VIDEO_QUALITY_DEFAULT_WIFI; @@ -66,6 +67,25 @@ public class RememberVideoQualityPatch { } } + /** + * Injection point. + *

+ * Overrides the initial video quality to not follow the 'Video quality preferences' in YouTube settings. + * (e.g. 'Auto (recommended)' - 360p/480p, 'Higher picture quality' - 720p/1080p...) + * If the maximum video quality available is 1080p and the default video quality is 2160p, + * 1080p is used as an initial video quality. + *

+ * Called before {@link #newVideoStarted(VideoInformation.PlaybackController)}. + */ + public static Optional getInitialVideoQuality(Optional optional) { + int preferredQuality = getDefaultQualityResolution(); + if (preferredQuality != VideoInformation.AUTOMATIC_VIDEO_QUALITY_VALUE) { + Logger.printDebug(() -> "initialVideoQuality: " + preferredQuality); + return Optional.of(preferredQuality); + } + return optional; + } + /** * Injection point. * @param userSelectedQualityIndex Element index of {@link VideoInformation#getCurrentQualities()}. diff --git a/extensions/youtube/stub/src/main/java/j$/util/Optional.java b/extensions/youtube/stub/src/main/java/j$/util/Optional.java new file mode 100644 index 0000000000..3f2bb9773e --- /dev/null +++ b/extensions/youtube/stub/src/main/java/j$/util/Optional.java @@ -0,0 +1,18 @@ +package j$.util; + +public final class Optional { + + /** + * Returns an {@code Optional} describing the given non-{@code null} + * value. + * + * @param value the value to describe, which must be non-{@code null} + * @param the type of the value + * @return an {@code Optional} with the value present + * @throws NullPointerException if value is {@code null} + */ + public static Optional of(T value) { + return null; + } + +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt index 614fd9c0f3..305b5d2e13 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt @@ -1,5 +1,6 @@ package app.revanced.patches.youtube.video.quality +import app.revanced.patcher.CompositeMatch import app.revanced.patcher.accessFlags import app.revanced.patcher.afterAtMost import app.revanced.patcher.allOf @@ -7,6 +8,7 @@ import app.revanced.patcher.composingFirstMethod import app.revanced.patcher.custom import app.revanced.patcher.definingClass import app.revanced.patcher.field +import app.revanced.patcher.firstMethodComposite import app.revanced.patcher.firstMethodDeclaratively import app.revanced.patcher.gettingFirstImmutableMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively @@ -18,6 +20,7 @@ import app.revanced.patcher.opcodes import app.revanced.patcher.parameterTypes import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.returnType +import app.revanced.util.findFieldFromToString import app.revanced.util.literal import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -60,6 +63,31 @@ internal val BytecodePatchContext.hidePremiumVideoQualityGetArrayMethod by getti custom { AccessFlags.SYNTHETIC.isSet(immutableClassDef.accessFlags) } } +internal const val FIXED_RESOLUTION_STRING = ", initialPlaybackVideoQualityFixedResolution=" + + +internal fun BytecodePatchContext.getPlaybackStartParametersConstructorMethod(): CompositeMatch { + val playbackStartParametersToStringMethod = firstMethodDeclaratively( + FIXED_RESOLUTION_STRING + ) { + name("toString") + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returnType("Ljava/lang/String;") + parameterTypes() + } + + val initialResolutionField = playbackStartParametersToStringMethod + .findFieldFromToString(FIXED_RESOLUTION_STRING) + + // Inject a call to override initial video quality. + return playbackStartParametersToStringMethod.immutableClassDef.firstMethodComposite { + name("") + instructions( + allOf(Opcode.IPUT_OBJECT(), field { this == initialResolutionField }) + ) + } +} + internal val BytecodePatchContext.videoQualityItemOnClickParentMethod by gettingFirstImmutableMethodDeclaratively( "VIDEO_QUALITIES_MENU_BOTTOM_SHEET_FRAGMENT", ) { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt index 15db089e5f..36bc2b148d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt @@ -1,8 +1,15 @@ package app.revanced.patches.youtube.video.quality +import app.revanced.patcher.allOf import app.revanced.patcher.extensions.addInstruction +import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.getInstruction +import app.revanced.patcher.field +import app.revanced.patcher.firstMethodComposite import app.revanced.patcher.immutableClassDef +import app.revanced.patcher.instructions +import app.revanced.patcher.invoke +import app.revanced.patcher.name import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch @@ -15,6 +22,8 @@ import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.shared.videoQualityChangedMethodMatch import app.revanced.patches.youtube.video.information.onCreateHook import app.revanced.patches.youtube.video.information.videoInformationPatch +import app.revanced.util.findFieldFromToString +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction private const val EXTENSION_CLASS_DESCRIPTOR = @@ -64,11 +73,28 @@ val rememberVideoQualityPatch = bytecodePatch { onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "newVideoStarted") + // Inject a call to override initial video quality. + getPlaybackStartParametersConstructorMethod().let { + it.method.apply { + val index = it[-1] + val register = getInstruction(index).registerA + + addInstructions( + index, + """ + invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->getInitialVideoQuality(Lj$/util/Optional;)Lj$/util/Optional; + move-result-object v$register + """ + ) + } + } + // Inject a call to remember the selected quality for Shorts. - videoQualityItemOnClickParentMethod.immutableClassDef.getVideoQualityItemOnClickMethod().addInstruction( - 0, - "invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->userChangedShortsQuality(I)V", - ) + videoQualityItemOnClickParentMethod.immutableClassDef.getVideoQualityItemOnClickMethod() + .addInstruction( + 0, + "invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->userChangedShortsQuality(I)V", + ) // Inject a call to remember the user selected quality for regular videos. videoQualityChangedMethodMatch.let { match -> diff --git a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index 5694608dfe..4ee01fe84b 100644 --- a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -38,7 +38,7 @@ import kotlin.collections.remove * * @param fieldName The name of the field to find. Partial matches are allowed. */ -private fun Method.findInstructionIndexFromToString(fieldName: String): Int { +private fun Method.findInstructionIndexFromToString(fieldName: String, isField: Boolean) : Int { val stringIndex = indexOfFirstInstruction { val reference = getReference() reference?.string?.contains(fieldName) == true @@ -67,22 +67,55 @@ private fun Method.findInstructionIndexFromToString(fieldName: String): Int { // Should never happen. throw IllegalArgumentException("Could not find StringBuilder append usage in: $this") } - val fieldUsageRegister = getInstruction(fieldUsageIndex).registerD + + var fieldUsageRegister = getInstruction(fieldUsageIndex).registerD // Look backwards up the method to find the instruction that sets the register. var fieldSetIndex = indexOfFirstInstructionReversedOrThrow(fieldUsageIndex - 1) { fieldUsageRegister == writeRegister } - // If the field is a method call, then adjust from MOVE_RESULT to the method call. - val fieldSetOpcode = getInstruction(fieldSetIndex).opcode - if (fieldSetOpcode == MOVE_RESULT || - fieldSetOpcode == MOVE_RESULT_WIDE || - fieldSetOpcode == MOVE_RESULT_OBJECT - ) { - fieldSetIndex-- + // Some 'toString()' methods, despite using a StringBuilder, convert the value via + // 'Object.toString()' or 'String.valueOf(object)' before appending it to the StringBuilder. + // In this case, the correct index cannot be found. + // Additional validation is done to find the index of the correct field or method. + // + // Check up to 3 method calls. + var checksLeft = 3 + while (checksLeft > 0) { + // If the field is a method call, then adjust from MOVE_RESULT to the method call. + val fieldSetOpcode = getInstruction(fieldSetIndex).opcode + if (fieldSetOpcode == MOVE_RESULT || + fieldSetOpcode == MOVE_RESULT_WIDE || + fieldSetOpcode == MOVE_RESULT_OBJECT + ) { + fieldSetIndex-- + } + + val fieldSetReference = getInstruction(fieldSetIndex).reference + + if (isField && fieldSetReference is FieldReference || + !isField && fieldSetReference is MethodReference + ) { + // Valid index. + return fieldSetIndex + } else if (fieldSetReference is MethodReference && + // Object.toString(), String.valueOf(object) + fieldSetReference.returnType == "Ljava/lang/String;" + ) { + fieldUsageRegister = getInstruction(fieldSetIndex).registerC + + // Look backwards up the method to find the instruction that sets the register. + fieldSetIndex = indexOfFirstInstructionReversedOrThrow(fieldSetIndex - 1) { + fieldUsageRegister == writeRegister + } + checksLeft-- + } else { + throw IllegalArgumentException("Unknown reference: $fieldSetReference") + } } + return fieldSetIndex } @@ -93,7 +126,7 @@ private fun Method.findInstructionIndexFromToString(fieldName: String): Int { */ context(context: BytecodePatchContext) internal fun Method.findMethodFromToString(fieldName: String): MutableMethod { - val methodUsageIndex = findInstructionIndexFromToString(fieldName) + val methodUsageIndex = findInstructionIndexFromToString(fieldName, false) return context.navigate(this).to(methodUsageIndex).stop() } @@ -103,7 +136,7 @@ internal fun Method.findMethodFromToString(fieldName: String): MutableMethod { * @param fieldName The name of the field to find. Partial matches are allowed. */ internal fun Method.findFieldFromToString(fieldName: String): FieldReference { - val methodUsageIndex = findInstructionIndexFromToString(fieldName) + val methodUsageIndex = findInstructionIndexFromToString(fieldName, true) return getInstruction(methodUsageIndex).getReference()!! } From 58898b92fe12f2f3d7f0f8794e89f265c2ba480b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 21 Mar 2026 19:37:50 +0100 Subject: [PATCH 34/41] fix(YouTube - Disable Shorts resuming on startup): Resolve patch not working on experimental versions Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../DisableResumingShortsOnStartupPatch.java | 21 +++++ ...sableResumingStartupShortsPlayerPatch.java | 21 ----- .../extension/youtube/settings/Settings.java | 2 +- .../DisableResumingShortsOnStartupPatch.kt | 40 ++++++---- .../layout/shortsresuming/Fingerprints.kt | 79 +++++++++++++++++++ .../layout/startupshortsreset/Fingerprints.kt | 46 ----------- .../resources/addresources/values/strings.xml | 8 +- 7 files changed, 130 insertions(+), 87 deletions(-) create mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableResumingShortsOnStartupPatch.java delete mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableResumingStartupShortsPlayerPatch.java rename patches/src/main/kotlin/app/revanced/patches/youtube/layout/{startupshortsreset => shortsresuming}/DisableResumingShortsOnStartupPatch.kt (75%) create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsresuming/Fingerprints.kt delete mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/Fingerprints.kt diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableResumingShortsOnStartupPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableResumingShortsOnStartupPatch.java new file mode 100644 index 0000000000..5f42b36db6 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableResumingShortsOnStartupPatch.java @@ -0,0 +1,21 @@ +package app.revanced.extension.youtube.patches; + +import app.revanced.extension.youtube.settings.Settings; + +@SuppressWarnings("unused") +public class DisableResumingShortsOnStartupPatch { + + /** + * Injection point. + */ + public static boolean disableResumingShortsOnStartup() { + return Settings.DISABLE_RESUMING_SHORTS_ON_STARTUP.get(); + } + + /** + * Injection point. + */ + public static boolean disableResumingShortsOnStartup(boolean original) { + return original && !Settings.DISABLE_RESUMING_SHORTS_ON_STARTUP.get(); + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableResumingStartupShortsPlayerPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableResumingStartupShortsPlayerPatch.java deleted file mode 100644 index 2e8c3cc06a..0000000000 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableResumingStartupShortsPlayerPatch.java +++ /dev/null @@ -1,21 +0,0 @@ -package app.revanced.extension.youtube.patches; - -import app.revanced.extension.youtube.settings.Settings; - -@SuppressWarnings("unused") -public class DisableResumingStartupShortsPlayerPatch { - - /** - * Injection point. - */ - public static boolean disableResumingStartupShortsPlayer() { - return Settings.DISABLE_RESUMING_SHORTS_PLAYER.get(); - } - - /** - * Injection point. - */ - public static boolean disableResumingStartupShortsPlayer(boolean original) { - return original && !Settings.DISABLE_RESUMING_SHORTS_PLAYER.get(); - } -} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 6f3c273865..18a557d67f 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -324,7 +324,7 @@ public class Settings extends YouTubeAndMusicSettings { public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true); // Shorts - public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", FALSE); + public static final BooleanSetting DISABLE_RESUMING_SHORTS_ON_STARTUP = new BooleanSetting("revanced_disable_resuming_shorts_on_startup", FALSE); public static final BooleanSetting DISABLE_SHORTS_BACKGROUND_PLAYBACK = new BooleanSetting("revanced_shorts_disable_background_playback", FALSE); public static final EnumSetting SHORTS_PLAYER_TYPE = new EnumSetting<>("revanced_shorts_player_type", ShortsPlayerType.SHORTS_PLAYER); public static final BooleanSetting HIDE_SHORTS_AI_BUTTON = new BooleanSetting("revanced_hide_shorts_ai_button", FALSE); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsresuming/DisableResumingShortsOnStartupPatch.kt similarity index 75% rename from patches/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch.kt rename to patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsresuming/DisableResumingShortsOnStartupPatch.kt index c7ca37a67f..2c759c98ec 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/DisableResumingShortsOnStartupPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsresuming/DisableResumingShortsOnStartupPatch.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.youtube.layout.startupshortsreset +package app.revanced.patches.youtube.layout.shortsresuming import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.getInstruction @@ -18,10 +18,11 @@ import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstruction import com.android.tools.smali.dexlib2.iface.reference.MethodReference private const val EXTENSION_CLASS_DESCRIPTOR = - "Lapp/revanced/extension/youtube/patches/DisableResumingStartupShortsPlayerPatch;" + "Lapp/revanced/extension/youtube/patches/DisableResumingShortsOnStartupPatch;" @Suppress("unused") val disableResumingShortsOnStartupPatch = bytecodePatch( @@ -44,24 +45,33 @@ val disableResumingShortsOnStartupPatch = bytecodePatch( "20.37.48", "20.40.45", "20.44.38" - // This patch is obsolete with 21.03 because YT seems to have - // removed resuming Shorts functionality. - // TODO: Before adding 21.03+, merge this patch into `Hide Shorts component` ), ) apply { - // 21.03+ seems to no longer have resuming Shorts functionality. - if (is_21_03_or_greater) return@apply - - addResources("youtube", "layout.startupshortsreset.disableResumingShortsOnStartupPatch") + addResources("youtube", "layout.shortsresuming.disableResumingShortsOnStartupPatch") PreferenceScreen.SHORTS.addPreferences( - SwitchPreference("revanced_disable_resuming_shorts_player"), + SwitchPreference("revanced_disable_resuming_shorts_on_startup"), ) - if (is_20_03_or_greater) { - userWasInShortsAlternativeMethodMatch.let { + if (is_21_03_or_greater) { + userWasInShortsEvaluateMethodMatch.let { + it.method.apply { + val instruction = getInstruction(it[0]) + val zMRegister = instruction.startRegister + 2 + + addInstructions( + it[0], + """ + invoke-static { v$zMRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->disableResumingShortsOnStartup(Z)Z + move-result v$zMRegister + """ + ) + } + } + } else if (is_20_03_or_greater) { + userWasInShortsListenerMethodMatch.let { it.method.apply { val insertIndex = it[2] + 1 val register = getInstruction(insertIndex).registerA @@ -69,7 +79,7 @@ val disableResumingShortsOnStartupPatch = bytecodePatch( addInstructions( insertIndex, """ - invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer(Z)Z + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->disableResumingShortsOnStartup(Z)Z move-result v$register """, ) @@ -87,7 +97,7 @@ val disableResumingShortsOnStartupPatch = bytecodePatch( addInstructionsAtControlFlowLabel( listenableInstructionIndex, """ - invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer()Z + invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disableResumingShortsOnStartup()Z move-result v$freeRegister if-eqz v$freeRegister, :show_startup_shorts_player return-void @@ -101,7 +111,7 @@ val disableResumingShortsOnStartupPatch = bytecodePatch( userWasInShortsConfigMethod.addInstructions( 0, """ - invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer()Z + invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->disableResumingShortsOnStartup()Z move-result v0 if-eqz v0, :show const/4 v0, 0x0 diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsresuming/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsresuming/Fingerprints.kt new file mode 100644 index 0000000000..4392f35283 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsresuming/Fingerprints.kt @@ -0,0 +1,79 @@ +package app.revanced.patches.youtube.layout.shortsresuming + +import app.revanced.patcher.* +import app.revanced.patcher.patch.BytecodePatchContext +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import kotlin.collections.all +import kotlin.collections.zip + + +/** + * 21.03+ + */ +internal val BytecodePatchContext.userWasInShortsEvaluateMethodMatch by composingFirstMethod { + val method1ParametersPrefix = listOf("L", "Z", "Z", "L", "Z") + val method2ParametersPrefix = listOf("L", "L", "L", "L", "L", "I") + + instructions( + allOf( + Opcode.INVOKE_DIRECT_RANGE(), + method { + name == "" && parameterTypes.zip(method1ParametersPrefix) + .all { (a, b) -> a.startsWith(b) } + } + ), + afterAtMost( + 50, allOf( + Opcode.INVOKE_DIRECT_RANGE(), + method { + name == "" && parameterTypes.zip(method2ParametersPrefix) + .all { (a, b) -> a.startsWith(b) } + } + ) + ) + ) +} + +/** + * 20.02+ + */ +internal +val BytecodePatchContext.userWasInShortsListenerMethodMatch by composingFirstMethod { + returnType("V") + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + parameterTypes("Ljava/lang/Object;") + instructions( + allOf(Opcode.CHECK_CAST(), type("Ljava/lang/Boolean;")), + after(method { toString() == "Ljava/lang/Boolean;->booleanValue()Z" }), + after(Opcode.MOVE_RESULT()), + // 20.40+ string was merged into another string and is a partial match. + afterAtMost(30, "ShortsStartup SetUserWasInShortsListener"(String::contains)), + ) +} + +/** + * Pre 20.02 + */ +internal +val BytecodePatchContext.userWasInShortsLegacyMethod by gettingFirstMethodDeclaratively { + returnType("V") + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + parameterTypes("Ljava/lang/Object;") + instructions( + "Failed to read user_was_in_shorts proto after successful warmup"(), + ) +} + +/** + * 18.15.40+ + */ +internal +val BytecodePatchContext.userWasInShortsConfigMethod by gettingFirstMethodDeclaratively { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returnType("Z") + parameterTypes() + instructions( + 45358360L(), + ) +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/Fingerprints.kt deleted file mode 100644 index 931bff6052..0000000000 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/Fingerprints.kt +++ /dev/null @@ -1,46 +0,0 @@ -package app.revanced.patches.youtube.layout.startupshortsreset - -import app.revanced.patcher.* -import app.revanced.patcher.patch.BytecodePatchContext -import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode - -/** - * 20.02+ - */ -internal val BytecodePatchContext.userWasInShortsAlternativeMethodMatch by composingFirstMethod { - returnType("V") - accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) - parameterTypes("Ljava/lang/Object;") - instructions( - allOf(Opcode.CHECK_CAST(), type("Ljava/lang/Boolean;")), - after(method { toString() == "Ljava/lang/Boolean;->booleanValue()Z" }), - after(Opcode.MOVE_RESULT()), - // 20.40+ string was merged into another string and is a partial match. - afterAtMost(15, "userIsInShorts: "(String::contains)), - ) -} - -/** - * Pre 20.02 - */ -internal val BytecodePatchContext.userWasInShortsLegacyMethod by gettingFirstMethodDeclaratively { - returnType("V") - accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) - parameterTypes("Ljava/lang/Object;") - instructions( - "Failed to read user_was_in_shorts proto after successful warmup"(), - ) -} - -/** - * 18.15.40+ - */ -internal val BytecodePatchContext.userWasInShortsConfigMethod by gettingFirstMethodDeclaratively { - accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) - returnType("Z") - parameterTypes() - instructions( - 45358360L(), - ) -} diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 47c483e5c5..ce5c2dd5fc 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1490,10 +1490,10 @@ If later turned off, it is recommended to clear the app data to prevent UI bugs. Limitation: Using the back button on the toolbar may not work" Start page is changed only on app startup - - Disable resuming Shorts player - Shorts player will not resume on app startup - Shorts player will resume on app startup + + Disable resuming Shorts player + Shorts player will not resume on app startup + Shorts player will resume on app startup Open Shorts with From e52114076ed2e5b9929f9b952860e9ff3727c323 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 21 Mar 2026 19:40:38 +0100 Subject: [PATCH 35/41] fix(YouTube - Playback speed): Old playback speed menu does not show with experimental app targets Co-authored-by: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> --- .../playback/speed/CustomPlaybackSpeedPatch.java | 8 ++++++++ .../video/speed/custom/CustomPlaybackSpeedPatch.kt | 13 ++++++++++++- .../youtube/video/speed/custom/Fingerprints.kt | 7 +++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java index e2c2353783..2b3784bcc8 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java @@ -126,6 +126,14 @@ public class CustomPlaybackSpeedPatch { return !DISABLE_TAP_AND_HOLD_SPEED && original; } + /** + * Injection point. + */ + public static boolean useNewFlyoutMenu(boolean useNewFlyout) { + // If using old speed turn off A/B flyout that breaks old playback speed menu. + return useNewFlyout && !Settings.RESTORE_OLD_SPEED_MENU.get(); + } + /** * Injection point. */ diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt index b048d53f0b..d7837d0794 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt @@ -21,12 +21,14 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.playservice.is_19_47_or_greater import app.revanced.patches.youtube.misc.playservice.is_20_34_or_greater +import app.revanced.patches.youtube.misc.playservice.is_21_02_or_greater import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTreeHook import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup import app.revanced.util.indexOfFirstLiteralInstructionOrThrow +import app.revanced.util.insertLiteralOverride import app.revanced.util.returnEarly import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @@ -88,7 +90,7 @@ internal val customPlaybackSpeedPatch = bytecodePatch( serverSideMaxSpeedFeatureFlagMethod.returnEarly(false) } - // region Force old video quality menu. + // region Force old playback speed menu. // Replace the speeds float array with custom speeds. speedArrayGeneratorMethodMatch.let { @@ -161,6 +163,15 @@ internal val customPlaybackSpeedPatch = bytecodePatch( // endregion + if (is_21_02_or_greater) { + flyoutMenuNonLegacyFeatureFlagMethodMatch.let { + it.method.insertLiteralOverride( + it[0], + "$EXTENSION_CLASS_DESCRIPTOR->useNewFlyoutMenu(Z)Z" + ) + } + } + // Close the unpatched playback dialog and show the custom speeds. addRecyclerViewTreeHook(EXTENSION_CLASS_DESCRIPTOR) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/Fingerprints.kt index e431b77c37..ee8e067dd6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/Fingerprints.kt @@ -32,6 +32,13 @@ internal val BytecodePatchContext.serverSideMaxSpeedFeatureFlagMethod by getting ) } +internal val BytecodePatchContext.flyoutMenuNonLegacyFeatureFlagMethodMatch by composingFirstMethod { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returnType("V") + parameterTypes() + instructions(45731126L()) +} + internal val BytecodePatchContext.speedArrayGeneratorMethodMatch by composingFirstMethod { accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) returnType("[L") From 09bce22aebc4e594063d2eec816ec9841134db31 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 21 Mar 2026 19:44:53 +0100 Subject: [PATCH 36/41] fix(YouTube - Hide Shorts components): Shorts shelves are sometimes not hidden Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com> --- .../extension/shared/ConversionContext.java | 8 ++++ .../shared/patches/litho/Filter.java | 14 ------- .../patches/LayoutReloadObserverPatch.java | 8 ++-- .../litho/DescriptionComponentsFilter.java | 31 +++++--------- .../litho/HorizontalShelvesFilter.java | 29 +++++--------- .../patches/litho/LayoutComponentsFilter.java | 13 +----- .../youtube/patches/litho/ShortsFilter.java | 40 +++++++++++++------ 7 files changed, 62 insertions(+), 81 deletions(-) diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/ConversionContext.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/ConversionContext.java index ea1f94fc54..8233d8eab8 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/ConversionContext.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/ConversionContext.java @@ -9,5 +9,13 @@ public final class ConversionContext { StringBuilder patch_getPathBuilder(); String patch_getIdentifier(); + + default boolean isHomeFeedOrRelatedVideo() { + return toString().contains("horizontalCollectionSwipeProtector=null"); + } + + default boolean isSubscriptionOrLibrary() { + return toString().contains("heightConstraint=null"); + } } } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/Filter.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/Filter.java index 1cdcb7387d..fe1fb66725 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/Filter.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/Filter.java @@ -26,18 +26,12 @@ import java.util.List; public abstract class Filter { public enum FilterContentType { - CONTEXT, IDENTIFIER, PATH, ACCESSIBILITY, PROTOBUFFER } - /** - * Context callbacks. Do not add to this instance, - * and instead use {@link #addContextCallbacks(StringFilterGroup...)}. - */ - protected final List contextCallbacks = new ArrayList<>(); /** * Identifier callbacks. Do not add to this instance, * and instead use {@link #addIdentifierCallbacks(StringFilterGroup...)}. @@ -49,14 +43,6 @@ public abstract class Filter { */ public final List pathCallbacks = new ArrayList<>(); - /** - * Adds callbacks to {@link #isFiltered(ContextInterface, String, String, String, byte[], StringFilterGroup, FilterContentType, int)} - * if any of the groups are found. - */ - protected final void addContextCallbacks(StringFilterGroup... groups) { - contextCallbacks.addAll(Arrays.asList(groups)); - } - /** * Adds callbacks to {@link #isFiltered(ContextInterface, String, String, String, byte[], StringFilterGroup, FilterContentType, int)} * if any of the groups are found. diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LayoutReloadObserverPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LayoutReloadObserverPatch.java index 95108882c6..6b43787d24 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LayoutReloadObserverPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LayoutReloadObserverPatch.java @@ -41,9 +41,11 @@ public class LayoutReloadObserverPatch { return; } - if (PlayerType.getCurrent() == PlayerType.WATCH_WHILE_MINIMIZED && - isActionBarVisible.compareAndSet(false, true)) { - Utils.runOnMainThreadDelayed(() -> isActionBarVisible.compareAndSet(true, false), 500); + PlayerType playerType = PlayerType.getCurrent(); + if (playerType == PlayerType.WATCH_WHILE_MINIMIZED || playerType == PlayerType.WATCH_WHILE_PICTURE_IN_PICTURE) { + if (isActionBarVisible.compareAndSet(false, true)) { + Utils.runOnMainThreadDelayed(() -> isActionBarVisible.compareAndSet(true, false), 250); + } } } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java index 9aedaa8f28..416ef28f39 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/DescriptionComponentsFilter.java @@ -134,26 +134,17 @@ public final class DescriptionComponentsFilter extends Filter { } @Override - public boolean isFiltered(ContextInterface contextInterface, - String identifier, - String accessibility, - String path, - byte[] buffer, - StringFilterGroup matchedGroup, - FilterContentType contentType, - int contentIndex) { - // The description panel can be opened in both the regular player and Shorts. - // If the description panel is opened in a Shorts, PlayerType is 'HIDDEN', - // so 'PlayerType.getCurrent().isMaximizedOrFullscreen()' does not guarantee that the description panel is open. - // Instead, use the engagement id to check if the description panel is opened. - if (!EngagementPanel.isDescription()) { - return false; - } - - // PlayerType when the description panel is opened: NONE, HIDDEN, - // WATCH_WHILE_MAXIMIZED, WATCH_WHILE_FULLSCREEN, WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN. - PlayerType playerType = PlayerType.getCurrent(); - if (!playerType.isNoneOrHidden() && !playerType.isMaximizedOrFullscreen()) { + public boolean isFiltered(ContextInterface contextInterface, + String identifier, + String accessibility, + String path, + byte[] buffer, + StringFilterGroup matchedGroup, + FilterContentType contentType, + int contentIndex) { + // Immediately after the layout is refreshed, litho components are updated before the UI is drawn. + // In this case, EngagementPanel.isDescription() cannot be used, and isActionBarVisible.get() should be used. + if (!EngagementPanel.isDescription() && !(PlayerType.getCurrent().isMaximizedOrFullscreen() || isActionBarVisible.get() || ShortsPlayerState.isOpen())) { return false; } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java index 49722901e8..6ed28b9b0d 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/HorizontalShelvesFilter.java @@ -65,19 +65,16 @@ public final class HorizontalShelvesFilter extends Filter { ); } - private boolean hideShelves() { + private boolean hideShelves(ContextInterface contextInterface) { if (!Settings.HIDE_HORIZONTAL_SHELVES.get()) { return false; } - // Must check player type first, as search bar can be active behind the player. - if (PlayerType.getCurrent().isMaximizedOrFullscreen() || isActionBarVisible.get()) { - return false; - } - // Must check second, as search can be from any tab. - if (NavigationBar.isSearchBarActive()) { - return true; - } - return NavigationButton.getSelectedNavigationButton() != NavigationButton.LIBRARY; + return contextInterface.isHomeFeedOrRelatedVideo() + || PlayerType.getCurrent().isMaximizedOrFullscreen() + || isActionBarVisible.get() + || NavigationBar.isSearchBarActive() + || NavigationBar.isBackButtonVisible() + || NavigationButton.getSelectedNavigationButton() != NavigationButton.LIBRARY; } @Override @@ -95,15 +92,9 @@ public final class HorizontalShelvesFilter extends Filter { if (generalBuffers.check(buffer).isFiltered()) { return true; } - if (EngagementPanel.isDescription()) { - PlayerType playerType = PlayerType.getCurrent(); - // PlayerType when the description panel is opened: NONE, HIDDEN, - // WATCH_WHILE_MAXIMIZED, WATCH_WHILE_FULLSCREEN, WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN. - if (!playerType.isMaximizedOrFullscreen() && !playerType.isNoneOrHidden()) { - return false; - } - return descriptionBuffers.check(buffer).isFiltered(); + if (descriptionBuffers.check(buffer).isFiltered()) { + return EngagementPanel.isDescription() || PlayerType.getCurrent().isMaximizedOrFullscreen() || isActionBarVisible.get() || ShortsPlayerState.isOpen(); } - return hideShelves(); + return hideShelves(contextInterface); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java index ae243ad8af..55c1e0fb22 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/LayoutComponentsFilter.java @@ -76,7 +76,6 @@ public final class LayoutComponentsFilter extends Filter { private final StringFilterGroup chipBar; private final StringFilterGroup channelProfile; private final StringFilterGroupList channelProfileGroupList; - private final StringFilterGroupList communityPostStringFilterGroup; public LayoutComponentsFilter() { exceptions.addPatterns( @@ -142,16 +141,6 @@ public final class LayoutComponentsFilter extends Filter { "poll_post_responsive_root.e", "shared_post_root.e" ); - communityPostStringFilterGroup = new StringFilterGroupList(); - communityPostStringFilterGroup.addAll( - new StringFilterGroup( - null, - // home - "horizontalCollectionSwipeProtector=null", - // subscriptions - "heightConstraint=null" - ) - ); final var subscribersCommunityGuidelines = new StringFilterGroup( Settings.HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES, @@ -419,7 +408,7 @@ public final class LayoutComponentsFilter extends Filter { } if (matchedGroup == communityPosts) { - return communityPostStringFilterGroup.check(contextInterface.toString()).isFiltered(); + return contextInterface.isHomeFeedOrRelatedVideo() || contextInterface.isSubscriptionOrLibrary(); } if (exceptions.matches(path)) return false; // Exceptions are not filtered. diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java index 0da4d4edfe..94b69c6cae 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java @@ -56,6 +56,7 @@ public final class ShortsFilter extends Filter { private final StringFilterGroup shortsCompactFeedVideo; private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer; private final StringFilterGroup channelProfile; + private final ByteArrayFilterGroup channelProfileShelfHeader; private final StringFilterGroup autoDubbedLabel; private final StringFilterGroup subscribeButton; @@ -95,6 +96,11 @@ public final class ShortsFilter extends Filter { "shorts_pivot_item" ); + channelProfileShelfHeader = new ByteArrayFilterGroup( + Settings.HIDE_SHORTS_CHANNEL, + "Shorts" + ); + // Feed Shorts shelf header. // Use a different filter group for this pattern, as it requires an additional check after matching. shelfHeaderIdentifier = new StringFilterGroup( @@ -412,8 +418,12 @@ public final class ShortsFilter extends Filter { if (contentIndex != 0) { return false; } - } - if (matchedGroup == channelProfile) { + // Check ConversationContext to not hide shelf header in channel profile + // This value does not exist in the shelf header in the channel profile + if (!contextInterface.isHomeFeedOrRelatedVideo()) { + return false; + } + } else if (matchedGroup == channelProfile) { return true; } @@ -433,7 +443,15 @@ public final class ShortsFilter extends Filter { } if (matchedGroup == shortsCompactFeedVideo) { - return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(buffer).isFiltered(); + return shouldHideShortsFeedItems() + && shortsCompactFeedVideoBuffer.check(buffer).isFiltered() + // The litho path of the feed video is 'video_lockup_with_attachment.e'. + // It appears shortsCompactFeedVideoBuffer is used after 20 seconds during autoplay in the feed in YouTube 20.44.38. + // If the Shorts shelf is hidden on the Home feed, the video in the feed will be hidden after 20 seconds have passed since autoplay began in the feed. + // + // When a video is autoplaying in the feed, no new components are drawn on the screen. + // Therefore, filtering is skipped when the current PlayerType is [INLINE_MINIMAL]. + && PlayerType.getCurrent() != PlayerType.INLINE_MINIMAL; } if (matchedGroup == shelfHeaderPath) { @@ -442,6 +460,11 @@ public final class ShortsFilter extends Filter { if (contentIndex != 0) { return false; } + // Check ConversationContext to not hide shelf header in channel profile + // This value does not exist in the shelf header in the channel profile + if (!contextInterface.isHomeFeedOrRelatedVideo()) { + return channelProfileShelfHeader.check(buffer).isFiltered(); + } return shouldHideShortsFeedItems(); } @@ -456,7 +479,7 @@ public final class ShortsFilter extends Filter { } if (matchedGroup == useButtons) { - return path.contains("button.e") && useButtonsBuffer.check(buffer).isFiltered(); + return path.contains("|button.e") && useButtonsBuffer.check(buffer).isFiltered(); } if (matchedGroup == suggestedAction) { @@ -493,15 +516,6 @@ public final class ShortsFilter extends Filter { if (!hideHome && !hideSubscriptions && !hideSearch && !hideVideoDescription && !hideHistory) { return false; } - // The Litho path of the feed video is 'video_lockup_with_attachment.e'. - // It appears shortsCompactFeedVideoBuffer is used after 20 seconds during autoplay in the feed in YouTube 20.44.38. - // If the Shorts shelf is hidden on the Home feed, the video in the feed will be hidden after 20 seconds have passed since autoplay began in the feed. - // - // When a video is autoplaying in the feed, no new components are drawn on the screen. - // Therefore, filtering is skipped when the current PlayerType is INLINE_MINIMAL. - if (PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL) { - return false; - } if (hideHome && hideSubscriptions && hideSearch && hideVideoDescription && hideHistory) { return true; } From f063cf69bd665ab51933c711748d21268dadd2ca Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 21 Mar 2026 19:48:30 +0100 Subject: [PATCH 37/41] fix(YouTube): Advanced video quality menu does not work Co-authored-by: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> --- .../patches/youtube/video/quality/Fingerprints.kt | 5 +++++ .../patches/youtube/video/quality/VideoQualityPatch.kt | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt index 305b5d2e13..7951352312 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt @@ -26,6 +26,11 @@ import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.ClassDef +internal val BytecodePatchContext.newAdvancedQualityMenuStyleFlyoutMethodMatch by composingFirstMethod { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + instructions(45712556L()) +} + internal val BytecodePatchContext.currentVideoFormatToStringMethod by gettingFirstImmutableMethodDeclaratively( "currentVideoFormat=" ) { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt index 72e94e3a61..c97eb807cf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt @@ -4,7 +4,9 @@ import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.shared.misc.settings.preference.BasePreference import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting +import app.revanced.patches.youtube.misc.playservice.is_20_40_or_greater import app.revanced.patches.youtube.misc.settings.PreferenceScreen +import app.revanced.util.insertLiteralOverride /** * Video quality settings. Used to organize all speed related settings together. @@ -46,5 +48,13 @@ val videoQualityPatch = bytecodePatch( preferences = settingsMenuVideoQualityGroup, ), ) + + if (is_20_40_or_greater) { + // Flag breaks opening advanced quality menu. + // Alternatively can be fixed by using a delay when simulating the UI click. + newAdvancedQualityMenuStyleFlyoutMethodMatch.let { + it.method.insertLiteralOverride(it[0], false) + } + } } } From 1ebd990051a6fc53916b4bb57c9c6974948b7dfc Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 21 Mar 2026 19:52:43 +0100 Subject: [PATCH 38/41] feat: Add import from & export settings to a file Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../extension/shared/settings/Setting.java | 36 ++- .../AbstractPreferenceFragment.java | 244 +++++++++++++++++- .../preference/ImportExportPreference.java | 105 +------- .../sponsorblock/SponsorBlockSettings.java | 18 +- .../resources/addresources/values/strings.xml | 7 + 5 files changed, 290 insertions(+), 120 deletions(-) diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java index 53a980e3c2..e3cb9b5b78 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java @@ -2,7 +2,7 @@ package app.revanced.extension.shared.settings; import static app.revanced.extension.shared.StringRef.str; -import android.content.Context; +import android.app.Activity; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -122,18 +122,18 @@ public abstract class Setting { /** * Called after all settings have been imported. */ - void settingsImported(@Nullable Context context); + void settingsImported(@Nullable Activity context); /** * Called after all settings have been exported. */ - void settingsExported(@Nullable Context context); + void settingsExported(@Nullable Activity context); } private static final List importExportCallbacks = new ArrayList<>(); /** - * Adds a callback for {@link #importFromJSON(Context, String)} and {@link #exportToJson(Context)}. + * Adds a callback for {@link #importFromJSON(Activity, String)} and {@link #exportToJson(Activity)}. */ public static void addImportExportCallback(ImportExportCallback callback) { importExportCallbacks.add(Objects.requireNonNull(callback)); @@ -413,7 +413,7 @@ public abstract class Setting { json.put(importExportKey, value); } - public static String exportToJson(@Nullable Context alertDialogContext) { + public static String exportToJson(@Nullable Activity alertDialogContext) { try { JSONObject json = new JSONObject(); for (Setting setting : allLoadedSettingsSorted()) { @@ -439,11 +439,17 @@ public abstract class Setting { String export = json.toString(0); - // Remove the outer JSON braces to make the output more compact, - // and leave less chance of the user forgetting to copy it - return export.substring(2, export.length() - 2); + if (export.startsWith("{") && export.endsWith("}")) { + // Remove the outer JSON braces to make the output more compact, + // and leave less chance of the user forgetting to copy it + export = export.substring(1, export.length() - 1); + } + + export = export.replaceAll("^\\n+", "").replaceAll("\\n+$", ""); + + return export + ","; } catch (JSONException e) { - Logger.printException(() -> "Export failure", e); // should never happen + Logger.printException(() -> "Export failure", e); // Should never happen return ""; } } @@ -451,10 +457,16 @@ public abstract class Setting { /** * @return if any settings that require a reboot were changed. */ - public static boolean importFromJSON(Context alertDialogContext, String settingsJsonString) { + public static boolean importFromJSON(Activity alertDialogContext, String settingsJsonString) { try { - if (!settingsJsonString.matches("[\\s\\S]*\\{")) { - settingsJsonString = '{' + settingsJsonString + '}'; // Restore outer JSON braces + settingsJsonString = settingsJsonString.trim(); + + if (settingsJsonString.endsWith(",")) { + settingsJsonString = settingsJsonString.substring(0, settingsJsonString.length() - 1); + } + + if (!settingsJsonString.trim().startsWith("{")) { + settingsJsonString = "{\n" + settingsJsonString + "\n}"; // Restore outer JSON braces } JSONObject json = new JSONObject(settingsJsonString); diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java index a515471a00..5d8a6bd2dd 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java @@ -3,8 +3,10 @@ package app.revanced.extension.shared.settings.preference; import static app.revanced.extension.shared.StringRef.str; import android.annotation.SuppressLint; +import android.app.Activity; import android.app.Dialog; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.Preference; @@ -16,11 +18,18 @@ import android.preference.SwitchPreference; import android.preference.EditTextPreference; import android.preference.ListPreference; import android.util.Pair; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.Objects; +import java.util.Scanner; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.ResourceType; @@ -33,6 +42,9 @@ import app.revanced.extension.shared.ui.CustomDialog; @SuppressWarnings("deprecation") public abstract class AbstractPreferenceFragment extends PreferenceFragment { + @SuppressLint("StaticFieldLeak") + public static AbstractPreferenceFragment instance; + /** * Indicates that if a preference changes, * to apply the change from the Setting to the UI component. @@ -56,6 +68,12 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { @Nullable protected static CharSequence restartDialogTitle, restartDialogMessage, restartDialogButtonText, confirmDialogTitle; + private static final int READ_REQUEST_CODE = 42; + private static final int WRITE_REQUEST_CODE = 43; + private String existingSettings = ""; + + private EditText currentImportExportEditText; + private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { try { if (updatingPreference) { @@ -198,8 +216,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { return listPref.getValue().equals(defaultValueString); } - throw new IllegalStateException("Must override method to handle " - + "preference type: " + pref.getClass()); + throw new IllegalStateException("Must override method to handle preference type: " + pref.getClass()); } /** @@ -332,10 +349,230 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { dialogPair.first.show(); } + /** + * Import / Export Subroutines + */ + @NonNull + private Button createDialogButton(Context context, String text, int marginLeft, int marginRight, View.OnClickListener listener) { + int height = (int) android.util.TypedValue.applyDimension(android.util.TypedValue.COMPLEX_UNIT_DIP, 36f, context.getResources().getDisplayMetrics()); + int paddingHorizontal = (int) android.util.TypedValue.applyDimension(android.util.TypedValue.COMPLEX_UNIT_DIP, 16f, context.getResources().getDisplayMetrics()); + float radius = android.util.TypedValue.applyDimension(android.util.TypedValue.COMPLEX_UNIT_DIP, 20f, context.getResources().getDisplayMetrics()); + + Button btn = new Button(context, null, 0); + btn.setText(text); + btn.setAllCaps(false); + btn.setTextSize(14); + btn.setSingleLine(true); + btn.setEllipsize(android.text.TextUtils.TruncateAt.END); + btn.setGravity(android.view.Gravity.CENTER); + btn.setPadding(paddingHorizontal, 0, paddingHorizontal, 0); + btn.setTextColor(Utils.isDarkModeEnabled() ? android.graphics.Color.WHITE : android.graphics.Color.BLACK); + + android.graphics.drawable.GradientDrawable bg = new android.graphics.drawable.GradientDrawable(); + bg.setCornerRadius(radius); + bg.setColor(Utils.getCancelOrNeutralButtonBackgroundColor()); + btn.setBackground(bg); + + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, height, 1.0f); + params.setMargins(marginLeft, 0, marginRight, 0); + btn.setLayoutParams(params); + btn.setOnClickListener(listener); + + return btn; + } + public void showImportExportTextDialog() { + try { + Activity context = getActivity(); + // Must set text before showing dialog, + // otherwise text is non-selectable if this preference is later reopened. + existingSettings = Setting.exportToJson(context); + currentImportExportEditText = getEditText(context); + + // Create a custom dialog with the EditText. + Pair dialogPair = CustomDialog.create( + context, + str("revanced_pref_import_export_title"), // Title. + null, // No message (EditText replaces it). + currentImportExportEditText, // Pass the EditText. + str("revanced_settings_save"), // OK button text. + () -> importSettingsText(context, currentImportExportEditText.getText().toString()), // OK button action. + () -> {}, // Cancel button action (dismiss only). + str("revanced_settings_import_copy"), // Neutral button (Copy) text. + () -> Utils.setClipboard(currentImportExportEditText.getText().toString()), // Neutral button (Copy) action. Show the user the settings in JSON format. + true // Dismiss dialog when onNeutralClick. + ); + + LinearLayout fileButtonsContainer = getLinearLayout(context); + int margin = (int) android.util.TypedValue.applyDimension(android.util.TypedValue.COMPLEX_UNIT_DIP, 4f, context.getResources().getDisplayMetrics()); + + Button btnExport = createDialogButton(context, str("revanced_settings_export_file"), 0, margin, v -> exportActivity()); + Button btnImport = createDialogButton(context, str("revanced_settings_import_file"), margin, 0, v -> importActivity()); + + fileButtonsContainer.addView(btnExport); + fileButtonsContainer.addView(btnImport); + + dialogPair.second.addView(fileButtonsContainer, 2); + + dialogPair.first.setOnDismissListener(d -> currentImportExportEditText = null); + + // If there are no settings yet, then show the on-screen keyboard and bring focus to + // the edit text. This makes it easier to paste saved settings after a reinstallation. + dialogPair.first.setOnShowListener(dialogInterface -> { + if (existingSettings.isEmpty() && currentImportExportEditText != null) { + currentImportExportEditText.postDelayed(() -> { + if (currentImportExportEditText != null) { + currentImportExportEditText.requestFocus(); + android.view.inputmethod.InputMethodManager imm = (android.view.inputmethod.InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) imm.showSoftInput(currentImportExportEditText, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT); + } + }, 100); + } + }); + + // Show the dialog. + dialogPair.first.show(); + } catch (Exception ex) { + Logger.printException(() -> "showImportExportTextDialog failure", ex); + } + } + + @NonNull + private static LinearLayout getLinearLayout(Context context) { + LinearLayout fileButtonsContainer = new LinearLayout(context); + fileButtonsContainer.setOrientation(LinearLayout.HORIZONTAL); + LinearLayout.LayoutParams fbParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + + int marginTop = (int) android.util.TypedValue.applyDimension(android.util.TypedValue.COMPLEX_UNIT_DIP, 16f, context.getResources().getDisplayMetrics()); + fbParams.setMargins(0, marginTop, 0, 0); + fileButtonsContainer.setLayoutParams(fbParams); + return fileButtonsContainer; + } + + @NonNull + private EditText getEditText(Context context) { + EditText editText = new EditText(context); + editText.setText(existingSettings); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + editText.setAutofillHints((String) null); + } + editText.setInputType(android.text.InputType.TYPE_CLASS_TEXT | + android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | + android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE); + editText.setSingleLine(false); + editText.setTextSize(14); + return editText; + } + + public void exportActivity() { + try { + Setting.exportToJson(getActivity()); + + String formatDate = new java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.US).format(new java.util.Date()); + String fileName = "revanced_Settings_" + formatDate + ".txt"; + + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TITLE, fileName); + startActivityForResult(intent, WRITE_REQUEST_CODE); + } catch (Exception ex) { + Logger.printException(() -> "exportActivity failure", ex); + } + } + + public void importActivity() { + try { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + startActivityForResult(intent, READ_REQUEST_CODE); + } catch (Exception ex) { + Logger.printException(() -> "importActivity failure", ex); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == WRITE_REQUEST_CODE && resultCode == android.app.Activity.RESULT_OK && data != null) { + exportTextToFile(data.getData()); + } else if (requestCode == READ_REQUEST_CODE && resultCode == android.app.Activity.RESULT_OK && data != null) { + importTextFromFile(data.getData()); + } + } + + protected static void showLocalizedToast(String resourceKey, String fallbackMessage) { + if (ResourceUtils.getIdentifier(ResourceType.STRING, resourceKey) != 0) { + Utils.showToastLong(str(resourceKey)); + } else { + Utils.showToastLong(fallbackMessage); + } + } + + private void exportTextToFile(android.net.Uri uri) { + try { + OutputStream out = getContext().getContentResolver().openOutputStream(uri); + if (out != null) { + String textToExport = existingSettings; + if (currentImportExportEditText != null) { + textToExport = currentImportExportEditText.getText().toString(); + } + out.write(textToExport.getBytes(StandardCharsets.UTF_8)); + out.close(); + + showLocalizedToast("revanced_settings_export_file_success", "Settings exported successfully"); + } + } catch (Exception e) { + showLocalizedToast("revanced_settings_export_file_failed", "Failed to export settings"); + Logger.printException(() -> "exportTextToFile failure", e); + } + } + + @SuppressWarnings("CharsetObjectCanBeUsed") + private void importTextFromFile(android.net.Uri uri) { + try { + InputStream in = getContext().getContentResolver().openInputStream(uri); + if (in != null) { + Scanner scanner = new Scanner(in, StandardCharsets.UTF_8.name()).useDelimiter("\\A"); + String result = scanner.hasNext() ? scanner.next() : ""; + in.close(); + + if (currentImportExportEditText != null) { + currentImportExportEditText.setText(result); + showLocalizedToast("revanced_settings_import_file_success", "Settings imported successfully, tap Save to apply"); + } else { + importSettingsText(getContext(), result); + } + } + } catch (Exception e) { + showLocalizedToast("revanced_settings_import_file_failed", "Failed to import settings"); + Logger.printException(() -> "importTextFromFile failure", e); + } + } + + private void importSettingsText(Context context, String replacementSettings) { + try { + existingSettings = Setting.exportToJson(null); + if (replacementSettings.equals(existingSettings)) { + return; + } + settingImportInProgress = true; + final boolean rebootNeeded = Setting.importFromJSON(getActivity(), replacementSettings); + if (rebootNeeded) { + showRestartDialog(context); + } + } catch (Exception ex) { + Logger.printException(() -> "importSettingsText failure", ex); + } finally { + settingImportInProgress = false; + } + } + @SuppressLint("ResourceType") @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + instance = this; try { PreferenceManager preferenceManager = getPreferenceManager(); preferenceManager.setSharedPreferencesName(Setting.preferences.name); @@ -354,6 +591,9 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { @Override public void onDestroy() { + if (instance == this) { + instance = null; + } getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener); super.onDestroy(); } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ImportExportPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ImportExportPreference.java index 1044ba424e..c187acf608 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ImportExportPreference.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ImportExportPreference.java @@ -4,40 +4,13 @@ import static app.revanced.extension.shared.StringRef.str; import android.app.Dialog; import android.content.Context; -import android.os.Build; -import android.os.Bundle; -import android.preference.EditTextPreference; import android.preference.Preference; -import android.text.InputType; import android.util.AttributeSet; -import android.util.Pair; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.LinearLayout; import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.Setting; -import app.revanced.extension.shared.ui.CustomDialog; @SuppressWarnings({"unused", "deprecation"}) -public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener { - - private String existingSettings; - - private void init() { - setSelectable(true); - - EditText editText = getEditText(); - editText.setTextIsSelectable(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - editText.setAutofillHints((String) null); - } - editText.setInputType(editText.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - editText.setTextSize(14); - - setOnPreferenceClickListener(this); - } +public class ImportExportPreference extends Preference implements Preference.OnPreferenceClickListener { public ImportExportPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); @@ -56,78 +29,20 @@ public class ImportExportPreference extends EditTextPreference implements Prefer init(); } + private void init() { + setOnPreferenceClickListener(this); + } + @Override public boolean onPreferenceClick(Preference preference) { try { - // Must set text before showing dialog, - // otherwise text is non-selectable if this preference is later reopened. - existingSettings = Setting.exportToJson(getContext()); - getEditText().setText(existingSettings); + if (AbstractPreferenceFragment.instance != null) { + AbstractPreferenceFragment.instance.showImportExportTextDialog(); + } } catch (Exception ex) { - Logger.printException(() -> "showDialog failure", ex); + Logger.printException(() -> "onPreferenceClick failure", ex); } + return true; } - - @Override - protected void showDialog(Bundle state) { - try { - Context context = getContext(); - EditText editText = getEditText(); - - // Create a custom dialog with the EditText. - Pair dialogPair = CustomDialog.create( - context, - str("revanced_pref_import_export_title"), // Title. - null, // No message (EditText replaces it). - editText, // Pass the EditText. - str("revanced_settings_import"), // OK button text. - () -> importSettings(context, editText.getText().toString()), // OK button action. - () -> {}, // Cancel button action (dismiss only). - str("revanced_settings_import_copy"), // Neutral button (Copy) text. - () -> { - // Neutral button (Copy) action. Show the user the settings in JSON format. - Utils.setClipboard(editText.getText()); - }, - true // Dismiss dialog when onNeutralClick. - ); - - // If there are no settings yet, then show the on screen keyboard and bring focus to - // the edit text. This makes it easier to paste saved settings after a reinstall. - dialogPair.first.setOnShowListener(dialogInterface -> { - if (existingSettings.isEmpty()) { - editText.postDelayed(() -> { - editText.requestFocus(); - - InputMethodManager inputMethodManager = (InputMethodManager) - editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT); - }, 100); - } - }); - - // Show the dialog. - dialogPair.first.show(); - } catch (Exception ex) { - Logger.printException(() -> "showDialog failure", ex); - } - } - - private void importSettings(Context context, String replacementSettings) { - try { - if (replacementSettings.equals(existingSettings)) { - return; - } - AbstractPreferenceFragment.settingImportInProgress = true; - - final boolean rebootNeeded = Setting.importFromJSON(context, replacementSettings); - if (rebootNeeded) { - AbstractPreferenceFragment.showRestartDialog(context); - } - } catch (Exception ex) { - Logger.printException(() -> "importSettings failure", ex); - } finally { - AbstractPreferenceFragment.settingImportInProgress = false; - } - } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java index e3a4c31ad9..26d8afa715 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java @@ -2,8 +2,8 @@ package app.revanced.extension.youtube.sponsorblock; import static app.revanced.extension.shared.StringRef.str; +import android.app.Activity; import android.app.Dialog; -import android.content.Context; import android.util.Pair; import android.util.Patterns; import android.widget.LinearLayout; @@ -34,12 +34,12 @@ public class SponsorBlockSettings { public static final Setting.ImportExportCallback SB_IMPORT_EXPORT_CALLBACK = new Setting.ImportExportCallback() { @Override - public void settingsImported(@Nullable Context context) { + public void settingsImported(@Nullable Activity context) { SegmentCategory.loadAllCategoriesFromSettings(); SponsorBlockPreferenceGroup.settingsImported = true; } @Override - public void settingsExported(@Nullable Context context) { + public void settingsExported(@Nullable Activity context) { showExportWarningIfNeeded(context); } }; @@ -184,16 +184,16 @@ public class SponsorBlockSettings { /** * Export the categories using flatten JSON (no embedded dictionaries or arrays). */ - private static void showExportWarningIfNeeded(@Nullable Context dialogContext) { + private static void showExportWarningIfNeeded(@Nullable Activity activity) { Utils.verifyOnMainThread(); initialize(); // If user has a SponsorBlock user ID then show a warning. - if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateID() + if (activity != null && SponsorBlockSettings.userHasSBPrivateID() && !Settings.SB_HIDE_EXPORT_WARNING.get()) { // Create the custom dialog. Pair dialogPair = CustomDialog.create( - dialogContext, + activity, null, // No title. str("revanced_sb_settings_revanced_export_user_id_warning"), // Message. null, // No EditText. @@ -205,11 +205,7 @@ public class SponsorBlockSettings { true // Dismiss dialog when onNeutralClick. ); - // Set dialog as non-cancelable. - dialogPair.first.setCancelable(false); - - // Show the dialog. - dialogPair.first.show(); + Utils.showDialog(activity, dialogPair.first, false, null); } } diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index ce5c2dd5fc..becd1a11ef 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -95,6 +95,13 @@ To translate new languages or improve the existing translations, visit translate App language Import / Export Import / Export ReVanced settings + Import from file + Settings imported successfully, save to apply + Failed to import settings + Export to file + Settings exported successfully + Failed to export settings + You are using ReVanced Patches version <i>%s</i> Note From 0371f7164f765fdab45c56604f92422d0e4dbf54 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 21 Mar 2026 19:56:43 +0100 Subject: [PATCH 39/41] fix(Settings): Prevent duplicate dialogs on rapid preference clicks Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> --- .../AbstractPreferenceFragment.java | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java index 5d8a6bd2dd..7e47a5e8c0 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java @@ -9,19 +9,24 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; +import android.os.SystemClock; +import android.preference.EditTextPreference; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.preference.SwitchPreference; -import android.preference.EditTextPreference; -import android.preference.ListPreference; import android.util.Pair; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.ListView; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -42,6 +47,33 @@ import app.revanced.extension.shared.ui.CustomDialog; @SuppressWarnings("deprecation") public abstract class AbstractPreferenceFragment extends PreferenceFragment { + private static class DebouncedListView extends ListView { + private long lastClick; + + public DebouncedListView(Context context) { + super(context); + + setId(android.R.id.list); // Required so PreferenceFragment recognizes it. + + // Match the default layout params + setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + )); + } + + @Override + public boolean performItemClick(View view, int position, long id) { + final long now = SystemClock.elapsedRealtime(); + if (now - lastClick < 500) { + return true; // Ignore fast double click. + } + lastClick = now; + + return super.performItemClick(view, position, id); + } + } + @SuppressLint("StaticFieldLeak") public static AbstractPreferenceFragment instance; @@ -491,6 +523,11 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { } } + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return new DebouncedListView(getActivity()); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); @@ -502,7 +539,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { } protected static void showLocalizedToast(String resourceKey, String fallbackMessage) { - if (ResourceUtils.getIdentifier(ResourceType.STRING, resourceKey) != 0) { + if (Utils.getResourceIdentifier(ResourceType.STRING, resourceKey) != 0) { Utils.showToastLong(str(resourceKey)); } else { Utils.showToastLong(fallbackMessage); From 467a62f4acaeb6aa13bc78da0d7f4bcb074f2346 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 21 Mar 2026 19:58:24 +0100 Subject: [PATCH 40/41] fix(YouTube - Playback speed): Fix playback speed menu opening from the feed flyout menu when `Restore old playback speed menu` is off Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com> --- .../speed/CustomPlaybackSpeedPatch.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java index 2b3784bcc8..c72a967412 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java @@ -81,9 +81,9 @@ public class CustomPlaybackSpeedPatch { private static final float customPlaybackSpeedsMin, customPlaybackSpeedsMax; /** - * The last time the old playback menu was forcefully called. + * The last time the playback menu was forcefully called. */ - private static volatile long lastTimeOldPlaybackMenuInvoked; + private static volatile long lastTimePlaybackMenuInvoked; /** * Formats speeds to UI strings. @@ -238,6 +238,15 @@ public class CustomPlaybackSpeedPatch { return false; } + // This method is sometimes used multiple times. + // To prevent this, ignore method reuse within 1 second. + final long now = System.currentTimeMillis(); + if (now - lastTimePlaybackMenuInvoked < 1000) { + Logger.printDebug(() -> "Ignoring call to hideLithoMenuAndShowSpeedMenu"); + return true; + } + lastTimePlaybackMenuInvoked = now; + // Dismiss View [R.id.touch_outside] is the 1st ChildView of the 4th ParentView. // This only shows in phone layout. var touchInsidedView = parentView4th.getChildAt(0); @@ -261,16 +270,8 @@ public class CustomPlaybackSpeedPatch { } public static void showOldPlaybackSpeedMenu() { - // This method is sometimes used multiple times. - // To prevent this, ignore method reuse within 1 second. - final long now = System.currentTimeMillis(); - if (now - lastTimeOldPlaybackMenuInvoked < 1000) { - Logger.printDebug(() -> "Ignoring call to showOldPlaybackSpeedMenu"); - return; - } - lastTimeOldPlaybackMenuInvoked = now; - // Rest of the implementation added by patch. + Logger.printDebug(() -> "showOldPlaybackSpeedMenu"); } /** From 14a16efa0f3aa422c834e307ce6bda732749b353 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 21 Mar 2026 20:00:24 +0100 Subject: [PATCH 41/41] feat(YouTube): Add support for `20.45.36` Co-authored-by: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> --- .../java/app/revanced/extension/shared/spoof/ClientType.java | 2 +- .../app/revanced/patches/youtube/ad/general/HideAdsPatch.kt | 3 ++- .../app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt | 3 ++- .../youtube/interaction/copyvideourl/CopyVideoURLPatch.kt | 3 ++- .../interaction/dialog/RemoveViewerDiscretionDialogPatch.kt | 3 ++- .../doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt | 3 ++- .../interaction/doubletap/DisableChapterSkipDoubleTapPatch.kt | 3 ++- .../patches/youtube/interaction/downloads/DownloadsPatch.kt | 3 ++- .../interaction/hapticfeedback/DisableHapticFeedbackPatch.kt | 3 ++- .../patches/youtube/interaction/seekbar/SeekbarPatch.kt | 3 ++- .../youtube/interaction/swipecontrols/SwipeControlsPatch.kt | 3 ++- .../patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt | 3 ++- .../patches/youtube/layout/branding/CustomBrandingPatch.kt | 3 ++- .../youtube/layout/branding/header/ChangeHeaderPatch.kt | 3 ++- .../layout/buttons/action/HideVideoActionsButtonsPatch.kt | 3 ++- .../buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt | 3 ++- .../youtube/layout/buttons/navigation/NavigationBarPatch.kt | 3 ++- .../layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt | 3 ++- .../patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt | 3 ++- .../layout/hide/autoplaypreview/HideAutoplayPreviewPatch.kt | 3 ++- .../layout/hide/endscreencards/HideEndScreenCardsPatch.kt | 3 ++- .../HideEndScreenSuggestedVideoPatch.kt | 3 ++- .../fullscreenambientmode/DisableFullscreenAmbientModePatch.kt | 3 ++- .../youtube/layout/hide/general/HideLayoutComponentsPatch.kt | 3 ++- .../youtube/layout/hide/infocards/HideInfoCardsPatch.kt | 3 ++- .../layout/hide/player/flyoutmenu/HidePlayerFlyoutMenuPatch.kt | 3 ++- .../youtube/layout/hide/player/popup/PlayerPopupPanelsPatch.kt | 3 ++- .../hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt | 3 ++- .../hide/rollingnumber/DisableRollingNumberAnimationPatch.kt | 3 ++- .../youtube/layout/hide/shorts/HideShortsComponentsPatch.kt | 3 ++- .../layout/hide/signintotvpopup/DisableSignInToTVPopupPatch.kt | 3 ++- .../patches/youtube/layout/hide/time/HideTimestampPatch.kt | 3 ++- .../patches/youtube/layout/miniplayer/MiniplayerPatch.kt | 3 ++- .../youtube/layout/player/fullscreen/ExitFullscreenPatch.kt | 3 ++- .../layout/player/fullscreen/OpenVideosFullscreenPatch.kt | 3 ++- .../layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt | 3 ++- .../layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt | 3 ++- .../youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt | 3 ++- .../layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt | 3 ++- .../shortsresuming/DisableResumingShortsOnStartupPatch.kt | 3 ++- .../patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt | 3 ++- .../youtube/layout/spoofappversion/SpoofAppVersionPatch.kt | 3 ++- .../patches/youtube/layout/startpage/ChangeStartPagePatch.kt | 3 ++- .../app/revanced/patches/youtube/layout/theme/ThemePatch.kt | 3 ++- .../youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt | 3 ++- .../layout/thumbnails/BypassImageRegionRestrictionsPatch.kt | 3 ++- .../patches/youtube/misc/announcements/AnnouncementsPatch.kt | 3 ++- .../youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt | 3 ++- .../youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt | 3 ++- .../patches/youtube/misc/debugging/EnableDebuggingPatch.kt | 3 ++- .../misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt | 3 ++- .../misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt | 3 ++- .../revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt | 3 ++- .../patches/youtube/misc/links/BypassURLRedirectsPatch.kt | 3 ++- .../patches/youtube/misc/links/OpenLinksExternallyPatch.kt | 3 ++- .../revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt | 3 ++- .../patches/youtube/misc/privacy/SanitizeSharingLinksPatch.kt | 3 ++- .../patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt | 3 ++- .../kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt | 1 - .../patches/youtube/video/audio/ForceOriginalAudioPatch.kt | 3 ++- .../patches/youtube/video/codecs/DisableVideoCodecsPatch.kt | 3 ++- .../patches/youtube/video/quality/VideoQualityPatch.kt | 3 ++- .../revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt | 3 ++- 63 files changed, 123 insertions(+), 63 deletions(-) diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java index 9abd430719..837a5b9066 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java @@ -28,7 +28,7 @@ public enum ClientType { Build.VERSION.RELEASE, String.valueOf(Build.VERSION.SDK_INT), Build.ID, - "20.44.38", + "20.45.36", // This client has been used by most open-source YouTube stream extraction tools since 2024, including NewPipe Extractor, SmartTube, and Grayjay. // This client can log in, but if an access token is used in the request, GVS can more easily identify the request as coming from ReVanced. // This means that the GVS server can strengthen its validation of the ANDROID_REEL client. diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt index 81e546ddfc..67d9bb79d4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt @@ -97,7 +97,8 @@ val hideAdsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt index 056faf82e9..2b3501bb23 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt @@ -35,7 +35,8 @@ val videoAdsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoURLPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoURLPatch.kt index 9369cfb03b..59ab4c5102 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoURLPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/copyvideourl/CopyVideoURLPatch.kt @@ -59,7 +59,8 @@ val copyVideoURLPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt index 04ef01dbdb..fa0e381d75 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/dialog/RemoveViewerDiscretionDialogPatch.kt @@ -46,7 +46,8 @@ val removeViewerDiscretionDialogPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt index 19c767c9ed..7543d21b87 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/AddMoreDoubleTapToSeekLengthOptionsPatch.kt @@ -23,7 +23,8 @@ val addMoreDoubleTapToSeekLengthOptionsPatch = resourcePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ) ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/DisableChapterSkipDoubleTapPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/DisableChapterSkipDoubleTapPatch.kt index 27836e47a5..ee172a3a4c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/DisableChapterSkipDoubleTapPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/doubletap/DisableChapterSkipDoubleTapPatch.kt @@ -38,7 +38,8 @@ val disableDoubleTapActionsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt index 3199426ac5..7f4d6e3b75 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt @@ -81,7 +81,8 @@ val downloadsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/hapticfeedback/DisableHapticFeedbackPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/hapticfeedback/DisableHapticFeedbackPatch.kt index 73f198c4a7..1a9bcbe4f5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/hapticfeedback/DisableHapticFeedbackPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/hapticfeedback/DisableHapticFeedbackPatch.kt @@ -59,7 +59,8 @@ val disableHapticFeedbackPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/SeekbarPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/SeekbarPatch.kt index 9882ec2716..6b7e48b2e9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/SeekbarPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/SeekbarPatch.kt @@ -24,7 +24,8 @@ val seekbarPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsPatch.kt index 7e85047d9b..94abd157e9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/SwipeControlsPatch.kt @@ -110,7 +110,8 @@ val swipeControlsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt index da94bc10e0..f3f2dde1e3 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/AutoCaptionsPatch.kt @@ -32,7 +32,8 @@ val disableAutoCaptionsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt index 2b9a8488b0..8c8ce3bca5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt @@ -32,7 +32,8 @@ val customBrandingPatch = baseCustomBrandingPatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt index 39206b8252..ed60e1cbeb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt @@ -87,7 +87,8 @@ val changeHeaderPatch = changeHeaderPatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ) ), variants = variants, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideVideoActionsButtonsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideVideoActionsButtonsPatch.kt index 7a68945b08..44ed0292a8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideVideoActionsButtonsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideVideoActionsButtonsPatch.kt @@ -34,7 +34,8 @@ val hideVideoActionButtonsPatch = resourcePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt index 5304f2ed08..da3be27a51 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt @@ -70,7 +70,8 @@ val overrideOpenInYouTubeMusicButtonPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationBarPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationBarPatch.kt index 50c204a593..3875c9f45f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationBarPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/navigation/NavigationBarPatch.kt @@ -61,7 +61,8 @@ val navigationBarPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt index 6d2adc6567..c9b2c3f62f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt @@ -44,7 +44,8 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt index 042fa97ef9..18d63174e8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt @@ -42,7 +42,8 @@ val changeFormFactorPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/autoplaypreview/HideAutoplayPreviewPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/autoplaypreview/HideAutoplayPreviewPatch.kt index e28d5f2743..be78bf46f8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/autoplaypreview/HideAutoplayPreviewPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/autoplaypreview/HideAutoplayPreviewPatch.kt @@ -40,7 +40,8 @@ val hideAutoplayPreviewPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ) ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndScreenCardsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndScreenCardsPatch.kt index ebf432492c..a82b5ca69d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndScreenCardsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreencards/HideEndScreenCardsPatch.kt @@ -69,7 +69,8 @@ val hideEndScreenCardsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt index 1eac8158e9..f84395c95e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/endscreensuggestedvideo/HideEndScreenSuggestedVideoPatch.kt @@ -36,7 +36,8 @@ val hideEndScreenSuggestedVideoPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt index b044efe7fe..1d889d2846 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/fullscreenambientmode/DisableFullscreenAmbientModePatch.kt @@ -36,7 +36,8 @@ val disableFullscreenAmbientModePatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt index 83de3bddaf..a230ee663c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt @@ -106,7 +106,8 @@ val hideLayoutComponentsPatch = hideLayoutComponentsPatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ), ) { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt index 51fb2ff7a1..2fe0360b29 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt @@ -53,7 +53,8 @@ val hideInfoCardsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenu/HidePlayerFlyoutMenuPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenu/HidePlayerFlyoutMenuPatch.kt index 33d41155e4..a4aa3d97b5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenu/HidePlayerFlyoutMenuPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenu/HidePlayerFlyoutMenuPatch.kt @@ -31,7 +31,8 @@ val hidePlayerFlyoutMenuItemsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/popup/PlayerPopupPanelsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/popup/PlayerPopupPanelsPatch.kt index c6d8adef9d..4ad5cfe88a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/popup/PlayerPopupPanelsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/popup/PlayerPopupPanelsPatch.kt @@ -32,7 +32,8 @@ val disablePlayerPopupPanelsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt index 297c004d15..b4c45e4440 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatch.kt @@ -36,7 +36,8 @@ val hideRelatedVideoOverlayPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt index 48d418d23d..7cff92a08f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatch.kt @@ -36,7 +36,8 @@ val disableRollingNumberAnimationsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt index df4aaa702c..4cea676218 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt @@ -167,7 +167,8 @@ val hideShortsComponentsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/signintotvpopup/DisableSignInToTVPopupPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/signintotvpopup/DisableSignInToTVPopupPatch.kt index 69ff48c876..acdcd59efd 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/signintotvpopup/DisableSignInToTVPopupPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/signintotvpopup/DisableSignInToTVPopupPatch.kt @@ -33,7 +33,8 @@ val disableSignInToTVPopupPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt index 75febb8e91..c4936e8838 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/time/HideTimestampPatch.kt @@ -31,7 +31,8 @@ val hideTimestampPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt index 2e29124ba5..efef43141a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt @@ -83,7 +83,8 @@ val miniplayerPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt index 27345b989c..c88afa172e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt @@ -28,7 +28,8 @@ val exitFullscreenPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenPatch.kt index dbc4094354..d5b65c66df 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenPatch.kt @@ -28,7 +28,8 @@ val openVideosFullscreenPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt index dafe7b87ab..06196619a4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatch.kt @@ -34,7 +34,8 @@ val customPlayerOverlayOpacityPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt index 3624a07c33..db342f440b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt @@ -68,7 +68,8 @@ val returnYouTubeDislikePatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt index 6886b72e10..55cf4af7ed 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt @@ -57,7 +57,8 @@ val shortsAutoplayPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt index febc7dce74..d7b658eaf1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt @@ -58,7 +58,8 @@ val openShortsInRegularPlayerPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsresuming/DisableResumingShortsOnStartupPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsresuming/DisableResumingShortsOnStartupPatch.kt index 2c759c98ec..fd44fbe1f7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsresuming/DisableResumingShortsOnStartupPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsresuming/DisableResumingShortsOnStartupPatch.kt @@ -44,7 +44,8 @@ val disableResumingShortsOnStartupPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt index 2ced4b5037..1c76ff702a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt @@ -135,7 +135,8 @@ val sponsorBlockPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt index 991001d26b..bf94d54d3c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt @@ -43,7 +43,8 @@ val spoofAppVersionPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt index 95d212df19..f8cf9a5a29 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/startpage/ChangeStartPagePatch.kt @@ -37,7 +37,8 @@ val changeStartPagePatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt index 8d5026b17d..deea5d78c0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt @@ -199,7 +199,8 @@ val themePatch = baseThemePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt index 91346f97a0..8701cd3a92 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt @@ -40,7 +40,8 @@ val alternativeThumbnailsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictionsPatch.kt index 540639de71..6049823c39 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/BypassImageRegionRestrictionsPatch.kt @@ -34,7 +34,8 @@ val bypassImageRegionRestrictionsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt index 5ca10fedfd..239b43df05 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt @@ -30,7 +30,8 @@ val announcementsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt index 94ce25eed2..30bff80d6e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt @@ -33,7 +33,8 @@ val pauseOnAudioInterruptPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt index d6afb4b106..dfeffaa518 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt @@ -51,7 +51,8 @@ val removeBackgroundPlaybackRestrictionsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt index f7d68184e5..f3e3829b27 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt @@ -25,7 +25,8 @@ val enableDebuggingPatch = enableDebuggingPatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ) ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt index 82b6eef219..6c11fab308 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDeviceDimensionsPatch.kt @@ -33,7 +33,8 @@ val spoofDeviceDimensionsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt index f22f18eb7b..e446d4bee7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/dns/CheckWatchHistoryDomainNameResolutionPatch.kt @@ -19,7 +19,8 @@ val checkWatchHistoryDomainNameResolutionPatch = checkWatchHistoryDomainNameReso "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt index 10f20cc388..ff5f12ac4c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt @@ -42,7 +42,8 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt index 0baab7ecd7..46d046940d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/BypassURLRedirectsPatch.kt @@ -36,7 +36,8 @@ val bypassURLRedirectsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt index fb753a1147..e251a08f3c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/links/OpenLinksExternallyPatch.kt @@ -48,7 +48,8 @@ val openLinksExternallyPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt index cef3477289..5fa1047e0d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt @@ -37,7 +37,8 @@ val loopVideoPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/SanitizeSharingLinksPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/SanitizeSharingLinksPatch.kt index 393f396506..92b898b6ef 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/SanitizeSharingLinksPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/SanitizeSharingLinksPatch.kt @@ -21,7 +21,8 @@ val sanitizeSharingLinksPatch = sanitizeSharingLinksPatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ) ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt index 0f8511e4a5..2165a770f1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt @@ -39,7 +39,8 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt index 05edd856cd..a228d6deb2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt @@ -48,7 +48,6 @@ internal val BytecodePatchContext.backgroundPlaybackManagerShortsMethod by getti } internal fun BytecodePatchContext.getEngagementPanelControllerMethodMatch() = firstMethodComposite { - accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL) returnType("L") parameterTypes("L", "L", "Z", "Z") instructions( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt index 33895724fc..131b702901 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt @@ -26,7 +26,8 @@ val forceOriginalAudioPatch = forceOriginalAudioPatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) }, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/codecs/DisableVideoCodecsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/codecs/DisableVideoCodecsPatch.kt index b028b393f2..89334fa27d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/codecs/DisableVideoCodecsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/codecs/DisableVideoCodecsPatch.kt @@ -62,7 +62,8 @@ val disableVideoCodecsPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt index c97eb807cf..67f3c427cf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt @@ -33,7 +33,8 @@ val videoQualityPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt index c0faf98419..dcdf33c858 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt @@ -34,7 +34,8 @@ val playbackSpeedPatch = bytecodePatch( "20.31.42", "20.37.48", "20.40.45", - "20.44.38" + "20.44.38", + "20.45.36" ) )