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 -->
-
+