diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OpenShortsInRegularPlayerPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OpenShortsInRegularPlayerPatch.java new file mode 100644 index 0000000000..6d5fbd9cd6 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OpenShortsInRegularPlayerPatch.java @@ -0,0 +1,94 @@ +package app.revanced.extension.youtube.patches; + +import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; + +import java.lang.ref.WeakReference; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.youtube.settings.Settings; + +@SuppressWarnings("unused") +public class OpenShortsInRegularPlayerPatch { + + public enum ShortsPlayerType { + SHORTS_PLAYER, + REGULAR_PLAYER, + REGULAR_PLAYER_FULLSCREEN + } + + static { + if (!VersionCheckPatch.IS_19_46_OR_GREATER + && Settings.SHORTS_PLAYER_TYPE.get() == ShortsPlayerType.REGULAR_PLAYER_FULLSCREEN) { + // User imported newer settings to an older app target. + Logger.printInfo(() -> "Resetting " + Settings.SHORTS_PLAYER_TYPE); + Settings.SHORTS_PLAYER_TYPE.resetToDefault(); + } + } + + private static WeakReference mainActivityRef = new WeakReference<>(null); + + /** + * Injection point. + */ + public static void setMainActivity(Activity activity) { + mainActivityRef = new WeakReference<>(activity); + } + + /** + * Injection point. + */ + public static boolean openShort(String videoID) { + try { + ShortsPlayerType type = Settings.SHORTS_PLAYER_TYPE.get(); + if (type == ShortsPlayerType.SHORTS_PLAYER) { + return false; // Default unpatched behavior. + } + + if (videoID.isEmpty()) { + // Shorts was opened using launcher app shortcut. + // + // This check will not detect if the Shorts app shortcut is used + // while the app is running in the background (instead the regular player is opened). + // To detect that the hooked method map parameter can be checked + // if integer key 'com.google.android.apps.youtube.app.endpoint.flags' + // has bitmask 16 set. + // + // This use case seems unlikely if the user has the Shorts + // set to open in the regular player, so it's ignored as + // checking the map makes the patch more complicated. + Logger.printDebug(() -> "Ignoring Short with no videoId"); + return false; + } + + if (NavigationButton.getSelectedNavigationButton() == NavigationButton.SHORTS) { + return false; // Always use Shorts player for the Shorts nav button. + } + + final boolean forceFullScreen = (type == ShortsPlayerType.REGULAR_PLAYER_FULLSCREEN); + OpenVideosFullscreenHookPatch.setOpenNextVideoFullscreen(forceFullScreen); + + // Can use the application context and add intent flags of + // FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TOP + // But the activity context seems to fix random app crashes + // if Shorts urls are opened outside the app. + var context = mainActivityRef.get(); + + Intent videoPlayerIntent = new Intent( + Intent.ACTION_VIEW, + Uri.parse("https://youtube.com/watch?v=" + videoID) + ); + videoPlayerIntent.setPackage(context.getPackageName()); + + context.startActivity(videoPlayerIntent); + return true; + } catch (Exception ex) { + OpenVideosFullscreenHookPatch.setOpenNextVideoFullscreen(null); + Logger.printException(() -> "openShort failure", ex); + return false; + } + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OpenVideosFullscreen.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OpenVideosFullscreen.java deleted file mode 100644 index a15d7f6414..0000000000 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OpenVideosFullscreen.java +++ /dev/null @@ -1,14 +0,0 @@ -package app.revanced.extension.youtube.patches; - -import app.revanced.extension.youtube.settings.Settings; - -@SuppressWarnings("unused") -public class OpenVideosFullscreen { - - /** - * Injection point. - */ - public static boolean openVideoFullscreenPortrait(boolean original) { - return Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get(); - } -} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OpenVideosFullscreenHookPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OpenVideosFullscreenHookPatch.java new file mode 100644 index 0000000000..62f9bbff9c --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OpenVideosFullscreenHookPatch.java @@ -0,0 +1,41 @@ +package app.revanced.extension.youtube.patches; + +import androidx.annotation.Nullable; + +import app.revanced.extension.youtube.settings.Settings; + +@SuppressWarnings("unused") +public class OpenVideosFullscreenHookPatch { + + @Nullable + private static volatile Boolean openNextVideoFullscreen; + + public static void setOpenNextVideoFullscreen(@Nullable Boolean forceFullScreen) { + openNextVideoFullscreen = forceFullScreen; + } + + /** + * Changed during patching since this class is also + * used by {@link OpenVideosFullscreenHookPatch}. + */ + private static boolean isFullScreenPatchIncluded() { + return false; + } + + /** + * Injection point. + */ + public static boolean openVideoFullscreenPortrait(boolean original) { + Boolean openFullscreen = openNextVideoFullscreen; + if (openFullscreen != null) { + openNextVideoFullscreen = null; + return openFullscreen; + } + + if (!isFullScreenPatchIncluded()) { + return false; + } + + return Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get(); + } +} 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 74f082bf4c..c825b88d3d 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 @@ -9,4 +9,5 @@ public class VersionCheckPatch { public static final boolean IS_19_26_OR_GREATER = Utils.getAppVersionName().compareTo("19.26.00") >= 0; public static final boolean IS_19_29_OR_GREATER = Utils.getAppVersionName().compareTo("19.29.00") >= 0; public static final boolean IS_19_34_OR_GREATER = Utils.getAppVersionName().compareTo("19.34.00") >= 0; + public static final boolean IS_19_46_OR_GREATER = Utils.getAppVersionName().compareTo("19.46.00") >= 0; } 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 0c0a06721f..92b9ba8432 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 @@ -16,6 +16,7 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_2; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_3; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_4; +import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType; import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability; import static app.revanced.extension.youtube.patches.VersionCheckPatch.IS_19_17_OR_GREATER; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE; @@ -224,6 +225,7 @@ public class Settings extends BaseSettings { // Shorts public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", 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_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE); public static final BooleanSetting HIDE_SHORTS_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_shorts_comments_button", FALSE); public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE); diff --git a/patches/api/patches.api b/patches/api/patches.api index c76a2d38fb..25d3d50601 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -1177,6 +1177,10 @@ public final class app/revanced/patches/youtube/layout/player/background/PlayerC } public final class app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenKt { + public static final fun getOpenVideosFullscreen ()Lapp/revanced/patcher/patch/BytecodePatch; +} + +public final class app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenPatchKt { public static final fun getOpenVideosFullscreenPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } @@ -1210,6 +1214,10 @@ public final class app/revanced/patches/youtube/layout/shortsautoplay/ShortsAuto public static final fun getShortsAutoplayPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatchKt { + public static final fun getOpenShortsInRegularPlayerPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatchKt { public static final fun getSponsorBlockPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch.kt index 3ede5d48af..bd5a80f707 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch.kt @@ -2,7 +2,7 @@ package app.revanced.patches.shared.misc.checks import android.os.Build.* import app.revanced.patcher.Fingerprint -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue @@ -82,7 +82,7 @@ fun checkEnvironmentPatch( } } - fun invokeCheck() = mainActivityOnCreateFingerprint.method.addInstructions( + fun invokeCheck() = mainActivityOnCreateFingerprint.method.addInstruction( 0, "invoke-static/range { p0 .. p0 },$EXTENSION_CLASS_DESCRIPTOR->check(Landroid/app/Activity;)V", ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/Fingerprints.kt index d188d8442b..096ed695da 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/Fingerprints.kt @@ -14,3 +14,15 @@ internal val openVideosFullscreenPortraitFingerprint = fingerprint { OPEN_VIDEOS_FULLSCREEN_PORTRAIT_FEATURE_FLAG } } + +/** + * Used to enable opening regular videos fullscreen. + */ +internal val openVideosFullscreenHookPatchExtensionFingerprint = fingerprint { + accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC) + returns("Z") + parameters() + custom { methodDef, classDef -> + methodDef.name == "isFullScreenPatchIncluded" && classDef.type == EXTENSION_CLASS_DESCRIPTOR + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreen.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreen.kt index 621381ae5c..89311b08c0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreen.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreen.kt @@ -1,46 +1,9 @@ package app.revanced.patches.youtube.layout.player.fullscreen 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.extension.sharedExtensionPatch -import app.revanced.patches.youtube.misc.settings.PreferenceScreen -import app.revanced.patches.youtube.misc.settings.settingsPatch -import app.revanced.util.insertFeatureFlagBooleanOverride - -private const val EXTENSION_CLASS_DESCRIPTOR = - "Lapp/revanced/extension/youtube/patches/OpenVideosFullscreen;" @Suppress("unused") -val openVideosFullscreenPatch = bytecodePatch( - name = "Open videos fullscreen", - description = "Adds an option to open videos in full screen portrait mode.", -) { - dependsOn( - sharedExtensionPatch, - settingsPatch, - addResourcesPatch, - ) - - compatibleWith( - "com.google.android.youtube"( - "19.46.42", - ) - ) - - execute { - openVideosFullscreenPortraitFingerprint.method.insertFeatureFlagBooleanOverride( - OPEN_VIDEOS_FULLSCREEN_PORTRAIT_FEATURE_FLAG, - "$EXTENSION_CLASS_DESCRIPTOR->openVideoFullscreenPortrait(Z)Z" - ) - - // Add resources and setting last, in case the user force patches an old incompatible version. - - addResources("youtube", "layout.player.fullscreen.openVideosFullscreen") - - PreferenceScreen.PLAYER.addPreferences( - SwitchPreference("revanced_open_videos_fullscreen_portrait") - ) - } -} +@Deprecated("Renamed to openVideosFullscreenPatch", ReplaceWith("openVideosFullscreenPatch")) +val openVideosFullscreen = bytecodePatch{ + dependsOn(openVideosFullscreenPatch) +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenHookPatch.kt new file mode 100644 index 0000000000..24896be997 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenHookPatch.kt @@ -0,0 +1,32 @@ +package app.revanced.patches.youtube.layout.player.fullscreen + +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.youtube.layout.shortsplayer.openShortsInRegularPlayerPatch +import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.patches.youtube.misc.playservice.is_19_46_or_greater +import app.revanced.patches.youtube.misc.playservice.versionCheckPatch +import app.revanced.util.insertFeatureFlagBooleanOverride + +internal const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/OpenVideosFullscreenHookPatch;" + +/** + * Used by both [openVideosFullscreenPatch] and [openShortsInRegularPlayerPatch]. + */ +internal val openVideosFullscreenHookPatch = bytecodePatch { + dependsOn( + sharedExtensionPatch, + versionCheckPatch + ) + + execute { + if (!is_19_46_or_greater) { + return@execute + } + + openVideosFullscreenPortraitFingerprint.method.insertFeatureFlagBooleanOverride( + OPEN_VIDEOS_FULLSCREEN_PORTRAIT_FEATURE_FLAG, + "$EXTENSION_CLASS_DESCRIPTOR->openVideoFullscreenPortrait(Z)Z" + ) + } +} 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 new file mode 100644 index 0000000000..a196f65e8b --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenPatch.kt @@ -0,0 +1,46 @@ +package app.revanced.patches.youtube.layout.player.fullscreen + +import app.revanced.patcher.patch.PatchException +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.playservice.is_19_46_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.returnEarly + +@Suppress("unused") +val openVideosFullscreenPatch = bytecodePatch( + name = "Open videos fullscreen", + description = "Adds an option to open videos in full screen portrait mode.", +) { + dependsOn( + openVideosFullscreenHookPatch, + settingsPatch, + addResourcesPatch, + versionCheckPatch + ) + + compatibleWith( + "com.google.android.youtube"( + "19.46.42", + ) + ) + + execute { + if (!is_19_46_or_greater) { + throw PatchException("'Open videos fullscreen' requires 19.46.42 or greater") + } + + addResources("youtube", "layout.player.fullscreen.openVideosFullscreen") + + PreferenceScreen.PLAYER.addPreferences( + SwitchPreference("revanced_open_videos_fullscreen_portrait") + ) + + // Enable the logic for the user Setting to open regular videos fullscreen. + openVideosFullscreenHookPatchExtensionFingerprint.method.returnEarly(true) + } +} \ No newline at end of file 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 d667729e91..06a66a7bfb 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 @@ -1,5 +1,6 @@ package app.revanced.patches.youtube.layout.shortsautoplay +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch @@ -56,7 +57,7 @@ val shortsAutoplayPatch = bytecodePatch( } // Main activity is used to check if app is in pip mode. - mainActivityOnCreateFingerprint.method.addInstructions( + mainActivityOnCreateFingerprint.method.addInstruction( 1, "invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" + "setMainActivity(Landroid/app/Activity;)V", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/Fingerprints.kt new file mode 100644 index 0000000000..961ddef023 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/Fingerprints.kt @@ -0,0 +1,56 @@ +package app.revanced.patches.youtube.layout.shortsplayer + +import app.revanced.patcher.fingerprint +import app.revanced.util.literal +import com.android.tools.smali.dexlib2.AccessFlags + +/** + * Purpose of this method is not clear, and it's only used to identify + * the obfuscated name of the videoId() method in PlaybackStartDescriptor. + */ +internal val playbackStartFeatureFlagFingerprint = fingerprint { + returns("Z") + parameters( + "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;", + ) + literal { + 45380134L + } +} + +// Pre 19.25 +internal val shortsPlaybackIntentLegacyFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returns("V") + parameters( + "L", + "Ljava/util/Map;", + "J", + "Ljava/lang/String;", + "Z", + "Ljava/util/Map;" + ) + strings( + // None of these strings are unique. + "com.google.android.apps.youtube.app.endpoint.flags", + "ReelWatchFragmentArgs", + "reels_fragment_descriptor" + ) +} + +internal val shortsPlaybackIntentFingerprint = fingerprint { + accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL) + returns("V") + parameters( + "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;", + "Ljava/util/Map;", + "J", + "Ljava/lang/String;" + ) + strings( + // None of these strings are unique. + "com.google.android.apps.youtube.app.endpoint.flags", + "ReelWatchFragmentArgs", + "reels_fragment_descriptor" + ) +} \ No newline at end of file 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 new file mode 100644 index 0000000000..5041b87e7f --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt @@ -0,0 +1,131 @@ +package app.revanced.patches.youtube.layout.shortsplayer + +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.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.ListPreference +import app.revanced.patches.youtube.layout.player.fullscreen.openVideosFullscreenHookPatch +import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch +import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater +import app.revanced.patches.youtube.misc.playservice.is_19_46_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.patches.youtube.shared.mainActivityOnCreateFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/OpenShortsInRegularPlayerPatch;" + +@Suppress("unused") +val openShortsInRegularPlayerPatch = bytecodePatch( + name = "Open Shorts in regular player", + description = "Adds options to open Shorts in the regular video player.", +) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + openVideosFullscreenHookPatch, + navigationBarHookPatch, + versionCheckPatch + ) + + compatibleWith( + "com.google.android.youtube"( + "18.38.44", + "18.49.37", + "19.16.39", + "19.25.37", + "19.34.42", + "19.43.41", + "19.45.38", + "19.46.42", + ), + ) + + execute { + addResources("youtube", "layout.shortsplayer.shortsPlayerTypePatch") + + PreferenceScreen.SHORTS.addPreferences( + if (is_19_46_or_greater) { + ListPreference( + key = "revanced_shorts_player_type", + summaryKey = null, + ) + } else { + ListPreference( + key = "revanced_shorts_player_type", + summaryKey = null, + entriesKey = "revanced_shorts_player_type_legacy_entries", + entryValuesKey = "revanced_shorts_player_type_legacy_entry_values" + ) + } + ) + + // Activity is used as the context to launch an Intent. + mainActivityOnCreateFingerprint.method.addInstruction( + 1, + "invoke-static/range { p0 .. p0 }, ${EXTENSION_CLASS_DESCRIPTOR}->" + + "setMainActivity(Landroid/app/Activity;)V", + ) + + // Find the obfuscated method name for PlaybackStartDescriptor.videoId() + val playbackStartVideoIdMethodName = playbackStartFeatureFlagFingerprint.method.let { + val stringMethodIndex = it.indexOfFirstInstructionOrThrow { + val reference = getReference() + reference?.definingClass == "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;" + && reference.returnType == "Ljava/lang/String;" + } + + navigate(it).to(stringMethodIndex).stop().name + } + + fun extensionInstructions(playbackStartRegister: Int, freeRegister: Int) = + """ + invoke-virtual { v$playbackStartRegister }, Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;->$playbackStartVideoIdMethodName()Ljava/lang/String; + move-result-object v$freeRegister + invoke-static { v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->openShort(Ljava/lang/String;)Z + move-result v$freeRegister + if-eqz v$freeRegister, :disabled + return-void + + :disabled + nop + """ + + if (!is_19_25_or_greater) { + shortsPlaybackIntentLegacyFingerprint.method.apply { + val index = indexOfFirstInstructionOrThrow { + getReference()?.returnType == + "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;" + } + val freeRegister = getInstruction(index).registerC + val playbackStartRegister = getInstruction(index + 1).registerA + + addInstructionsWithLabels( + index + 2, + extensionInstructions(playbackStartRegister, freeRegister) + ) + } + + return@execute + } + + shortsPlaybackIntentFingerprint.method.addInstructionsWithLabels( + 0, + """ + move-object/from16 v0, p1 + ${extensionInstructions(0, 1)} + """ + ) + } +} 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 f318d838a1..e0d3a44f62 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 @@ -1,6 +1,6 @@ package app.revanced.patches.youtube.misc.announcements -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch @@ -41,7 +41,7 @@ val announcementsPatch = bytecodePatch( SwitchPreference("revanced_announcements"), ) - mainActivityOnCreateFingerprint.method.addInstructions( + mainActivityOnCreateFingerprint.method.addInstruction( // Insert index must be greater than the insert index used by GmsCoreSupport, // as both patch the same method and GmsCore check should be first. 1, 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 6437b12991..827c669033 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 @@ -1,6 +1,6 @@ package app.revanced.patches.youtube.misc.dns -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch @@ -31,7 +31,7 @@ val checkWatchHistoryDomainNameResolutionPatch = bytecodePatch( execute { addResources("youtube", "misc.dns.checkWatchHistoryDomainNameResolutionPatch") - mainActivityOnCreateFingerprint.method.addInstructions( + mainActivityOnCreateFingerprint.method.addInstruction( // FIXME: Insert index must be greater than the insert index used by GmsCoreSupport, // as both patch the same method and GmsCoreSupport check should be first, // but the patch does not depend on GmsCoreSupport, so it should not be possible to enforce this diff --git a/patches/src/main/resources/addresources/values/arrays.xml b/patches/src/main/resources/addresources/values/arrays.xml index c600e86b7e..b11c1f9c35 100644 --- a/patches/src/main/resources/addresources/values/arrays.xml +++ b/patches/src/main/resources/addresources/values/arrays.xml @@ -221,6 +221,28 @@ BROWSE + + + @string/revanced_shorts_player_type_shorts + @string/revanced_shorts_player_type_regular_player + + + + SHORTS_PLAYER + REGULAR_PLAYER + + + @string/revanced_shorts_player_type_shorts + @string/revanced_shorts_player_type_regular_player + @string/revanced_shorts_player_type_regular_player_fullscreen + + + + SHORTS_PLAYER + REGULAR_PLAYER + REGULAR_PLAYER_FULLSCREEN + + @string/revanced_alt_thumbnail_options_entry_1 diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index bb502a992b..f7f95f6e5a 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1058,6 +1058,12 @@ If later turned off, it is recommended to clear the app data to prevent UI bugs. Shorts player will not resume on app startup Shorts player will resume on app startup + + Open Shorts with + Shorts player + Regular player + Regular player fullscreen + Autoplay Shorts Shorts will autoplay