From 2018306d5f578ac9915f0a6001391999896fdacc Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Mon, 9 Dec 2024 22:50:53 +0900 Subject: [PATCH] feat(YouTube): Support version `19.34.42` --- .../patches/utils/InitializationPatch.java | 5 - .../ReVancedPreferenceFragment.java | 4 +- .../extension/shared/utils/PackageUtils.java | 48 ++-- .../youtube/patches/feed/FeedPatch.java | 4 +- .../patches/general/MiniplayerPatch.java | 234 ++++++++++++++---- .../patches/utils/InitializationPatch.java | 3 - .../extension/youtube/settings/Settings.java | 12 +- .../ReVancedPreferenceFragment.java | 4 +- .../youtube/utils/ExtendedUtils.java | 6 + .../branding/icon/CustomBrandingIconPatch.kt | 46 ++-- .../music/utils/settings/SettingsPatch.kt | 4 - .../feed/components/FeedComponentsPatch.kt | 18 +- .../general/miniplayer/Fingerprints.kt | 78 +++--- .../general/miniplayer/MiniplayerPatch.kt | 190 ++++++++------ .../spoofappversion/SpoofAppVersionPatch.kt | 53 ++++ .../general/toolbar/ToolBarComponentsPatch.kt | 2 + .../branding/icon/CustomBrandingIconPatch.kt | 45 +++- .../components/PlayerComponentsPatch.kt | 2 + .../patches/youtube/utils/Fingerprints.kt | 22 ++ .../youtube/utils/compatibility/Constants.kt | 3 +- .../playercontrols/PlayerControlsPatch.kt | 4 +- .../youtube/utils/playertype/Fingerprints.kt | 16 +- .../utils/playertype/PlayerTypeHookPatch.kt | 46 ++-- .../utils/playservice/VersionCheckPatch.kt | 18 ++ .../utils/resourceid/SharedResourceIdPatch.kt | 6 + .../youtube/utils/toolbar/Fingerprints.kt | 16 -- .../youtube/utils/toolbar/ToolBarHookPatch.kt | 59 +++-- .../kotlin/app/revanced/util/ResourceUtils.kt | 78 ++---- .../youtube/settings/host/values/arrays.xml | 30 ++- .../youtube/settings/xml/revanced_prefs.xml | 41 ++- 30 files changed, 711 insertions(+), 386 deletions(-) diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/InitializationPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/InitializationPatch.java index 4524a10d0..a924e39c8 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/InitializationPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/InitializationPatch.java @@ -26,9 +26,4 @@ public static void onCreate(@NonNull Activity mActivity) { showRestartDialog(mActivity, "revanced_extended_restart_first_run", 3000); Utils.runOnMainThreadDelayed(() -> BaseSettings.SETTINGS_INITIALIZED.save(true), 3000); } - - public static void setDeviceInformation(@NonNull Activity mActivity) { - ExtendedUtils.setApplicationLabel(); - ExtendedUtils.setVersionName(); - } } \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java index a819e425b..70ef1de5a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java @@ -248,8 +248,8 @@ private void exportActivity() { @SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); - var appName = ExtendedUtils.getApplicationLabel(); - var versionName = ExtendedUtils.getVersionName(); + var appName = ExtendedUtils.getAppLabel(); + var versionName = ExtendedUtils.getAppVersionName(); var formatDate = dateFormat.format(new Date(System.currentTimeMillis())); var fileName = String.format("%s_v%s_%s.txt", appName, versionName, formatDate); diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/utils/PackageUtils.java b/extensions/shared/src/main/java/app/revanced/extension/shared/utils/PackageUtils.java index 7975ba063..1db1f137e 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/utils/PackageUtils.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/utils/PackageUtils.java @@ -8,16 +8,25 @@ import androidx.annotation.Nullable; public class PackageUtils extends Utils { - private static String applicationLabel = ""; - private static int smallestScreenWidthDp = 0; - private static String versionName = ""; - public static String getApplicationLabel() { - return applicationLabel; + public static String getAppLabel() { + final PackageInfo packageInfo = getPackageInfo(); + if (packageInfo != null) { + final ApplicationInfo applicationInfo = packageInfo.applicationInfo; + if (applicationInfo != null && applicationInfo.loadLabel(getPackageManager()) instanceof String applicationLabel) { + return applicationLabel; + } + } + return ""; } - public static String getVersionName() { - return versionName; + public static String getAppVersionName() { + final PackageInfo packageInfo = getPackageInfo(); + if (packageInfo != null) { + return packageInfo.versionName; + } else { + return ""; + } } public static boolean isPackageEnabled(@NonNull String packageName) { @@ -30,32 +39,11 @@ public static boolean isPackageEnabled(@NonNull String packageName) { } public static boolean isTablet() { - return smallestScreenWidthDp >= 600; - } - - public static void setApplicationLabel() { - final PackageInfo packageInfo = getPackageInfo(); - if (packageInfo != null) { - final ApplicationInfo applicationInfo = packageInfo.applicationInfo; - if (applicationInfo != null) { - applicationLabel = (String) applicationInfo.loadLabel(getPackageManager()); - } - } - } - - public static void setSmallestScreenWidthDp() { - smallestScreenWidthDp = context.getResources().getConfiguration().smallestScreenWidthDp; - } - - public static void setVersionName() { - final PackageInfo packageInfo = getPackageInfo(); - if (packageInfo != null) { - versionName = packageInfo.versionName; - } + return getSmallestScreenWidthDp() >= 600; } public static int getSmallestScreenWidthDp() { - return smallestScreenWidthDp; + return context.getResources().getConfiguration().smallestScreenWidthDp; } // utils diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/FeedPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/FeedPatch.java index 46a494a87..9ab4dd81d 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/FeedPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/FeedPatch.java @@ -78,8 +78,8 @@ public static void hideCaptionsButtonContainer(View view) { ); } - public static boolean hideFloatingButton() { - return Settings.HIDE_FLOATING_BUTTON.get(); + public static String hideFloatingButton(String fab) { + return Settings.HIDE_FLOATING_BUTTON.get() ? null : fab; } public static void hideLatestVideosButton(View view) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/MiniplayerPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/MiniplayerPatch.java index 9c13d5a52..44f845926 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/MiniplayerPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/MiniplayerPatch.java @@ -1,15 +1,17 @@ package app.revanced.extension.youtube.patches.general; -import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_1; -import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_2; -import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_3; -import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.ORIGINAL; +import static app.revanced.extension.shared.utils.StringRef.str; +import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.*; +import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_20_OR_GREATER; +import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_21_OR_GREATER; +import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_26_OR_GREATER; +import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_29_OR_GREATER; import static app.revanced.extension.youtube.utils.ExtendedUtils.validateValue; +import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.Nullable; @@ -17,9 +19,10 @@ import app.revanced.extension.shared.utils.Logger; import app.revanced.extension.shared.utils.ResourceUtils; import app.revanced.extension.shared.utils.Utils; +import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.youtube.settings.Settings; -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "SpellCheckingInspection"}) public final class MiniplayerPatch { /** @@ -27,14 +30,28 @@ public final class MiniplayerPatch { */ public enum MiniplayerType { /** - * Unmodified type, and same as un-patched. + * Disabled. When swiped down the miniplayer is immediately closed. + * Only available with 19.43+ */ + DISABLED(false, null), + /** Unmodified type, and same as un-patched. */ ORIGINAL(null, null), + /** + * Exactly the same as MINIMAL and only here for migration of user settings. + * Eventually this should be deleted. + */ + @Deprecated PHONE(false, null), + MINIMAL(false, null), TABLET(true, null), MODERN_1(null, 1), MODERN_2(null, 2), - MODERN_3(null, 3); + MODERN_3(null, 3), + /** + * Half broken miniplayer, that might be work in progress or left over abandoned code. + * Can force this type by editing the import/export settings. + */ + MODERN_4(null, 4); /** * Legacy tablet hook value. @@ -58,6 +75,44 @@ public boolean isModern() { } } + private static final int MINIPLAYER_SIZE; + + static { + // YT appears to use the device screen dip width, plus an unknown fixed horizontal padding size. + DisplayMetrics displayMetrics = Utils.getContext().getResources().getDisplayMetrics(); + final int deviceDipWidth = (int) (displayMetrics.widthPixels / displayMetrics.density); + + // YT seems to use a minimum height to calculate the minimum miniplayer width based on the video. + // 170 seems to be the smallest that can be used and using less makes no difference. + final int WIDTH_DIP_MIN = 170; // Seems to be the smallest that works. + final int HORIZONTAL_PADDING_DIP = 15; // Estimated padding. + // Round down to the nearest 5 pixels, to keep any error toasts easier to read. + final int WIDTH_DIP_MAX = 5 * ((deviceDipWidth - HORIZONTAL_PADDING_DIP) / 5); + Logger.printDebug(() -> "Screen dip width: " + deviceDipWidth + " maxWidth: " + WIDTH_DIP_MAX); + + int dipWidth = Settings.MINIPLAYER_WIDTH_DIP.get(); + + if (dipWidth < WIDTH_DIP_MIN || dipWidth > WIDTH_DIP_MAX) { + Utils.showToastLong(str("revanced_miniplayer_width_dip_invalid_toast", + WIDTH_DIP_MIN, WIDTH_DIP_MAX)); + + // Instead of resetting, clamp the size at the bounds. + dipWidth = Math.max(WIDTH_DIP_MIN, Math.min(dipWidth, WIDTH_DIP_MAX)); + Settings.MINIPLAYER_WIDTH_DIP.save(dipWidth); + } + + MINIPLAYER_SIZE = dipWidth; + + final int opacity = validateValue( + Settings.MINIPLAYER_OPACITY, + 0, + 100, + "revanced_miniplayer_opacity_invalid_toast" + ); + + OPACITY_LEVEL = (opacity * 255) / 100; + } + /** * Modern subtitle overlay for {@link MiniplayerType#MODERN_2}. * Resource is not present in older targets, and this field will be zero. @@ -67,19 +122,21 @@ public boolean isModern() { private static final MiniplayerType CURRENT_TYPE = Settings.MINIPLAYER_TYPE.get(); + /** + * Cannot turn off double tap with modern 2 or 3 with later targets, + * as forcing it off breakings tapping the miniplayer. + */ private static final boolean DOUBLE_TAP_ACTION_ENABLED = - (CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_2 || CURRENT_TYPE == MODERN_3) && Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get(); + // 19.29+ is very broken if double tap is not enabled. + IS_19_29_OR_GREATER || + (CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get()); private static final boolean DRAG_AND_DROP_ENABLED = - CURRENT_TYPE == MODERN_1 && Settings.MINIPLAYER_DRAG_AND_DROP.get(); - - private static final boolean HIDE_EXPAND_CLOSE_AVAILABLE = - (CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3) && - !DOUBLE_TAP_ACTION_ENABLED && - !DRAG_AND_DROP_ENABLED; + CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get(); private static final boolean HIDE_EXPAND_CLOSE_ENABLED = - HIDE_EXPAND_CLOSE_AVAILABLE && Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.get(); + Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.get() + && Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.isAvailable(); private static final boolean HIDE_SUBTEXT_ENABLED = (CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3) && Settings.MINIPLAYER_HIDE_SUBTEXT.get(); @@ -87,17 +144,49 @@ public boolean isModern() { private static final boolean HIDE_REWIND_FORWARD_ENABLED = CURRENT_TYPE == MODERN_1 && Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get(); + private static final boolean MINIPLAYER_ROUNDED_CORNERS_ENABLED = + Settings.MINIPLAYER_ROUNDED_CORNERS.get(); + + private static final boolean MINIPLAYER_HORIZONTAL_DRAG_ENABLED = + DRAG_AND_DROP_ENABLED && Settings.MINIPLAYER_HORIZONTAL_DRAG.get(); + + /** + * Remove a broken and always present subtitle text that is only + * present with {@link MiniplayerType#MODERN_2}. Bug was fixed in 19.21. + */ + private static final boolean HIDE_BROKEN_MODERN_2_SUBTITLE = + CURRENT_TYPE == MODERN_2 && !IS_19_21_OR_GREATER; + private static final int OPACITY_LEVEL; - static { - final int opacity = validateValue( - Settings.MINIPLAYER_OPACITY, - 0, - 100, - "revanced_miniplayer_opacity_invalid_toast" - ); + public static final class MiniplayerHorizontalDragAvailability implements Setting.Availability { + @Override + public boolean isAvailable() { + return Settings.MINIPLAYER_TYPE.get().isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get(); + } + } - OPACITY_LEVEL = (opacity * 255) / 100; + public static final class MiniplayerHideExpandCloseAvailability implements Setting.Availability { + @Override + public boolean isAvailable() { + MiniplayerType type = Settings.MINIPLAYER_TYPE.get(); + return (!IS_19_20_OR_GREATER && (type == MODERN_1 || type == MODERN_3)) + || (!IS_19_26_OR_GREATER && type == MODERN_1 + && !Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() && !Settings.MINIPLAYER_DRAG_AND_DROP.get()) + || (IS_19_29_OR_GREATER && type == MODERN_3); + } + } + + /** + * Injection point. + *

+ * Enables a handler that immediately closes the miniplayer when the video is minimized, + * effectively disabling the miniplayer. + */ + public static boolean getMiniplayerOnCloseHandler(boolean original) { + return CURRENT_TYPE == ORIGINAL + ? original + : CURRENT_TYPE == DISABLED; } /** @@ -141,17 +230,71 @@ public static void adjustMiniplayerOpacity(ImageView view) { /** * Injection point. */ - public static boolean enableMiniplayerDoubleTapAction() { + public static boolean getModernFeatureFlagsActiveOverride(boolean original) { + if (CURRENT_TYPE == ORIGINAL) { + return original; + } + + return CURRENT_TYPE.isModern(); + } + + /** + * Injection point. + */ + public static boolean enableMiniplayerDoubleTapAction(boolean original) { + if (CURRENT_TYPE == ORIGINAL) { + return original; + } + return DOUBLE_TAP_ACTION_ENABLED; } /** * Injection point. */ - public static boolean enableMiniplayerDragAndDrop() { + public static boolean enableMiniplayerDragAndDrop(boolean original) { + if (CURRENT_TYPE == ORIGINAL) { + return original; + } + return DRAG_AND_DROP_ENABLED; } + + /** + * Injection point. + */ + public static boolean setRoundedCorners(boolean original) { + if (CURRENT_TYPE.isModern()) { + return MINIPLAYER_ROUNDED_CORNERS_ENABLED; + } + + return original; + } + + /** + * Injection point. + */ + public static int setMiniplayerDefaultSize(int original) { + if (CURRENT_TYPE.isModern()) { + return MINIPLAYER_SIZE; + } + + return original; + } + + + /** + * Injection point. + */ + public static boolean setHorizontalDrag(boolean original) { + if (CURRENT_TYPE.isModern()) { + return MINIPLAYER_HORIZONTAL_DRAG_ENABLED; + } + + return original; + } + /** * Injection point. */ @@ -169,29 +312,36 @@ public static void hideMiniplayerRewindForward(ImageView view) { /** * Injection point. */ - public static boolean hideMiniplayerSubTexts(View view) { - // Different subviews are passed in, but only TextView and layouts are of interest here. - final boolean hideView = HIDE_SUBTEXT_ENABLED && (view instanceof TextView || view instanceof LinearLayout); - Utils.hideViewByRemovingFromParentUnderCondition(hideView, view); - return hideView || view == null; + public static void hideMiniplayerSubTexts(View view) { + try { + // Different subviews are passed in, but only TextView is of interest here. + if (HIDE_SUBTEXT_ENABLED && view instanceof TextView) { + Logger.printDebug(() -> "Hiding subtext view"); + Utils.hideViewByRemovingFromParentUnderCondition(true, view); + } + } catch (Exception ex) { + Logger.printException(() -> "hideMiniplayerSubTexts failure", ex); + } } /** * Injection point. */ public static void playerOverlayGroupCreated(View group) { - // Modern 2 has an half broken subtitle that is always present. - // Always hide it to make the miniplayer mostly usable. - if (CURRENT_TYPE == MODERN_2 && MODERN_OVERLAY_SUBTITLE_TEXT != 0) { - if (group instanceof ViewGroup viewGroup) { - View subtitleText = Utils.getChildView(viewGroup, true, - view -> view.getId() == MODERN_OVERLAY_SUBTITLE_TEXT); - - if (subtitleText != null) { - subtitleText.setVisibility(View.GONE); - Logger.printDebug(() -> "Modern overlay subtitle view set to hidden"); + try { + if (HIDE_BROKEN_MODERN_2_SUBTITLE && MODERN_OVERLAY_SUBTITLE_TEXT != 0) { + if (group instanceof ViewGroup) { + View subtitleText = Utils.getChildView((ViewGroup) group, true, + view -> view.getId() == MODERN_OVERLAY_SUBTITLE_TEXT); + + if (subtitleText != null) { + subtitleText.setVisibility(View.GONE); + Logger.printDebug(() -> "Modern overlay subtitle view set to hidden"); + } } } + } catch (Exception ex) { + Logger.printException(() -> "playerOverlayGroupCreated failure", ex); } } -} +} \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/InitializationPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/InitializationPatch.java index 4dd5f0821..680ff105a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/InitializationPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/InitializationPatch.java @@ -31,9 +31,6 @@ public static void onCreate(@NonNull Activity mActivity) { } public static void setExtendedUtils(@NonNull Activity mActivity) { - ExtendedUtils.setApplicationLabel(); - ExtendedUtils.setSmallestScreenWidthDp(); - ExtendedUtils.setVersionName(); ExtendedUtils.setPlayerFlyoutMenuAdditionalSettings(); } } \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 40b410985..1351ecc9a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -10,6 +10,7 @@ import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_1; import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_2; import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_3; +import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_4; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE; @@ -34,6 +35,7 @@ import app.revanced.extension.youtube.patches.general.ChangeStartPagePatch; import app.revanced.extension.youtube.patches.general.ChangeStartPagePatch.StartPage; import app.revanced.extension.youtube.patches.general.LayoutSwitchPatch.FormFactor; +import app.revanced.extension.youtube.patches.general.MiniplayerPatch; import app.revanced.extension.youtube.patches.general.YouTubeMusicActionsPatch; import app.revanced.extension.youtube.patches.misc.SpoofStreamingDataPatch; import app.revanced.extension.youtube.patches.misc.WatchHistoryPatch.WatchHistoryType; @@ -167,11 +169,15 @@ public class Settings extends BaseSettings { // PreferenceScreen: General - Miniplayer public static final EnumSetting MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.ORIGINAL, true); - public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_enable_double_tap_action", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3)); - public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_enable_drag_and_drop", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1)); - public static final BooleanSetting MINIPLAYER_HIDE_EXPAND_CLOSE = new BooleanSetting("revanced_miniplayer_hide_expand_close", FALSE, true); + private static final Setting.Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4); + public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, MINIPLAYER_ANY_MODERN); + public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_drag_and_drop", TRUE, true, MINIPLAYER_ANY_MODERN); + public static final BooleanSetting MINIPLAYER_HORIZONTAL_DRAG = new BooleanSetting("revanced_miniplayer_horizontal_drag", FALSE, true, new MiniplayerPatch.MiniplayerHorizontalDragAvailability()); + public static final BooleanSetting MINIPLAYER_HIDE_EXPAND_CLOSE = new BooleanSetting("revanced_miniplayer_hide_expand_close", FALSE, true, new MiniplayerPatch.MiniplayerHideExpandCloseAvailability()); public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1, MODERN_3)); public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1)); + public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, MINIPLAYER_ANY_MODERN); + public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, MINIPLAYER_ANY_MODERN); public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, MINIPLAYER_TYPE.availability(MODERN_1)); // PreferenceScreen: General - Navigation bar diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java index 086243f9a..7d9db94fe 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java @@ -593,8 +593,8 @@ private void setBackupRestorePreference() { private void exportActivity() { @SuppressLint("SimpleDateFormat") final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); - final String appName = ExtendedUtils.getApplicationLabel(); - final String versionName = ExtendedUtils.getVersionName(); + final String appName = ExtendedUtils.getAppLabel(); + final String versionName = ExtendedUtils.getAppVersionName(); final String formatDate = dateFormat.format(new Date(System.currentTimeMillis())); final String fileName = String.format("%s_v%s_%s.txt", appName, versionName, formatDate); diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/ExtendedUtils.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/ExtendedUtils.java index 321e28de0..b92b4f09b 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/ExtendedUtils.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/ExtendedUtils.java @@ -12,6 +12,12 @@ import app.revanced.extension.youtube.settings.Settings; public class ExtendedUtils extends PackageUtils { + public static final boolean IS_19_17_OR_GREATER = getAppVersionName().compareTo("19.17.00") >= 0; + public static final boolean IS_19_20_OR_GREATER = getAppVersionName().compareTo("19.20.00") >= 0; + public static final boolean IS_19_21_OR_GREATER = getAppVersionName().compareTo("19.21.00") >= 0; + public static final boolean IS_19_26_OR_GREATER = getAppVersionName().compareTo("19.26.00") >= 0; + public static final boolean IS_19_29_OR_GREATER = getAppVersionName().compareTo("19.29.00") >= 0; + public static final boolean IS_19_34_OR_GREATER = getAppVersionName().compareTo("19.34.00") >= 0; public static int validateValue(IntegerSetting settings, int min, int max, String message) { int value = settings.get(); diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt index 3d4c9e178..5fadac89c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt @@ -14,6 +14,7 @@ import app.revanced.patches.music.utils.settings.settingsPatch import app.revanced.util.ResourceGroup import app.revanced.util.Utils.trimIndentMultiline import app.revanced.util.copyResources +import app.revanced.util.getAdaptiveIconResourceFile import app.revanced.util.getResourceGroup import app.revanced.util.underBarOrThrow import org.w3c.dom.Element @@ -258,38 +259,23 @@ val customBrandingIconPatch = resourcePatch( return@execute } - fun getAdaptiveIconResourceFile(tag: String): String { - document("res/mipmap-anydpi/ic_launcher_release.xml").use { document -> - val adaptiveIcon = document - .getElementsByTagName("adaptive-icon") - .item(0) as Element - - val childNodes = adaptiveIcon.childNodes - for (i in 0 until childNodes.length) { - val node = childNodes.item(i) - if (node is Element && node.tagName == tag && node.hasAttribute("android:drawable")) { - return node.getAttribute("android:drawable").split("/")[1] - } - } - throw PatchException("Element not found: $tag") - } - } - mapOf( - ADAPTIVE_ICON_BACKGROUND_FILE_NAME to getAdaptiveIconResourceFile("background"), - ADAPTIVE_ICON_FOREGROUND_FILE_NAME to getAdaptiveIconResourceFile("foreground") + ADAPTIVE_ICON_BACKGROUND_FILE_NAME to getAdaptiveIconResourceFile("res/mipmap-anydpi/ic_launcher_release.xml", "background"), + ADAPTIVE_ICON_FOREGROUND_FILE_NAME to getAdaptiveIconResourceFile("res/mipmap-anydpi/ic_launcher_release.xml", "foreground") ).forEach { (oldIconResourceFile, newIconResourceFile) -> - mipmapDirectories.forEach { - val mipmapDirectory = resourceDirectory.resolve(it) - Files.move( - mipmapDirectory - .resolve("$oldIconResourceFile.png") - .toPath(), - mipmapDirectory - .resolve("$newIconResourceFile.png") - .toPath(), - StandardCopyOption.REPLACE_EXISTING - ) + if (oldIconResourceFile != newIconResourceFile) { + mipmapDirectories.forEach { + val mipmapDirectory = resourceDirectory.resolve(it) + Files.move( + mipmapDirectory + .resolve("$oldIconResourceFile.png") + .toPath(), + mipmapDirectory + .resolve("$newIconResourceFile.png") + .toPath(), + StandardCopyOption.REPLACE_EXISTING + ) + } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt index 1966122b5..a9462c265 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt @@ -112,10 +112,6 @@ private val settingsBytecodePatch = bytecodePatch( // endregion - injectOnCreateMethodCall( - EXTENSION_INITIALIZATION_CLASS_DESCRIPTOR, - "setDeviceInformation" - ) injectOnCreateMethodCall( EXTENSION_INITIALIZATION_CLASS_DESCRIPTOR, "onCreate" diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt index 4c19d9fc4..fa99e6c4b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt @@ -147,18 +147,22 @@ val feedComponentsPatch = bytecodePatch( // region patch for hide floating button onCreateMethod.apply { - val fabIndex = indexOfFirstInstructionOrThrow { + val stringIndex = indexOfFirstInstructionOrThrow { opcode == Opcode.CONST_STRING && getReference()?.string == "fab" } - val fabRegister = getInstruction(fabIndex).registerA - val jumpIndex = indexOfFirstInstructionOrThrow(fabIndex + 1, Opcode.CONST_STRING) + val stringRegister = getInstruction(stringIndex).registerA + val insertIndex = indexOfFirstInstructionOrThrow(stringIndex) { + opcode == Opcode.INVOKE_DIRECT && + getReference()?.name == "" + } + val jumpIndex = indexOfFirstInstructionOrThrow(insertIndex, Opcode.CONST_STRING) addInstructionsWithLabels( - fabIndex, """ - invoke-static {}, $FEED_CLASS_DESCRIPTOR->hideFloatingButton()Z - move-result v$fabRegister - if-nez v$fabRegister, :hide + insertIndex, """ + invoke-static {v$stringRegister}, $FEED_CLASS_DESCRIPTOR->hideFloatingButton(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$stringRegister + if-eqz v$stringRegister, :hide """, ExternalLabel("hide", getInstruction(jumpIndex)) ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/Fingerprints.kt index 6a5f0b04e..f349da03e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/Fingerprints.kt @@ -2,20 +2,18 @@ package app.revanced.patches.youtube.general.miniplayer -import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater import app.revanced.patches.youtube.utils.resourceid.floatyBarTopMargin +import app.revanced.patches.youtube.utils.resourceid.miniplayerMaxSize import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerClose import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerExpand import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerForwardButton import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerRewindButton import app.revanced.patches.youtube.utils.resourceid.scrimOverlay import app.revanced.patches.youtube.utils.resourceid.ytOutlinePictureInPictureWhite -import app.revanced.util.containsLiteralInstruction import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.or import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.util.MethodUtil internal val miniplayerDimensionsCalculatorParentFingerprint = legacyFingerprint( name = "miniplayerDimensionsCalculatorParentFingerprint", @@ -25,6 +23,9 @@ internal val miniplayerDimensionsCalculatorParentFingerprint = legacyFingerprint literals = listOf(floatyBarTopMargin), ) +/** + * Matches using the class found in [miniplayerModernViewParentFingerprint]. + */ internal val miniplayerModernAddViewListenerFingerprint = legacyFingerprint( name = "miniplayerModernAddViewListenerFingerprint", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, @@ -32,6 +33,9 @@ internal val miniplayerModernAddViewListenerFingerprint = legacyFingerprint( parameters = listOf("Landroid/view/View;") ) +/** + * Matches using the class found in [miniplayerModernViewParentFingerprint]. + */ internal val miniplayerModernCloseButtonFingerprint = legacyFingerprint( name = "miniplayerModernCloseButtonFingerprint", returnType = "Landroid/widget/ImageView;", @@ -40,41 +44,33 @@ internal val miniplayerModernCloseButtonFingerprint = legacyFingerprint( literals = listOf(modernMiniPlayerClose), ) -private var constructorMethodCount = 0 - -internal fun isMultiConstructorMethod() = constructorMethodCount > 1 +internal const val MINIPLAYER_MODERN_FEATURE_KEY = 45622882L +// In later targets this feature flag does nothing and is dead code. +internal const val MINIPLAYER_MODERN_FEATURE_LEGACY_KEY = 45630429L +internal const val MINIPLAYER_DOUBLE_TAP_FEATURE_KEY = 45628823L +internal const val MINIPLAYER_DRAG_DROP_FEATURE_KEY = 45628752L +internal const val MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY = 45658112L +internal const val MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY = 45652224L +internal const val MINIPLAYER_INITIAL_SIZE_FEATURE_KEY = 45640023L +internal const val MINIPLAYER_DISABLED_FEATURE_KEY = 45657015L internal val miniplayerModernConstructorFingerprint = legacyFingerprint( name = "miniplayerModernConstructorFingerprint", accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, parameters = listOf("L"), literals = listOf(45623000L), - customFingerprint = custom@{ method, classDef -> - classDef.methods.forEach { - if (MethodUtil.isConstructor(it)) constructorMethodCount += 1 - } - - if (!is_19_25_or_greater) - return@custom true - - // Double tap action (Used in YouTube 19.25.39+). - method.containsLiteralInstruction(45628823L) - && method.containsLiteralInstruction(45630429L) - } -) - -internal val miniplayerModernDragAndDropFingerprint = legacyFingerprint( - name = "miniplayerModernDragAndDropFingerprint", - accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, - parameters = listOf("L"), - literals = listOf(45628752L), ) -internal val miniplayerModernEnabledFingerprint = legacyFingerprint( - name = "miniplayerModernEnabledFingerprint", - literals = listOf(45622882L), +internal val miniplayerOnCloseHandlerFingerprint = legacyFingerprint( + name = "miniplayerOnCloseHandlerFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Z", + literals = listOf(MINIPLAYER_DISABLED_FEATURE_KEY), ) +/** + * Matches using the class found in [miniplayerModernViewParentFingerprint]. + */ internal val miniplayerModernExpandButtonFingerprint = legacyFingerprint( name = "miniplayerModernExpandButtonFingerprint", returnType = "Landroid/widget/ImageView;", @@ -83,6 +79,9 @@ internal val miniplayerModernExpandButtonFingerprint = legacyFingerprint( literals = listOf(modernMiniPlayerExpand), ) +/** + * Matches using the class found in [miniplayerModernViewParentFingerprint]. + */ internal val miniplayerModernExpandCloseDrawablesFingerprint = legacyFingerprint( name = "miniplayerModernExpandCloseDrawablesFingerprint", returnType = "V", @@ -91,6 +90,9 @@ internal val miniplayerModernExpandCloseDrawablesFingerprint = legacyFingerprint literals = listOf(ytOutlinePictureInPictureWhite), ) +/** + * Matches using the class found in [miniplayerModernViewParentFingerprint]. + */ internal val miniplayerModernForwardButtonFingerprint = legacyFingerprint( name = "miniplayerModernForwardButtonFingerprint", returnType = "Landroid/widget/ImageView;", @@ -99,6 +101,9 @@ internal val miniplayerModernForwardButtonFingerprint = legacyFingerprint( literals = listOf(modernMiniPlayerForwardButton), ) +/** + * Matches using the class found in [miniplayerModernViewParentFingerprint]. + */ internal val miniplayerModernOverlayViewFingerprint = legacyFingerprint( name = "miniplayerModernOverlayViewFingerprint", returnType = "V", @@ -107,6 +112,9 @@ internal val miniplayerModernOverlayViewFingerprint = legacyFingerprint( literals = listOf(scrimOverlay), ) +/** + * Matches using the class found in [miniplayerModernViewParentFingerprint]. + */ internal val miniplayerModernRewindButtonFingerprint = legacyFingerprint( name = "miniplayerModernRewindButtonFingerprint", returnType = "Landroid/widget/ImageView;", @@ -123,11 +131,16 @@ internal val miniplayerModernViewParentFingerprint = legacyFingerprint( strings = listOf("player_overlay_modern_mini_player_controls") ) +internal val miniplayerMinimumSizeFingerprint = legacyFingerprint( + name = "miniplayerMinimumSizeFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + literals = listOf(192L, 128L, miniplayerMaxSize), +) + internal val miniplayerOverrideFingerprint = legacyFingerprint( name = "miniplayerOverrideFingerprint", returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("L"), strings = listOf("appName") ) @@ -144,6 +157,7 @@ internal val miniplayerResponseModelSizeCheckFingerprint = legacyFingerprint( returnType = "L", parameters = listOf("Ljava/lang/Object;", "Ljava/lang/Object;"), opcodes = listOf( + Opcode.RETURN_OBJECT, Opcode.CHECK_CAST, Opcode.CHECK_CAST, Opcode.INVOKE_STATIC, @@ -152,12 +166,12 @@ internal val miniplayerResponseModelSizeCheckFingerprint = legacyFingerprint( ) ) +internal const val YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME = + "Lcom/google/android/apps/youtube/app/common/player/overlay/YouTubePlayerOverlaysLayout;" + internal val youTubePlayerOverlaysLayoutFingerprint = legacyFingerprint( name = "youTubePlayerOverlaysLayoutFingerprint", customFingerprint = { _, classDef -> classDef.type == YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME } ) - -internal const val YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME = - "Lcom/google/android/apps/youtube/app/common/player/overlay/YouTubePlayerOverlaysLayout;" diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch.kt index 6fa89f9ba..cfe0b936c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch.kt @@ -1,22 +1,22 @@ package app.revanced.patches.youtube.general.miniplayer -import app.revanced.patcher.Fingerprint import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions -import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable -import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH import app.revanced.patches.youtube.utils.patch.PatchList.MINIPLAYER import app.revanced.patches.youtube.utils.playservice.is_19_15_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_23_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_26_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_29_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_36_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_43_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerClose import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerExpand @@ -28,6 +28,7 @@ import app.revanced.patches.youtube.utils.resourceid.ytOutlinePictureInPictureWh import app.revanced.patches.youtube.utils.resourceid.ytOutlineXWhite import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch +import app.revanced.util.addInstructionsAtControlFlowLabel import app.revanced.util.findInstructionIndicesReversedOrThrow import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.matchOrThrow @@ -41,14 +42,13 @@ 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.iface.Method +import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction -import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.TypeReference import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter -import com.android.tools.smali.dexlib2.util.MethodUtil private const val EXTENSION_CLASS_DESCRIPTOR = "$GENERAL_PATH/MiniplayerPatch;" @@ -83,7 +83,7 @@ val miniplayerPatch = bytecodePatch( """ invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->$methodName(Z)Z move-result v$register - """ + """ ) } @@ -105,39 +105,34 @@ val miniplayerPatch = bytecodePatch( * Adds an override to specify which modern miniplayer is used. */ fun MutableMethod.insertModernMiniplayerTypeOverride(iPutIndex: Int) { - val targetInstruction = getInstruction(iPutIndex) - val targetReference = (targetInstruction as ReferenceInstruction).reference + val register = getInstruction(iPutIndex).registerA - addInstructions( - iPutIndex + 1, """ - invoke-static { v${targetInstruction.registerA} }, $EXTENSION_CLASS_DESCRIPTOR->getModernMiniplayerOverrideType(I)I - move-result v${targetInstruction.registerA} - # Original instruction - iput v${targetInstruction.registerA}, v${targetInstruction.registerB}, $targetReference - """ + addInstructionsAtControlFlowLabel( + iPutIndex, + """ + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getModernMiniplayerOverrideType(I)I + move-result v$register + """, ) - removeInstruction(iPutIndex) } - fun Pair.hookInflatedView( + fun MutableMethod.hookInflatedView( literalValue: Long, hookedClassType: String, extensionMethodName: String, ) { - methodOrThrow(miniplayerModernViewParentFingerprint).apply { - val imageViewIndex = indexOfFirstInstructionOrThrow( - indexOfFirstLiteralInstructionOrThrow(literalValue) - ) { - opcode == Opcode.CHECK_CAST && - getReference()?.type == hookedClassType - } - - val register = getInstruction(imageViewIndex).registerA - addInstruction( - imageViewIndex + 1, - "invoke-static { v$register }, $extensionMethodName" - ) + val imageViewIndex = indexOfFirstInstructionOrThrow( + indexOfFirstLiteralInstructionOrThrow(literalValue) + ) { + opcode == Opcode.CHECK_CAST && + getReference()?.type == hookedClassType } + + val register = getInstruction(imageViewIndex).registerA + addInstruction( + imageViewIndex + 1, + "invoke-static { v$register }, $extensionMethodName" + ) } // Modern mini player is only present and functional in 19.15+. @@ -184,7 +179,7 @@ val miniplayerPatch = bytecodePatch( } if (isPatchingOldVersion) { - settingArray += "SETTINGS: MINIPLAYER_TYPE_LEGACY" + settingArray += "SETTINGS: MINIPLAYER_TYPE_19_14" addPreference(settingArray, MINIPLAYER) // Return here, as patch below is only intended for new versions of the app. @@ -197,14 +192,14 @@ val miniplayerPatch = bytecodePatch( miniplayerModernConstructorFingerprint.mutableClassOrThrow().methods.forEach { it.apply { - if (MethodUtil.isConstructor(it)) { + if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) { val iPutIndex = indexOfFirstInstructionOrThrow { - opcode == Opcode.IPUT && - getReference()?.type == "I" + this.opcode == Opcode.IPUT && + this.getReference()?.type == "I" } insertModernMiniplayerTypeOverride(iPutIndex) - } else if (isMultiConstructorMethod()) { + } else { findReturnIndicesReversed().forEach { index -> insertModernMiniplayerOverride( index @@ -214,27 +209,96 @@ val miniplayerPatch = bytecodePatch( } } + if (is_19_23_or_greater) { + miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( + MINIPLAYER_DRAG_DROP_FEATURE_KEY, + "$EXTENSION_CLASS_DESCRIPTOR->enableMiniplayerDragAndDrop(Z)Z" + ) + settingArray += "SETTINGS: MINIPLAYER_DRAG_AND_DROP" + } + if (is_19_25_or_greater) { - miniplayerModernEnabledFingerprint.injectLiteralInstructionBooleanCall( - 45622882L, + miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( + MINIPLAYER_MODERN_FEATURE_LEGACY_KEY, "$EXTENSION_CLASS_DESCRIPTOR->getModernMiniplayerOverride(Z)Z" ) + + miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( + MINIPLAYER_MODERN_FEATURE_KEY, + "$EXTENSION_CLASS_DESCRIPTOR->getModernFeatureFlagsActiveOverride(Z)Z" + ) + + miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( + MINIPLAYER_DOUBLE_TAP_FEATURE_KEY, + "$EXTENSION_CLASS_DESCRIPTOR->enableMiniplayerDoubleTapAction(Z)Z" + ) + + if (!is_19_29_or_greater) { + settingArray += "SETTINGS: MINIPLAYER_DOUBLE_TAP_ACTION" + } } - // endregion + if (is_19_26_or_greater) { + miniplayerModernConstructorFingerprint.methodOrThrow().apply { + val literalIndex = indexOfFirstLiteralInstructionOrThrow( + MINIPLAYER_INITIAL_SIZE_FEATURE_KEY, + ) + val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.LONG_TO_INT) - // region Enable double tap action. + val register = getInstruction(targetIndex).registerA - if (is_19_25_or_greater) { + addInstructions( + targetIndex + 1, + """ + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->setMiniplayerDefaultSize(I)I + move-result v$register + """, + ) + } + + // Override a minimum size constant. + miniplayerMinimumSizeFingerprint.methodOrThrow().apply { + val index = indexOfFirstInstructionOrThrow { + opcode == Opcode.CONST_16 && + (this as NarrowLiteralInstruction).narrowLiteral == 192 + } + val register = getInstruction(index).registerA + + // Smaller sizes can be used, but the miniplayer will always start in size 170 if set any smaller. + // The 170 initial limit probably could be patched to allow even smaller initial sizes, + // but 170 is already half the horizontal space and smaller does not seem useful. + replaceInstruction(index, "const/16 v$register, 170") + } + + settingArray += "SETTINGS: MINIPLAYER_WIDTH_DIP" + } else { + settingArray += "SETTINGS: MINIPLAYER_REWIND_FORWARD" + } + + if (is_19_36_or_greater) { miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( - 45628823L, - "$EXTENSION_CLASS_DESCRIPTOR->enableMiniplayerDoubleTapAction()Z" + MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY, + "$EXTENSION_CLASS_DESCRIPTOR->setRoundedCorners(Z)Z" ) + + settingArray += "SETTINGS: MINIPLAYER_ROUNDED_CONERS" + } + + if (is_19_43_or_greater) { + miniplayerOnCloseHandlerFingerprint.injectLiteralInstructionBooleanCall( + MINIPLAYER_DISABLED_FEATURE_KEY, + "$EXTENSION_CLASS_DESCRIPTOR->getMiniplayerOnCloseHandler(Z)Z" + ) + miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( - 45630429L, - "$EXTENSION_CLASS_DESCRIPTOR->getModernMiniplayerOverride(Z)Z" + MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY, + "$EXTENSION_CLASS_DESCRIPTOR->setHorizontalDrag(Z)Z" ) - settingArray += "SETTINGS: MINIPLAYER_DOUBLE_TAP_ACTION" + + settingArray += "SETTINGS: MINIPLAYER_HORIZONTAL_DRAG" + settingArray += "SETTINGS: MINIPLAYER_TYPE_19_43" + } else { + settingArray += "SETTINGS: MINIPLAYER_TYPE_19_16" } // endregion @@ -291,7 +355,7 @@ val miniplayerPatch = bytecodePatch( "adjustMiniplayerOpacity" ) ).forEach { (fingerprint, literalValue, methodName) -> - fingerprint.hookInflatedView( + fingerprint.methodOrThrow(miniplayerModernViewParentFingerprint).hookInflatedView( literalValue, "Landroid/widget/ImageView;", "$EXTENSION_CLASS_DESCRIPTOR->$methodName(Landroid/widget/ImageView;)V" @@ -300,23 +364,19 @@ val miniplayerPatch = bytecodePatch( miniplayerModernAddViewListenerFingerprint.methodOrThrow( miniplayerModernViewParentFingerprint - ).apply { - addInstructionsWithLabels( - 0, - """ - invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->hideMiniplayerSubTexts(Landroid/view/View;)Z - move-result v0 - if-nez v0, :hidden - """, - ExternalLabel("hidden", getInstruction(implementation!!.instructions.lastIndex)) - ) - } - + ).addInstruction( + 0, + "invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->" + + "hideMiniplayerSubTexts(Landroid/view/View;)V", + ) // Modern 2 has a broken overlay subtitle view that is always present. // Modern 2 uses the same overlay controls as the regular video player, // and the overlay views are added at runtime. // Add a hook to the overlay class, and pass the added views to extension. + // + // NOTE: Modern 2 uses the same video UI as the regular player except resized to smaller. + // This patch code could be used to hide other player overlays that do not use Litho. youTubePlayerOverlaysLayoutFingerprint.matchOrThrow().let { it.method.apply { it.classDef.methods.add( @@ -352,18 +412,6 @@ val miniplayerPatch = bytecodePatch( // endregion - // region Enable drag and drop. - - if (is_19_23_or_greater) { - miniplayerModernDragAndDropFingerprint.injectLiteralInstructionBooleanCall( - 45628752L, - "$EXTENSION_CLASS_DESCRIPTOR->enableMiniplayerDragAndDrop()Z" - ) - settingArray += "SETTINGS: MINIPLAYER_DRAG_AND_DROP" - } - - // endregion - settingArray += "SETTINGS: MINIPLAYER_TYPE_MODERN" // region add settings diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt index 269bb1f39..fff7be0aa 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt @@ -1,17 +1,69 @@ package app.revanced.patches.youtube.general.spoofappversion +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch +import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.spoof.appversion.baseSpoofAppVersionPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR +import app.revanced.patches.youtube.utils.indexOfGetDrawableInstruction import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_APP_VERSION import app.revanced.patches.youtube.utils.playservice.is_18_34_or_greater import app.revanced.patches.youtube.utils.playservice.is_18_39_or_greater import app.revanced.patches.youtube.utils.playservice.is_18_49_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_23_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch +import app.revanced.patches.youtube.utils.toolBarButtonFingerprint import app.revanced.util.appendAppVersion +import app.revanced.util.fingerprint.methodOrThrow +import app.revanced.util.getReference +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.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +private val spoofAppVersionBytecodePatch = bytecodePatch( + description = "spoofAppVersionBytecodePatch" +) { + + dependsOn(versionCheckPatch) + + execute { + if (!is_19_23_or_greater) { + return@execute + } + + /** + * When spoofing the app version to YouTube 19.20.xx or earlier via Spoof app version on YouTube 19.23.xx+, the Library tab will crash. + * As a temporary workaround, do not set an image in the toolbar when the enum name is UNKNOWN. + */ + toolBarButtonFingerprint.methodOrThrow().apply { + val getDrawableIndex = indexOfGetDrawableInstruction(this) + val enumOrdinalIndex = indexOfFirstInstructionReversedOrThrow(getDrawableIndex) { + opcode == Opcode.INVOKE_INTERFACE && + getReference()?.returnType == "I" + } + val insertIndex = enumOrdinalIndex + 2 + val insertRegister = getInstruction(insertIndex - 1).registerA + val jumpIndex = indexOfFirstInstructionOrThrow(insertIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setImageDrawable" + } + 1 + + addInstructionsWithLabels( + insertIndex, """ + if-eqz v$insertRegister, :ignore + """, ExternalLabel("ignore", getInstruction(jumpIndex)) + ) + } + } + +} @Suppress("unused") val spoofAppVersionPatch = resourcePatch( @@ -22,6 +74,7 @@ val spoofAppVersionPatch = resourcePatch( dependsOn( baseSpoofAppVersionPatch("$GENERAL_CLASS_DESCRIPTOR->getVersionOverride(Ljava/lang/String;)Ljava/lang/String;"), + spoofAppVersionBytecodePatch, settingsPatch, versionCheckPatch, ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt index 1a72c93b1..536c51d9e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt @@ -297,6 +297,7 @@ val toolBarComponentsPatch = bytecodePatch( // endregion + /* // region patch for hide voice search button if (is_19_28_or_greater) { @@ -311,6 +312,7 @@ val toolBarComponentsPatch = bytecodePatch( } // endregion + */ // region patch for hide voice search button diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt index b13ec993e..63f60637e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt @@ -5,6 +5,8 @@ import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.stringOption import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.patch.PatchList.CUSTOM_BRANDING_ICON_FOR_YOUTUBE +import app.revanced.patches.youtube.utils.playservice.is_19_34_or_greater +import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusIcon import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.util.ResourceGroup @@ -12,9 +14,16 @@ import app.revanced.util.Utils.trimIndentMultiline import app.revanced.util.copyFile import app.revanced.util.copyResources import app.revanced.util.copyXmlNode +import app.revanced.util.getAdaptiveIconResourceFile import app.revanced.util.getResourceGroup import app.revanced.util.underBarOrThrow +import java.nio.file.Files +import java.nio.file.StandardCopyOption +private const val ADAPTIVE_ICON_BACKGROUND_FILE_NAME = + "adaptiveproduct_youtube_background_color_108" +private const val ADAPTIVE_ICON_FOREGROUND_FILE_NAME = + "adaptiveproduct_youtube_foreground_color_108" private const val DEFAULT_ICON = "xisr_yellow" private val availableIcon = mapOf( @@ -48,8 +57,8 @@ private val drawableDirectories = sizeArray.map { "drawable-$it" } private val mipmapDirectories = sizeArray.map { "mipmap-$it" } private val launcherIconResourceFileNames = arrayOf( - "adaptiveproduct_youtube_background_color_108", - "adaptiveproduct_youtube_foreground_color_108", + ADAPTIVE_ICON_BACKGROUND_FILE_NAME, + ADAPTIVE_ICON_FOREGROUND_FILE_NAME, "ic_launcher", "ic_launcher_round" ).map { "$it.png" }.toTypedArray() @@ -93,7 +102,10 @@ val customBrandingIconPatch = resourcePatch( ) { compatibleWith(COMPATIBLE_PACKAGE) - dependsOn(settingsPatch) + dependsOn( + settingsPatch, + versionCheckPatch, + ) val appIconOption = stringOption( @@ -191,5 +203,32 @@ val customBrandingIconPatch = resourcePatch( updatePatchStatusIcon(appIcon) } + + if (!is_19_34_or_greater) { + return@execute + } + if (appIcon == "youtube") { + return@execute + } + + mapOf( + ADAPTIVE_ICON_BACKGROUND_FILE_NAME to getAdaptiveIconResourceFile("res/mipmap-anydpi/ic_launcher.xml", "background"), + ADAPTIVE_ICON_FOREGROUND_FILE_NAME to getAdaptiveIconResourceFile("res/mipmap-anydpi/ic_launcher.xml", "foreground") + ).forEach { (oldIconResourceFile, newIconResourceFile) -> + if (oldIconResourceFile != newIconResourceFile) { + mipmapDirectories.forEach { + val mipmapDirectory = get("res").resolve(it) + Files.copy( + mipmapDirectory + .resolve("$oldIconResourceFile.png") + .toPath(), + mipmapDirectory + .resolve("$newIconResourceFile.png") + .toPath(), + StandardCopyOption.REPLACE_EXISTING + ) + } + } + } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt index 5b379ed4e..2f1b27d12 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt @@ -386,6 +386,8 @@ val playerComponentsPatch = bytecodePatch( findMethodOrThrow(syntheticReference) { name == "onClick" }.hookInitVideoPanel(0) + } else { + println("WARNING: target Opcode not found in ${fingerprint.first}") } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt index fba851b85..c0bfcae87 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt @@ -5,6 +5,7 @@ import app.revanced.patches.youtube.utils.resourceid.fadeDurationFast import app.revanced.patches.youtube.utils.resourceid.inlineTimeBarColorizedBarPlayedColorDark import app.revanced.patches.youtube.utils.resourceid.inlineTimeBarPlayedNotHighlightedColor import app.revanced.patches.youtube.utils.resourceid.insetOverlayViewLayout +import app.revanced.patches.youtube.utils.resourceid.menuItemView import app.revanced.patches.youtube.utils.resourceid.scrimOverlay import app.revanced.patches.youtube.utils.resourceid.seekUndoEduOverlayStub import app.revanced.patches.youtube.utils.resourceid.totalTime @@ -12,9 +13,13 @@ import app.revanced.patches.youtube.utils.resourceid.varispeedUnavailableTitle import app.revanced.patches.youtube.utils.resourceid.videoQualityBottomSheet import app.revanced.patches.youtube.utils.sponsorblock.sponsorBlockBytecodePatch import app.revanced.util.fingerprint.legacyFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction import app.revanced.util.or 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.reference.MethodReference internal val engagementPanelBuilderFingerprint = legacyFingerprint( name = "engagementPanelBuilderFingerprint", @@ -164,6 +169,23 @@ internal val seekbarOnDrawFingerprint = legacyFingerprint( customFingerprint = { method, _ -> method.name == "onDraw" } ) +internal fun indexOfGetDrawableInstruction(method: Method) = + method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.toString() == "Landroid/content/res/Resources;->getDrawable(I)Landroid/graphics/drawable/Drawable;" + } + +internal val toolBarButtonFingerprint = legacyFingerprint( + name = "toolBarButtonFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("Landroid/view/MenuItem;"), + literals = listOf(menuItemView), + customFingerprint = { method, _ -> + indexOfGetDrawableInstruction(method) >= 0 + } +) + internal val totalTimeFingerprint = legacyFingerprint( name = "totalTimeFingerprint", returnType = "V", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/compatibility/Constants.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/compatibility/Constants.kt index 39b1a7259..8aee32a6a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/compatibility/Constants.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/compatibility/Constants.kt @@ -14,7 +14,8 @@ internal object Constants { "18.38.44", // This is the last version with no delay in applying video quality on the server side. "18.48.39", // This is the last version that do not use Rolling Number. "19.05.36", // This is the last version with the least YouTube experimental flag. - "19.16.39", // This is the latest version supported by the RVX patch. + "19.16.39", // This is the last version where the 'Restore old seekbar thumbnails' setting works. + "19.34.42", // This is the latest version supported by the RVX patch. ) ) } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt index 034856435..4595a36c8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt @@ -134,8 +134,8 @@ private val playerControlsBytecodePatch = bytecodePatch( changeVisibilityMethod = findMethodOrThrow(EXTENSION_PLAYER_CONTROLS_CLASS_DESCRIPTOR) { - name == "changeVisibility" - && parameters == listOf("Z", "Z") + name == "changeVisibility" && + parameters == listOf("Z", "Z") } changeVisibilityNegatedImmediatelyMethod = diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/Fingerprints.kt index 5a418d9f0..f7208bf7b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/Fingerprints.kt @@ -10,12 +10,6 @@ import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.reference.MethodReference -internal fun indexOfLayoutDirectionInstruction(method: Method) = - method.indexOfFirstInstruction { - opcode == Opcode.INVOKE_VIRTUAL && - getReference().toString() == "Landroid/view/View;->setLayoutDirection(I)V" - } - internal val browseIdClassFingerprint = legacyFingerprint( name = "browseIdClassFingerprint", returnType = "Ljava/lang/Object;", @@ -45,18 +39,26 @@ internal val reelWatchPagerFingerprint = legacyFingerprint( literals = listOf(reelWatchPlayer), ) +internal fun indexOfStringIsEmptyInstruction(method: Method) = + method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference().toString() == "Ljava/lang/String;->isEmpty()Z" + } + internal val searchQueryClassFingerprint = legacyFingerprint( name = "searchQueryClassFingerprint", returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("L", "Ljava/util/Map;"), opcodes = listOf( - Opcode.CHECK_CAST, Opcode.IGET_OBJECT, Opcode.INVOKE_VIRTUAL, Opcode.MOVE_RESULT, ), strings = listOf("force_enable_sticky_browsy_bars"), + customFingerprint = { method, _ -> + indexOfStringIsEmptyInstruction(method) >= 0 + } ) internal val videoStateFingerprint = legacyFingerprint( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/PlayerTypeHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/PlayerTypeHookPatch.kt index 4fef422ff..7edd10a61 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/PlayerTypeHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/PlayerTypeHookPatch.kt @@ -135,32 +135,30 @@ val playerTypeHookPatch = bytecodePatch( // region patch for hook search bar - searchQueryClassFingerprint.matchOrThrow().let { - it.method.apply { - val searchQueryIndex = it.patternMatch!!.startIndex + 1 - val searchQueryFieldReference = getInstruction(searchQueryIndex).reference - val searchQueryClass = (searchQueryFieldReference as FieldReference).definingClass - - findMethodOrThrow(searchQueryClass).apply { - val smaliInstructions = + searchQueryClassFingerprint.methodOrThrow().apply { + val searchQueryIndex = indexOfStringIsEmptyInstruction(this) - 1 + val searchQueryFieldReference = getInstruction(searchQueryIndex).reference + val searchQueryClass = (searchQueryFieldReference as FieldReference).definingClass + + findMethodOrThrow(searchQueryClass).apply { + val smaliInstructions = + """ + if-eqz v0, :ignore + iget-object v0, v0, $searchQueryFieldReference + if-eqz v0, :ignore + return-object v0 + :ignore + const-string v0, "" + return-object v0 """ - if-eqz v0, :ignore - iget-object v0, v0, $searchQueryFieldReference - if-eqz v0, :ignore - return-object v0 - :ignore - const-string v0, "" - return-object v0 - """ - addStaticFieldToExtension( - EXTENSION_ROOT_VIEW_HOOK_CLASS_DESCRIPTOR, - "getSearchQuery", - "searchQueryClass", - definingClass, - smaliInstructions - ) - } + addStaticFieldToExtension( + EXTENSION_ROOT_VIEW_HOOK_CLASS_DESCRIPTOR, + "getSearchQuery", + "searchQueryClass", + definingClass, + smaliInstructions + ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt index 71cecfc5c..5dc7c5d7a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt @@ -23,10 +23,22 @@ var is_19_23_or_greater = false private set var is_19_25_or_greater = false private set +var is_19_26_or_greater = false + private set var is_19_28_or_greater = false private set +var is_19_29_or_greater = false + private set var is_19_32_or_greater = false private set +var is_19_34_or_greater = false + private set +var is_19_36_or_greater = false + private set +var is_19_41_or_greater = false + private set +var is_19_43_or_greater = false + private set var is_19_44_or_greater = false private set @@ -53,8 +65,14 @@ val versionCheckPatch = resourcePatch( is_19_15_or_greater = 241602000 <= playStoreServicesVersion is_19_23_or_greater = 242402000 <= playStoreServicesVersion is_19_25_or_greater = 242599000 <= playStoreServicesVersion + is_19_26_or_greater = 242705000 <= playStoreServicesVersion is_19_28_or_greater = 242905000 <= playStoreServicesVersion + is_19_29_or_greater = 243005000 <= playStoreServicesVersion is_19_32_or_greater = 243305000 <= playStoreServicesVersion + is_19_34_or_greater = 243499000 <= playStoreServicesVersion + is_19_36_or_greater = 243705000 <= playStoreServicesVersion + is_19_41_or_greater = 244305000 <= playStoreServicesVersion + is_19_43_or_greater = 244405000 <= playStoreServicesVersion is_19_44_or_greater = 244505000 <= playStoreServicesVersion } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt index b813e6a84..17b8bba16 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt @@ -120,6 +120,8 @@ var menuItemView = -1L private set var metaPanel = -1L private set +var miniplayerMaxSize = -1L + private set var modernMiniPlayerClose = -1L private set var modernMiniPlayerExpand = -1L @@ -444,6 +446,10 @@ internal val sharedResourceIdPatch = resourcePatch( ID, "metapanel" ] + miniplayerMaxSize = resourceMappings[ + DIMEN, + "miniplayer_max_size", + ] modernMiniPlayerClose = resourceMappings[ ID, "modern_miniplayer_close" diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/toolbar/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/toolbar/Fingerprints.kt index dfaaf14a0..94c741f2e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/toolbar/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/toolbar/Fingerprints.kt @@ -1,26 +1,10 @@ package app.revanced.patches.youtube.utils.toolbar import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH -import app.revanced.patches.youtube.utils.resourceid.menuItemView import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.or import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode -internal val toolBarButtonFingerprint = legacyFingerprint( - name = "toolBarButtonFingerprint", - returnType = "V", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("Landroid/view/MenuItem;"), - opcodes = listOf( - Opcode.INVOKE_INTERFACE, - Opcode.MOVE_RESULT, - Opcode.IGET_OBJECT, - Opcode.IGET_OBJECT, - Opcode.INVOKE_VIRTUAL - ), - literals = listOf(menuItemView), -) internal val toolBarPatchFingerprint = legacyFingerprint( name = "toolBarPatchFingerprint", accessFlags = AccessFlags.PRIVATE or AccessFlags.STATIC, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/toolbar/ToolBarHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/toolbar/ToolBarHookPatch.kt index 250bd839f..262220a64 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/toolbar/ToolBarHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/toolbar/ToolBarHookPatch.kt @@ -6,12 +6,18 @@ import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH +import app.revanced.patches.youtube.utils.indexOfGetDrawableInstruction import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch -import app.revanced.util.fingerprint.matchOrThrow +import app.revanced.patches.youtube.utils.toolBarButtonFingerprint import app.revanced.util.fingerprint.methodOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference private const val EXTENSION_CLASS_DESCRIPTOR = "$UTILS_PATH/ToolBarPatch;" @@ -24,30 +30,35 @@ val toolBarHookPatch = bytecodePatch( dependsOn(sharedResourceIdPatch) execute { - toolBarButtonFingerprint.matchOrThrow().let { - it.method.apply { - val replaceIndex = it.patternMatch!!.startIndex - val freeIndex = it.patternMatch!!.endIndex - 1 - - val replaceReference = getInstruction(replaceIndex).reference - val replaceRegister = - getInstruction(replaceIndex).registerC - val enumRegister = getInstruction(replaceIndex).registerD - val freeRegister = getInstruction(freeIndex).registerA - - val imageViewIndex = replaceIndex + 2 - val imageViewReference = - getInstruction(imageViewIndex).reference - - addInstructions( - replaceIndex + 1, """ - iget-object v$freeRegister, p0, $imageViewReference - invoke-static {v$enumRegister, v$freeRegister}, $EXTENSION_CLASS_DESCRIPTOR->hookToolBar(Ljava/lang/Enum;Landroid/widget/ImageView;)V - invoke-interface {v$replaceRegister, v$enumRegister}, $replaceReference - """ - ) - removeInstruction(replaceIndex) + toolBarButtonFingerprint.methodOrThrow().apply { + val getDrawableIndex = indexOfGetDrawableInstruction(this) + val enumOrdinalIndex = indexOfFirstInstructionReversedOrThrow(getDrawableIndex) { + opcode == Opcode.INVOKE_INTERFACE && + getReference()?.returnType == "I" } + val freeIndex = getDrawableIndex - 1 + + val replaceReference = getInstruction(enumOrdinalIndex).reference + val replaceRegister = + getInstruction(enumOrdinalIndex).registerC + val enumRegister = getInstruction(enumOrdinalIndex).registerD + val freeRegister = getInstruction(freeIndex).registerA + + val imageViewIndex = indexOfFirstInstructionReversedOrThrow(enumOrdinalIndex) { + opcode == Opcode.IGET_OBJECT && + getReference()?.type == "Landroid/widget/ImageView;" + } + val imageViewReference = + getInstruction(imageViewIndex).reference + + addInstructions( + enumOrdinalIndex + 1, """ + iget-object v$freeRegister, p0, $imageViewReference + invoke-static {v$enumRegister, v$freeRegister}, $EXTENSION_CLASS_DESCRIPTOR->hookToolBar(Ljava/lang/Enum;Landroid/widget/ImageView;)V + invoke-interface {v$replaceRegister, v$enumRegister}, $replaceReference + """ + ) + removeInstruction(enumOrdinalIndex) } toolbarMethod = toolBarPatchFingerprint.methodOrThrow() diff --git a/patches/src/main/kotlin/app/revanced/util/ResourceUtils.kt b/patches/src/main/kotlin/app/revanced/util/ResourceUtils.kt index 26f7e5fa2..342a1c6eb 100644 --- a/patches/src/main/kotlin/app/revanced/util/ResourceUtils.kt +++ b/patches/src/main/kotlin/app/revanced/util/ResourceUtils.kt @@ -5,7 +5,6 @@ import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.ResourcePatchContext import app.revanced.patcher.util.Document -import org.w3c.dom.Attr import org.w3c.dom.Element import org.w3c.dom.Node import org.w3c.dom.NodeList @@ -44,26 +43,6 @@ fun Node.cloneNodes(parent: Node) { parent.removeChild(this) } -/** - * Returns a sequence for all child nodes. - */ -fun NodeList.asSequence() = (0 until this.length).asSequence().map { this.item(it) } - -/** - * Returns a sequence for all child nodes. - */ -@Suppress("UNCHECKED_CAST") -fun Node.childElementsSequence() = - this.childNodes.asSequence().filter { it.nodeType == Node.ELEMENT_NODE } as Sequence - -/** - * Performs the given [action] on each child element. - */ -inline fun Node.forEachChildElement(action: (Element) -> Unit) = - childElementsSequence().forEach { - action(it) - } - /** * Recursively traverse the DOM tree starting from the given root node. * @@ -74,20 +53,29 @@ fun Node.doRecursively(action: (Node) -> Unit) { for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action) } -fun String.startsWithAny(vararg prefixes: String): Boolean { - for (prefix in prefixes) - if (this.startsWith(prefix)) - return true - - return false -} - fun List.getResourceGroup(fileNames: Array) = map { directory -> ResourceGroup( directory, *fileNames ) } +fun ResourcePatchContext.getAdaptiveIconResourceFile(path: String, tag: String): String { + document(path).use { document -> + val adaptiveIcon = document + .getElementsByTagName("adaptive-icon") + .item(0) as Element + + val childNodes = adaptiveIcon.childNodes + for (i in 0 until childNodes.length) { + val node = childNodes.item(i) + if (node is Element && node.tagName == tag && node.hasAttribute("android:drawable")) { + return node.getAttribute("android:drawable").split("/")[1] + } + } + throw PatchException("Element not found: $tag") + } +} + fun ResourcePatchContext.appendAppVersion(appVersion: String) { addEntryValues( "revanced_spoof_app_version_target_entries", @@ -227,14 +215,6 @@ fun ResourcePatchContext.removeStringsElements( } } -fun Node.insertFirst(node: Node) { - if (hasChildNodes()) { - insertBefore(node, firstChild) - } else { - appendChild(node) - } -} - fun Node.insertNode(tagName: String, targetNode: Node, block: Element.() -> Unit) { val child = ownerDocument.createElement(tagName) child.block() @@ -339,21 +319,6 @@ internal fun inputStreamFromBundledResource( */ class ResourceGroup(val resourceDirectoryName: String, vararg val resources: String) -/** - * Iterate through the children of a node by its tag. - * @param resource The xml resource. - * @param targetTag The target xml node. - * @param callback The callback to call when iterating over the nodes. - */ -fun ResourcePatchContext.iterateXmlNodeChildren( - resource: String, - targetTag: String, - callback: (node: Node) -> Unit, -) = document(classLoader.getResourceAsStream(resource)!!).use { document -> - val stringsNode = document.getElementsByTagName(targetTag).item(0).childNodes - for (i in 1 until stringsNode.length - 1) callback(stringsNode.item(i)) -} - /** * Copy resources from the current class loader to the resource directory. * @param resourceDirectory The directory of the resource. @@ -428,12 +393,3 @@ internal fun NodeList.findElementByAttributeValue(attributeName: String, value: internal fun NodeList.findElementByAttributeValueOrThrow(attributeName: String, value: String) = findElementByAttributeValue(attributeName, value) ?: throw PatchException("Could not find: $attributeName $value") - -internal fun Element.copyAttributesFrom(oldContainer: Element) { - // Copy attributes from the old element to the new element - val attributes = oldContainer.attributes - for (i in 0 until attributes.length) { - val attr = attributes.item(i) as Attr - setAttribute(attr.name, attr.value) - } -} diff --git a/patches/src/main/resources/youtube/settings/host/values/arrays.xml b/patches/src/main/resources/youtube/settings/host/values/arrays.xml index c26822d4e..d304ed62c 100644 --- a/patches/src/main/resources/youtube/settings/host/values/arrays.xml +++ b/patches/src/main/resources/youtube/settings/host/values/arrays.xml @@ -185,7 +185,8 @@ MEMBERSHIPS_SHORTS_ONLY MEMBERSHIPS_LIVESTREAMS_ONLY - + + @string/revanced_miniplayer_type_entry_0 @string/revanced_miniplayer_type_entry_1 @string/revanced_miniplayer_type_entry_2 @string/revanced_miniplayer_type_entry_3 @@ -193,22 +194,39 @@ @string/revanced_miniplayer_type_entry_5 @string/revanced_miniplayer_type_entry_6 - + + DISABLED ORIGINAL - PHONE + MINIMAL TABLET MODERN_1 MODERN_2 MODERN_3 - + + @string/revanced_miniplayer_type_entry_1 + @string/revanced_miniplayer_type_entry_2 + @string/revanced_miniplayer_type_entry_3 + @string/revanced_miniplayer_type_entry_4 + @string/revanced_miniplayer_type_entry_5 + @string/revanced_miniplayer_type_entry_6 + + + ORIGINAL + MINIMAL + TABLET + MODERN_1 + MODERN_2 + MODERN_3 + + @string/revanced_miniplayer_type_entry_1 @string/revanced_miniplayer_type_entry_2 @string/revanced_miniplayer_type_entry_3 - + ORIGINAL - PHONE + MINIMAL TABLET diff --git a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml index b6b2690ab..bc2a2e772 100644 --- a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -38,19 +38,42 @@ SETTINGS: HOOK_BUTTONS --> + SETTINGS: MINIPLAYER_TYPE_MODERN --> + + + + + SETTINGS: MINIPLAYER_DOUBLE_TAP_ACTION --> + SETTINGS: MINIPLAYER_DRAG_AND_DROP --> + + + + + + + + + + + + + + @@ -144,8 +167,8 @@ SETTINGS: HIDE_LAYOUT_COMPONENTS --> - +