diff --git a/README.md b/README.md
index cb7f710c0..6c0eb44df 100644
--- a/README.md
+++ b/README.md
@@ -134,19 +134,19 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
-| `Change package name` | Changes the package name for Reddit to the name specified in patch options. | 2024.17.0 ~ 2025.12.0 |
-| `Custom branding name for Reddit` | Changes the Reddit app name to the name specified in patch options. | 2024.17.0 ~ 2025.12.0 |
-| `Disable screenshot popup` | Adds an option to disable the popup that appears when taking a screenshot. | 2024.17.0 ~ 2025.12.0 |
-| `Hide Recently Visited shelf` | Adds an option to hide the Recently Visited shelf in the sidebar. | 2024.17.0 ~ 2025.12.0 |
-| `Hide ads` | Adds options to hide ads. | 2024.17.0 ~ 2025.12.0 |
-| `Hide navigation buttons` | Adds options to hide buttons in the navigation bar. | 2024.17.0 ~ 2025.12.0 |
-| `Hide recommended communities shelf` | Adds an option to hide the recommended communities shelves in subreddits. | 2024.17.0 ~ 2025.12.0 |
-| `Open links directly` | Adds an option to skip over redirection URLs in external links. | 2024.17.0 ~ 2025.12.0 |
-| `Open links externally` | Adds an option to always open links in your browser instead of in the in-app-browser. | 2024.17.0 ~ 2025.12.0 |
-| `Premium icon` | Unlocks premium app icons. | 2024.17.0 ~ 2025.12.0 |
-| `Remove subreddit dialog` | Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically. | 2024.17.0 ~ 2025.12.0 |
-| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 2024.17.0 ~ 2025.12.0 |
-| `Settings for Reddit` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 2024.17.0 ~ 2025.12.0 |
+| `Change package name` | Changes the package name for Reddit to the name specified in patch options. | 2024.17.0 ~ 2025.12.1 |
+| `Custom branding name for Reddit` | Changes the Reddit app name to the name specified in patch options. | 2024.17.0 ~ 2025.12.1 |
+| `Disable screenshot popup` | Adds an option to disable the popup that appears when taking a screenshot. | 2024.17.0 ~ 2025.12.1 |
+| `Hide Recently Visited shelf` | Adds an option to hide the Recently Visited shelf in the sidebar. | 2024.17.0 ~ 2025.12.1 |
+| `Hide ads` | Adds options to hide ads. | 2024.17.0 ~ 2025.12.1 |
+| `Hide navigation buttons` | Adds options to hide buttons in the navigation bar. | 2024.17.0 ~ 2025.12.1 |
+| `Hide recommended communities shelf` | Adds an option to hide the recommended communities shelves in subreddits. | 2024.17.0 ~ 2025.12.1 |
+| `Open links directly` | Adds an option to skip over redirection URLs in external links. | 2024.17.0 ~ 2025.12.1 |
+| `Open links externally` | Adds an option to always open links in your browser instead of in the in-app-browser. | 2024.17.0 ~ 2025.12.1 |
+| `Premium icon` | Unlocks premium app icons. | 2024.17.0 ~ 2025.12.1 |
+| `Remove subreddit dialog` | Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically. | 2024.17.0 ~ 2025.12.1 |
+| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 2024.17.0 ~ 2025.12.1 |
+| `Settings for Reddit` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 2024.17.0 ~ 2025.12.1 |
@@ -200,7 +200,7 @@ Example:
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": []
diff --git a/extensions/shared/src/main/java/app/revanced/extension/reddit/patches/RemoveSubRedditDialogPatch.java b/extensions/shared/src/main/java/app/revanced/extension/reddit/patches/RemoveSubRedditDialogPatch.java
index dc6672633..fae8d0627 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/reddit/patches/RemoveSubRedditDialogPatch.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/reddit/patches/RemoveSubRedditDialogPatch.java
@@ -2,7 +2,10 @@ package app.revanced.extension.reddit.patches;
import static app.revanced.extension.shared.utils.StringRef.str;
+import android.app.Dialog;
import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -34,6 +37,35 @@ public class RemoveSubRedditDialogPatch {
return Settings.REMOVE_NSFW_DIALOG.get() || hasBeenVisited;
}
+ public static void dismissNSFWDialog(Object customDialog) {
+ if (Settings.REMOVE_NSFW_DIALOG.get() &&
+ customDialog instanceof Dialog dialog) {
+ Window window = dialog.getWindow();
+ if (window != null) {
+ WindowManager.LayoutParams params = window.getAttributes();
+ params.height = 0;
+ params.width = 0;
+
+ // Change the size of dialog to 0.
+ window.setAttributes(params);
+
+ // Disable dialog's background dim.
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+ // Hide DecorView.
+ View decorView = window.getDecorView();
+ decorView.setVisibility(View.GONE);
+
+ // Dismiss dialog.
+ dialog.dismiss();
+ }
+ }
+ }
+
+ public static boolean removeNSFWDialog() {
+ return Settings.REMOVE_NSFW_DIALOG.get();
+ }
+
public static boolean spoofLoggedInStatus(boolean isLoggedIn) {
return !Settings.REMOVE_NOTIFICATION_DIALOG.get() && isLoggedIn;
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/reddit/patches/ScreenshotPopupPatch.java b/extensions/shared/src/main/java/app/revanced/extension/reddit/patches/ScreenshotPopupPatch.java
index 7216ea55c..9c84cdbec 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/reddit/patches/ScreenshotPopupPatch.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/reddit/patches/ScreenshotPopupPatch.java
@@ -5,7 +5,7 @@ import app.revanced.extension.reddit.settings.Settings;
@SuppressWarnings("unused")
public class ScreenshotPopupPatch {
- public static boolean disableScreenshotPopup() {
- return Settings.DISABLE_SCREENSHOT_POPUP.get();
+ public static Boolean disableScreenshotPopup(Boolean original) {
+ return Settings.DISABLE_SCREENSHOT_POPUP.get() ? Boolean.FALSE : original;
}
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java
index 6ee95843d..2c43c80b0 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java
@@ -183,8 +183,8 @@ public class PlayerPatch {
// The type of descriptionView can be either ViewGroup or TextView. (A/B tests)
// If the type of descriptionView is TextView, longer delay is required.
final long delayMillis = descriptionView instanceof TextView
- ? 500
- : 100;
+ ? 750
+ : 200;
Utils.runOnMainThreadDelayed(() -> Utils.clickView(descriptionView), delayMillis);
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PatchStatus.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PatchStatus.java
index d5317e396..cf8efd5db 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PatchStatus.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PatchStatus.java
@@ -9,6 +9,11 @@ public class PatchStatus {
return false;
}
+ // Modified by a patch. Do not touch.
+ public static boolean OldSeekbarThumbnailsDefaultBoolean() {
+ return false;
+ }
+
public static boolean OldSplashAnimation() {
// Replace this with true if the Restore old splash animation (Custom branding icon) succeeds
return false;
@@ -40,23 +45,22 @@ public class PatchStatus {
return false;
}
- public static String SpoofAppVersionDefaultString() {
- return "18.17.43";
- }
-
public static boolean ToolBarComponents() {
// Replace this with true if the Toolbar components patch succeeds
return false;
}
+ public static long PatchedTime() {
+ return 0L;
+ }
+
+ public static String SpoofAppVersionDefaultString() {
+ return "18.17.43";
+ }
+
// Modified by a patch. Do not touch.
public static String RVXMusicPackageName() {
return "com.google.android.apps.youtube.music";
}
- // Modified by a patch. Do not touch.
- public static boolean OldSeekbarThumbnailsDefaultBoolean() {
- return false;
- }
-
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlaylistPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlaylistPatch.java
index 484ad319a..98ebe9f3c 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlaylistPatch.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlaylistPatch.java
@@ -1,6 +1,10 @@
package app.revanced.extension.youtube.patches.utils;
import static app.revanced.extension.shared.utils.StringRef.str;
+import static app.revanced.extension.shared.utils.Utils.runOnMainThreadDelayed;
+import static app.revanced.extension.youtube.utils.VideoUtils.dismissPlayer;
+import static app.revanced.extension.youtube.utils.VideoUtils.launchVideoExternalDownloader;
+import static app.revanced.extension.youtube.utils.VideoUtils.openPlaylist;
import android.content.Context;
import android.view.KeyEvent;
@@ -30,30 +34,20 @@ import app.revanced.extension.youtube.patches.utils.requests.EditPlaylistRequest
import app.revanced.extension.youtube.patches.utils.requests.GetPlaylistsRequest;
import app.revanced.extension.youtube.patches.utils.requests.SavePlaylistRequest;
import app.revanced.extension.youtube.settings.Settings;
+import app.revanced.extension.youtube.shared.PlayerType;
+import app.revanced.extension.youtube.shared.VideoInformation;
+import app.revanced.extension.youtube.utils.AuthUtils;
import app.revanced.extension.youtube.utils.ExtendedUtils;
-import app.revanced.extension.youtube.utils.VideoUtils;
import kotlin.Pair;
// TODO: Implement sync queue and clean up code.
@SuppressWarnings({"unused", "StaticFieldLeak"})
-public class PlaylistPatch extends VideoUtils {
- private static final String AUTHORIZATION_HEADER = "Authorization";
- private static final String[] REQUEST_HEADER_KEYS = {
- AUTHORIZATION_HEADER,
- "X-GOOG-API-FORMAT-VERSION",
- "X-Goog-Visitor-Id"
- };
+public class PlaylistPatch extends AuthUtils {
private static final boolean QUEUE_MANAGER =
Settings.OVERLAY_BUTTON_EXTERNAL_DOWNLOADER_QUEUE_MANAGER.get()
|| Settings.OVERRIDE_VIDEO_DOWNLOAD_BUTTON_QUEUE_MANAGER.get();
private static Context mContext;
- private static volatile String authorization = "";
- public static volatile String dataSyncId = "";
- public static volatile boolean isIncognito = false;
- private static volatile Map requestHeader;
- private static volatile String playlistId = "";
- private static volatile String videoId = "";
private static String checkFailedAuth;
private static String checkFailedPlaylistId;
@@ -124,6 +118,7 @@ public class PlaylistPatch extends VideoUtils {
if (videoId != null) {
lastVideoIds.remove(videoId, setVideoId);
EditPlaylistRequest.clearVideoId(videoId);
+ Logger.printDebug(() -> "Video removed by YouTube flyout menu: " + videoId);
}
}
}
@@ -138,30 +133,6 @@ public class PlaylistPatch extends VideoUtils {
}
}
- /**
- * Injection point.
- */
- public static void setRequestHeaders(String url, Map requestHeaders) {
- if (QUEUE_MANAGER) {
- try {
- // Save requestHeaders whenever an account is switched.
- String auth = requestHeaders.get(AUTHORIZATION_HEADER);
- if (auth == null || authorization.equals(auth)) {
- return;
- }
- for (String key : REQUEST_HEADER_KEYS) {
- if (requestHeaders.get(key) == null) {
- return;
- }
- }
- authorization = auth;
- requestHeader = requestHeaders;
- } catch (Exception ex) {
- Logger.printException(() -> "setRequestHeaders failure", ex);
- }
- }
- }
-
/**
* Invoked by extension.
*/
@@ -182,9 +153,22 @@ public class PlaylistPatch extends VideoUtils {
} else {
videoId = currentVideoId;
synchronized (lastVideoIds) {
- QueueManager[] customActionsEntries = playlistId.isEmpty() || lastVideoIds.get(currentVideoId) == null
- ? QueueManager.addToQueueEntries
- : QueueManager.removeFromQueueEntries;
+ QueueManager[] customActionsEntries;
+ boolean canReload = PlayerType.getCurrent().isMaximizedOrFullscreen() &&
+ lastVideoIds.get(VideoInformation.getVideoId()) != null;
+ if (playlistId.isEmpty() || lastVideoIds.get(currentVideoId) == null) {
+ if (canReload) {
+ customActionsEntries = QueueManager.addToQueueWithReloadEntries;
+ } else {
+ customActionsEntries = QueueManager.addToQueueEntries;
+ }
+ } else {
+ if (canReload) {
+ customActionsEntries = QueueManager.removeFromQueueWithReloadEntries;
+ } else {
+ customActionsEntries = QueueManager.removeFromQueueEntries;
+ }
+ }
buildBottomSheetDialog(customActionsEntries);
}
@@ -213,7 +197,8 @@ public class PlaylistPatch extends VideoUtils {
ExtendedUtils.showBottomSheetDialog(mContext, mScrollView, actionsMap);
}
- private static void fetchQueue(boolean remove, boolean openPlaylist, boolean openVideo) {
+ private static void fetchQueue(boolean remove, boolean openPlaylist,
+ boolean openVideo, boolean reload) {
try {
String currentPlaylistId = playlistId;
String currentVideoId = videoId;
@@ -233,7 +218,7 @@ public class PlaylistPatch extends VideoUtils {
showToast(fetchSucceededCreate);
Logger.printDebug(() -> "Queue successfully created, playlistId: " + createdPlaylistId + ", setVideoId: " + setVideoId);
if (openPlaylist) {
- openQueue(currentVideoId, openVideo);
+ openQueue(currentVideoId, openVideo, reload);
}
return;
}
@@ -251,22 +236,24 @@ public class PlaylistPatch extends VideoUtils {
String fetchedSetVideoId = request.getResult();
Logger.printDebug(() -> "fetchedSetVideoId: " + fetchedSetVideoId);
if (remove) { // Remove from queue.
- if (StringUtils.isEmpty(fetchedSetVideoId)) {
+ if ("".equals(fetchedSetVideoId)) {
lastVideoIds.remove(currentVideoId, setVideoId);
+ EditPlaylistRequest.clearVideoId(currentVideoId);
showToast(fetchSucceededRemove);
if (openPlaylist) {
- openQueue(currentVideoId, openVideo);
+ openQueue(currentVideoId, openVideo, reload);
}
return;
}
showToast(fetchFailedRemove);
} else { // Add to queue.
- if (StringUtils.isNotEmpty(fetchedSetVideoId)) {
+ if (fetchedSetVideoId != null && !fetchedSetVideoId.isEmpty()) {
lastVideoIds.putIfAbsent(currentVideoId, fetchedSetVideoId);
+ EditPlaylistRequest.clearVideoId(currentVideoId);
showToast(fetchSucceededAdd);
Logger.printDebug(() -> "Video successfully added, setVideoId: " + fetchedSetVideoId);
if (openPlaylist) {
- openQueue(currentVideoId, openVideo);
+ openQueue(currentVideoId, openVideo, reload);
}
return;
}
@@ -388,10 +375,10 @@ public class PlaylistPatch extends VideoUtils {
}
private static void openQueue() {
- openQueue("", false);
+ openQueue("", false, false);
}
- private static void openQueue(String currentVideoId, boolean openVideo) {
+ private static void openQueue(String currentVideoId, boolean openVideo, boolean reload) {
String currentPlaylistId = playlistId;
if (currentPlaylistId.isEmpty()) {
handleCheckError(checkFailedQueue);
@@ -403,7 +390,15 @@ public class PlaylistPatch extends VideoUtils {
return;
}
// Open a video from a playlist
- openPlaylist(currentPlaylistId, currentVideoId);
+ if (reload) {
+ // Since the Queue is not automatically synced, a 'reload' action has been added as a workaround.
+ // The 'reload' action simply closes the video and reopens it.
+ // It is important to close the video, otherwise the Queue will not be updated.
+ dismissPlayer();
+ openPlaylist(currentPlaylistId, VideoInformation.getVideoId(), true);
+ } else {
+ openPlaylist(currentPlaylistId, currentVideoId);
+ }
} else {
// Open a playlist
openPlaylist(currentPlaylistId);
@@ -422,27 +417,37 @@ public class PlaylistPatch extends VideoUtils {
ADD_TO_QUEUE(
"revanced_queue_manager_add_to_queue",
"yt_outline_list_add_black_24",
- () -> fetchQueue(false, false, false)
+ () -> fetchQueue(false, false, false, false)
),
ADD_TO_QUEUE_AND_OPEN_QUEUE(
"revanced_queue_manager_add_to_queue_and_open_queue",
"yt_outline_list_add_black_24",
- () -> fetchQueue(false, true, false)
+ () -> fetchQueue(false, true, false, false)
),
ADD_TO_QUEUE_AND_PLAY_VIDEO(
"revanced_queue_manager_add_to_queue_and_play_video",
"yt_outline_list_play_arrow_black_24",
- () -> fetchQueue(false, true, true)
+ () -> fetchQueue(false, true, true, false)
+ ),
+ ADD_TO_QUEUE_AND_RELOAD_VIDEO(
+ "revanced_queue_manager_add_to_queue_and_reload_video",
+ "yt_outline_arrow_circle_black_24",
+ () -> fetchQueue(false, true, true, true)
),
REMOVE_FROM_QUEUE(
"revanced_queue_manager_remove_from_queue",
"yt_outline_trash_can_black_24",
- () -> fetchQueue(true, false, false)
+ () -> fetchQueue(true, false, false, false)
),
REMOVE_FROM_QUEUE_AND_OPEN_QUEUE(
"revanced_queue_manager_remove_from_queue_and_open_queue",
"yt_outline_trash_can_black_24",
- () -> fetchQueue(true, true, false)
+ () -> fetchQueue(true, true, false, false)
+ ),
+ REMOVE_FROM_QUEUE_AND_RELOAD_VIDEO(
+ "revanced_queue_manager_remove_from_queue_and_reload_video",
+ "yt_outline_arrow_circle_black_24",
+ () -> fetchQueue(true, true, true, true)
),
OPEN_QUEUE(
"revanced_queue_manager_open_queue",
@@ -490,6 +495,17 @@ public class PlaylistPatch extends VideoUtils {
SAVE_QUEUE,
};
+ public static final QueueManager[] addToQueueWithReloadEntries = {
+ ADD_TO_QUEUE,
+ ADD_TO_QUEUE_AND_OPEN_QUEUE,
+ ADD_TO_QUEUE_AND_PLAY_VIDEO,
+ ADD_TO_QUEUE_AND_RELOAD_VIDEO,
+ OPEN_QUEUE,
+ //REMOVE_QUEUE,
+ EXTERNAL_DOWNLOADER,
+ SAVE_QUEUE,
+ };
+
public static final QueueManager[] removeFromQueueEntries = {
REMOVE_FROM_QUEUE,
REMOVE_FROM_QUEUE_AND_OPEN_QUEUE,
@@ -499,6 +515,16 @@ public class PlaylistPatch extends VideoUtils {
SAVE_QUEUE,
};
+ public static final QueueManager[] removeFromQueueWithReloadEntries = {
+ REMOVE_FROM_QUEUE,
+ REMOVE_FROM_QUEUE_AND_OPEN_QUEUE,
+ REMOVE_FROM_QUEUE_AND_RELOAD_VIDEO,
+ OPEN_QUEUE,
+ //REMOVE_QUEUE,
+ EXTERNAL_DOWNLOADER,
+ SAVE_QUEUE,
+ };
+
public static final QueueManager[] noVideoIdQueueEntries = {
OPEN_QUEUE,
//REMOVE_QUEUE,
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/EditPlaylistRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/EditPlaylistRequest.kt
index a76277543..aa369b8a5 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/EditPlaylistRequest.kt
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/EditPlaylistRequest.kt
@@ -8,7 +8,6 @@ import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.EDIT_PLA
import app.revanced.extension.shared.requests.Requester
import app.revanced.extension.shared.utils.Logger
import app.revanced.extension.shared.utils.Utils
-import org.apache.commons.lang3.StringUtils
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
@@ -216,7 +215,7 @@ class EditPlaylistRequest private constructor(
dataSyncId,
)
if (json != null) {
- return parseResponse(json, StringUtils.isNotEmpty(setVideoId))
+ return parseResponse(json, setVideoId != null && setVideoId.isNotEmpty())
}
return null
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java
index 58897b35d..895a793a2 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java
@@ -3,10 +3,14 @@ package app.revanced.extension.youtube.patches.video;
import static app.revanced.extension.shared.utils.StringRef.str;
import static app.revanced.extension.youtube.shared.RootView.isShortsActive;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import org.apache.commons.lang3.BooleanUtils;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.FloatSetting;
import app.revanced.extension.shared.utils.Logger;
@@ -28,48 +32,61 @@ public class PlaybackSpeedPatch {
Settings.DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC.get();
private static final long TOAST_DELAY_MILLISECONDS = 750;
private static long lastTimeSpeedChanged;
+
+ /**
+ * The last used playback speed.
+ * This value is used when the default playback speed is 'Auto'.
+ */
private static float lastSelectedPlaybackSpeed = 1.0f;
+ private static float lastSelectedShortsPlaybackSpeed = 1.0f;
- private static volatile String channelId = "";
- private static volatile String videoId = "";
- private static boolean isLiveStream;
+ /**
+ * The last regular video id.
+ */
+ private static String videoId = "";
- private static volatile String channelIdShorts = "";
- private static volatile String videoIdShorts = "";
- private static boolean isLiveStreamShorts;
+ @GuardedBy("itself")
+ private static final Map ignoredPlaybackSpeedVideoIds = new LinkedHashMap<>() {
+ private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 3;
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK;
+ }
+ };
/**
* Injection point.
+ * This method is used to reset the playback speed to 1.0 when a general video is started, whether it is a live stream, music, or whitelist.
*/
public static void newVideoStarted(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName,
@NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle,
final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) {
if (isShortsActive()) {
- channelIdShorts = newlyLoadedChannelId;
- videoIdShorts = newlyLoadedVideoId;
- isLiveStreamShorts = newlyLoadedLiveStreamValue;
-
- Logger.printDebug(() -> "newVideoStarted: " + newlyLoadedVideoId);
- } else {
- channelId = newlyLoadedChannelId;
- videoId = newlyLoadedVideoId;
- isLiveStream = newlyLoadedLiveStreamValue;
-
- Logger.printDebug(() -> "newShortsVideoStarted: " + newlyLoadedVideoId);
+ return;
}
- }
+ if (videoId.equals(newlyLoadedVideoId)) {
+ return;
+ }
+ videoId = newlyLoadedVideoId;
- /**
- * Injection point.
- */
- public static void newShortsVideoStarted(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName,
- @NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle,
- final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) {
- channelIdShorts = newlyLoadedChannelId;
- videoIdShorts = newlyLoadedVideoId;
- isLiveStreamShorts = newlyLoadedLiveStreamValue;
+ boolean isMusic = isMusic(newlyLoadedVideoId);
+ boolean isWhitelisted = Whitelist.isChannelWhitelistedPlaybackSpeed(newlyLoadedVideoId);
- Logger.printDebug(() -> "newShortsVideoStarted: " + newlyLoadedVideoId);
+ if (newlyLoadedLiveStreamValue || isMusic || isWhitelisted) {
+ synchronized(ignoredPlaybackSpeedVideoIds) {
+ if (!ignoredPlaybackSpeedVideoIds.containsKey(newlyLoadedVideoId)) {
+ lastSelectedPlaybackSpeed = 1.0f;
+ ignoredPlaybackSpeedVideoIds.put(newlyLoadedVideoId, lastSelectedPlaybackSpeed);
+
+ VideoInformation.setPlaybackSpeed(lastSelectedPlaybackSpeed);
+ VideoInformation.overridePlaybackSpeed(lastSelectedPlaybackSpeed);
+
+ Logger.printDebug(() -> "changing playback speed to: 1.0, isLiveStream: " + newlyLoadedLiveStreamValue +
+ ", isMusic: " + isMusic + ", isWhitelisted: " + isWhitelisted);
+ }
+ }
+ }
}
/**
@@ -98,32 +115,29 @@ public class PlaybackSpeedPatch {
/**
* Injection point.
+ * This method is called every second for regular videos and Shorts.
*/
public static float getPlaybackSpeed(float playbackSpeed) {
boolean isShorts = isShortsActive();
- String currentChannelId = isShorts ? channelIdShorts : channelId;
- String currentVideoId = isShorts ? videoIdShorts : videoId;
- boolean currentVideoIsLiveStream = isShorts ? isLiveStreamShorts : isLiveStream;
- boolean currentVideoIsWhitelisted = Whitelist.isChannelWhitelistedPlaybackSpeed(currentChannelId);
- boolean currentVideoIsMusic = !isShorts && isMusic();
-
- if (currentVideoIsLiveStream || currentVideoIsWhitelisted || currentVideoIsMusic) {
- Logger.printDebug(() -> "changing playback speed to: 1.0");
- VideoInformation.setPlaybackSpeed(1.0f);
- return 1.0f;
- }
-
float defaultPlaybackSpeed = isShorts ? DEFAULT_PLAYBACK_SPEED_SHORTS.get() : DEFAULT_PLAYBACK_SPEED.get();
- if (defaultPlaybackSpeed < 0) {
- float finalPlaybackSpeed = isShorts ? playbackSpeed : lastSelectedPlaybackSpeed;
+ if (defaultPlaybackSpeed < 0) { // If the default playback speed is 'Auto', it will be overridden to the last used playback speed.
+ float finalPlaybackSpeed = isShorts ? lastSelectedShortsPlaybackSpeed : lastSelectedPlaybackSpeed;
VideoInformation.overridePlaybackSpeed(finalPlaybackSpeed);
Logger.printDebug(() -> "changing playback speed to: " + finalPlaybackSpeed);
return finalPlaybackSpeed;
- } else {
- if (isShorts) {
- VideoInformation.setPlaybackSpeed(defaultPlaybackSpeed);
+ } else { // Otherwise the default playback speed is used.
+ synchronized (ignoredPlaybackSpeedVideoIds) {
+ if (isShorts) {
+ // For Shorts, the VideoInformation.overridePlaybackSpeed() method is not used, so manually save the playback speed in VideoInformation.
+ VideoInformation.setPlaybackSpeed(defaultPlaybackSpeed);
+ } else if (ignoredPlaybackSpeedVideoIds.containsKey(videoId)) {
+ // For general videos, check whether the default video playback speed should not be applied.
+ Logger.printDebug(() -> "changing playback speed to: 1.0");
+ return 1.0f;
+ }
}
+
Logger.printDebug(() -> "changing playback speed to: " + defaultPlaybackSpeed);
return defaultPlaybackSpeed;
}
@@ -138,6 +152,19 @@ public class PlaybackSpeedPatch {
public static void userSelectedPlaybackSpeed(float playbackSpeed) {
try {
boolean isShorts = isShortsActive();
+
+ // Saves the user-selected playback speed in the method.
+ if (isShorts) {
+ lastSelectedShortsPlaybackSpeed = playbackSpeed;
+ } else {
+ lastSelectedPlaybackSpeed = playbackSpeed;
+ // If the user has manually changed the playback speed, the whitelist has already been applied.
+ // If there is a videoId on the map, it will be removed.
+ synchronized (ignoredPlaybackSpeedVideoIds) {
+ ignoredPlaybackSpeedVideoIds.remove(videoId);
+ }
+ }
+
if (PatchStatus.RememberPlaybackSpeed()) {
BooleanSetting rememberPlaybackSpeedLastSelectedSetting = isShorts
? Settings.REMEMBER_PLAYBACK_SPEED_SHORTS_LAST_SELECTED
@@ -178,15 +205,23 @@ public class PlaybackSpeedPatch {
}
}, TOAST_DELAY_MILLISECONDS);
}
- } else if (!isShorts) {
- lastSelectedPlaybackSpeed = playbackSpeed;
}
} catch (Exception ex) {
Logger.printException(() -> "userSelectedPlaybackSpeed failure", ex);
}
}
- private static boolean isMusic() {
+ /**
+ * Injection point.
+ */
+ public static void onDismiss() {
+ synchronized (ignoredPlaybackSpeedVideoIds) {
+ ignoredPlaybackSpeedVideoIds.remove(videoId);
+ videoId = "";
+ }
+ }
+
+ private static boolean isMusic(String videoId) {
if (DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC && !videoId.isEmpty()) {
try {
MusicRequest request = MusicRequest.getRequestForVideoId(videoId);
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedSettingsPreference.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedSettingsPreference.java
index e4cc256bf..3cb96c7ae 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedSettingsPreference.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedSettingsPreference.java
@@ -6,6 +6,8 @@ import static app.revanced.extension.shared.utils.Utils.isSDKAbove;
import android.preference.Preference;
import android.preference.SwitchPreference;
+import java.util.Date;
+
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.youtube.patches.general.ChangeFormFactorPatch;
import app.revanced.extension.youtube.patches.utils.PatchStatus;
@@ -44,6 +46,7 @@ public class ReVancedSettingsPreference extends ReVancedPreferenceFragment {
AmbientModePreferenceLinks();
FullScreenPanelPreferenceLinks();
NavigationPreferenceLinks();
+ PatchInformationPreferenceLinks();
RYDPreferenceLinks();
SeekBarPreferenceLinks();
ShortsPreferenceLinks();
@@ -143,6 +146,26 @@ public class ReVancedSettingsPreference extends ReVancedPreferenceFragment {
);
}
+ /**
+ * Set patch information preference summary
+ */
+ private static void PatchInformationPreferenceLinks() {
+ Preference appNamePreference = mPreferenceManager.findPreference("revanced_app_name");
+ if (appNamePreference != null) {
+ appNamePreference.setSummary(ExtendedUtils.getAppLabel());
+ }
+ Preference appVersionPreference = mPreferenceManager.findPreference("revanced_app_version");
+ if (appVersionPreference != null) {
+ appVersionPreference.setSummary(ExtendedUtils.getAppVersionName());
+ }
+ Preference patchedDatePreference = mPreferenceManager.findPreference("revanced_patched_date");
+ if (patchedDatePreference != null) {
+ long patchedTime = PatchStatus.PatchedTime();
+ Date date = new Date(patchedTime);
+ patchedDatePreference.setSummary(date.toLocaleString());
+ }
+ }
+
/**
* Enable/Disable Preference related to RYD settings
*/
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/AuthUtils.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/AuthUtils.java
new file mode 100644
index 000000000..8e015700e
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/AuthUtils.java
@@ -0,0 +1,40 @@
+package app.revanced.extension.youtube.utils;
+
+import java.util.Map;
+
+import app.revanced.extension.shared.utils.Logger;
+
+@SuppressWarnings("unused")
+public class AuthUtils {
+ public static final String AUTHORIZATION_HEADER = "Authorization";
+ public static final String[] REQUEST_HEADER_KEYS = {
+ AUTHORIZATION_HEADER,
+ "X-GOOG-API-FORMAT-VERSION",
+ "X-Goog-Visitor-Id"
+ };
+ public static volatile String authorization = "";
+ public static volatile String dataSyncId = "";
+ public static volatile boolean isIncognito = false;
+ public static volatile Map requestHeader;
+ public static volatile String playlistId = "";
+ public static volatile String videoId = "";
+
+ public static void setRequestHeaders(String url, Map requestHeaders) {
+ try {
+ // Save requestHeaders whenever an account is switched.
+ String auth = requestHeaders.get(AUTHORIZATION_HEADER);
+ if (auth == null || authorization.equals(auth)) {
+ return;
+ }
+ for (String key : REQUEST_HEADER_KEYS) {
+ if (requestHeaders.get(key) == null) {
+ return;
+ }
+ }
+ authorization = auth;
+ requestHeader = requestHeaders;
+ } catch (Exception ex) {
+ Logger.initializationException(AuthUtils.class, "setRequestHeaders failure", ex);
+ }
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/VideoUtils.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/VideoUtils.java
index 6d8d407c4..1671e734c 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/VideoUtils.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/VideoUtils.java
@@ -133,13 +133,25 @@ public class VideoUtils extends IntentUtils {
}
public static void openPlaylist(@NonNull String playlistId, @NonNull String videoId) {
+ openPlaylist(playlistId, videoId, false);
+ }
+
+ public static void openPlaylist(@NonNull String playlistId, @NonNull String videoId, boolean withTimestamp) {
final StringBuilder sb = new StringBuilder();
if (videoId.isEmpty()) {
sb.append(getPlaylistUrl(playlistId));
} else {
- sb.append(getVideoScheme(videoId, false));
- sb.append("&list=");
+ sb.append(VIDEO_URL);
+ sb.append(videoId);
+ sb.append("?list=");
sb.append(playlistId);
+ if (withTimestamp) {
+ final long currentVideoTimeInSeconds = VideoInformation.getVideoTimeInSeconds();
+ if (currentVideoTimeInSeconds > 0) {
+ sb.append("&t=");
+ sb.append(currentVideoTimeInSeconds);
+ }
+ }
}
launchView(sb.toString(), getContext().getPackageName());
}
@@ -269,6 +281,13 @@ public class VideoUtils extends IntentUtils {
return !isExternalDownloaderLaunched.get() && original;
}
+ /**
+ * Rest of the implementation added by patch.
+ */
+ public static void dismissPlayer() {
+ Logger.printDebug(() -> "Dismiss player");
+ }
+
/**
* Rest of the implementation added by patch.
*/
diff --git a/gradle.properties b/gradle.properties
index fbb95566f..f6daeb24a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,5 +4,5 @@ org.gradle.parallel = true
android.useAndroidX = true
kotlin.code.style = official
kotlin.jvm.target.validation.mode = IGNORE
-version = 5.6.1
+version = 5.6.2
diff --git a/patches.json b/patches.json
index cff3a24c6..7a1f5fe8e 100644
--- a/patches.json
+++ b/patches.json
@@ -188,7 +188,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": [
@@ -485,7 +485,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": [
@@ -1030,7 +1030,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": []
@@ -1309,6 +1309,15 @@
"Clone": "com.rvx.android.apps.youtube.music",
"Default": "app.rvx.android.apps.youtube.music"
}
+ },
+ {
+ "key": "patchAllManifest",
+ "title": "Patch all manifest components",
+ "description": "Patch all permissions, intents and content provider authorities supported by GmsCore.",
+ "required": true,
+ "type": "kotlin.Boolean",
+ "default": true,
+ "values": null
}
]
},
@@ -1373,6 +1382,15 @@
"Clone": "com.rvx.android.apps.youtube.music",
"Default": "app.rvx.android.apps.youtube.music"
}
+ },
+ {
+ "key": "patchAllManifest",
+ "title": "Patch all manifest components",
+ "description": "Patch all permissions, intents and content provider authorities supported by GmsCore.",
+ "required": true,
+ "type": "kotlin.Boolean",
+ "default": true,
+ "values": null
}
]
},
@@ -1387,7 +1405,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": []
@@ -1536,7 +1554,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": []
@@ -1688,7 +1706,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": []
@@ -1789,7 +1807,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": []
@@ -1987,7 +2005,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": []
@@ -2004,7 +2022,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": []
@@ -2160,7 +2178,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": []
@@ -2211,13 +2229,14 @@
"description": "Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically.",
"use": true,
"dependencies": [
- "Settings for Reddit"
+ "Settings for Reddit",
+ "ResourcePatch"
],
"compatiblePackages": {
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": []
@@ -2403,7 +2422,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": []
@@ -2463,7 +2482,7 @@
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1",
- "2025.12.0"
+ "2025.12.1"
]
},
"options": [
@@ -2508,7 +2527,7 @@
"description": "The settings menu name that the RVX settings menu should be above.",
"required": true,
"type": "kotlin.String",
- "default": "@string/about_key",
+ "default": "@string/parent_tools_key",
"values": {
"Parent settings": "@string/parent_tools_key",
"General": "@string/general_key",
@@ -3095,6 +3114,7 @@
"BytecodePatch",
"BytecodePatch",
"BytecodePatch",
+ "BytecodePatch",
"ResourcePatch"
],
"compatiblePackages": {
diff --git a/patches/api/patches.api b/patches/api/patches.api
index 11f542538..a314dc173 100644
--- a/patches/api/patches.api
+++ b/patches/api/patches.api
@@ -458,6 +458,10 @@ public final class app/revanced/patches/reddit/utils/extension/SharedExtensionPa
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
+public final class app/revanced/patches/reddit/utils/resourceid/SharedResourceIdPatchKt {
+ public static final fun getNsfwDialogTitle ()J
+}
+
public final class app/revanced/patches/reddit/utils/settings/SettingsPatchKt {
public static final fun getSettingsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
public static final fun is_2024_26_or_greater ()Z
@@ -891,6 +895,10 @@ public final class app/revanced/patches/youtube/utils/FingerprintsKt {
public static final fun indexOfSpannedCharSequenceInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
}
+public final class app/revanced/patches/youtube/utils/auth/AuthHookPatchKt {
+ public static final fun getAuthHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
+}
+
public final class app/revanced/patches/youtube/utils/bottomsheet/BottomSheetHookPatchKt {
public static final fun getBottomSheetHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -903,6 +911,10 @@ public final class app/revanced/patches/youtube/utils/controlsoverlay/ControlsOv
public static final fun getControlsOverlayConfigPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
+public final class app/revanced/patches/youtube/utils/dismiss/DismissPlayerHookPatchKt {
+ public static final fun getDismissPlayerHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
+}
+
public final class app/revanced/patches/youtube/utils/engagement/EngagementPanelHookPatchKt {
public static final fun getEngagementPanelHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1050,6 +1062,7 @@ public final class app/revanced/patches/youtube/utils/playservice/VersionCheckPa
public static final fun is_19_34_or_greater ()Z
public static final fun is_19_36_or_greater ()Z
public static final fun is_19_41_or_greater ()Z
+ public static final fun is_19_42_or_greater ()Z
public static final fun is_19_43_or_greater ()Z
public static final fun is_19_44_or_greater ()Z
public static final fun is_19_46_or_greater ()Z
diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/Fingerprints.kt
deleted file mode 100644
index 01f630b2b..000000000
--- a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/Fingerprints.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package app.revanced.patches.reddit.layout.screenshotpopup
-
-import app.revanced.util.fingerprint.legacyFingerprint
-import app.revanced.util.or
-import com.android.tools.smali.dexlib2.AccessFlags
-
-internal val screenshotBannerContainerFingerprint = legacyFingerprint(
- name = "screenshotTakenBannerFingerprint",
- returnType = "V",
- accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
- strings = listOf(
- "bannerContainer",
- "scope",
- )
-)
diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/ScreenshotPopupPatch.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/ScreenshotPopupPatch.kt
index 4de050ce9..096fa09be 100644
--- a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/ScreenshotPopupPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/ScreenshotPopupPatch.kt
@@ -1,31 +1,23 @@
package app.revanced.patches.reddit.layout.screenshotpopup
-import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
+import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
-import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
-import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.reddit.utils.extension.Constants.PATCHES_PATH
import app.revanced.patches.reddit.utils.patch.PatchList.DISABLE_SCREENSHOT_POPUP
import app.revanced.patches.reddit.utils.settings.settingsPatch
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
import app.revanced.util.findMutableMethodOf
-import app.revanced.util.fingerprint.methodCall
-import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
-import app.revanced.util.indexOfFirstInstructionOrThrow
-import app.revanced.util.indexOfFirstStringInstruction
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
+import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
-private const val EXTENSION_METHOD_DESCRIPTOR =
- "$PATCHES_PATH/ScreenshotPopupPatch;->disableScreenshotPopup()Z"
-
@Suppress("unused")
val screenshotPopupPatch = bytecodePatch(
DISABLE_SCREENSHOT_POPUP.title,
@@ -37,68 +29,57 @@ val screenshotPopupPatch = bytecodePatch(
execute {
- val screenshotTriggerSharingListenerMethodCall =
- screenshotBannerContainerFingerprint.methodCall()
-
- fun indexOfScreenshotTriggerInstruction(method: Method) =
+ fun indexOfShowBannerInstruction(method: Method) =
method.indexOfFirstInstruction {
- getReference()?.toString() == screenshotTriggerSharingListenerMethodCall
+ val reference = getReference()
+ opcode == Opcode.IGET_OBJECT &&
+ reference?.name?.contains("shouldShowBanner") == true &&
+ reference.definingClass.startsWith("Lcom/reddit/sharing/screenshot/") == true
}
- val isScreenshotTriggerMethod: Method.() -> Boolean = {
- indexOfScreenshotTriggerInstruction(this) >= 0
+ fun indexOfSetValueInstruction(method: Method) =
+ method.indexOfFirstInstruction {
+ getReference()?.name == "setValue"
+ }
+
+ fun indexOfBooleanInstruction(method: Method, startIndex: Int = 0) =
+ method.indexOfFirstInstruction(startIndex) {
+ val reference = getReference()
+ opcode == Opcode.SGET_OBJECT &&
+ reference?.definingClass == "Ljava/lang/Boolean;" &&
+ reference.type == "Ljava/lang/Boolean;"
+ }
+
+ val isScreenShotMethod: Method.() -> Boolean = {
+ definingClass.startsWith("Lcom/reddit/sharing/screenshot/") &&
+ name == "invokeSuspend" &&
+ indexOfShowBannerInstruction(this) >= 0 &&
+ indexOfBooleanInstruction(this) >= 0 &&
+ indexOfSetValueInstruction(this) >= 0
}
var hookCount = 0
- fun MutableMethod.hook() {
- if (returnType == "V") {
- addInstructionsWithLabels(
- 0, """
- invoke-static {}, $EXTENSION_METHOD_DESCRIPTOR
- move-result v0
- if-eqz v0, :shown
- return-void
- """, ExternalLabel("shown", getInstruction(0))
- )
-
- hookCount++
- } else if (returnType.startsWith("L")) { // Reddit 2025.06+
- val insertIndex =
- indexOfFirstStringInstruction("screenshotTriggerSharingListener")
-
- if (insertIndex >= 0) {
- val insertRegister =
- getInstruction(insertIndex).registerA
- val triggerIndex =
- indexOfScreenshotTriggerInstruction(this)
- val jumpIndex =
- indexOfFirstInstructionOrThrow(triggerIndex, Opcode.RETURN_OBJECT)
-
- addInstructionsWithLabels(
- insertIndex, """
- invoke-static {}, $EXTENSION_METHOD_DESCRIPTOR
- move-result v$insertRegister
- if-nez v$insertRegister, :hidden
- """, ExternalLabel("hidden", getInstruction(jumpIndex))
- )
-
- hookCount++
- }
- }
- }
-
- screenshotBannerContainerFingerprint
- .methodOrThrow()
- .hook()
-
classes.forEach { classDef ->
classDef.methods.forEach { method ->
- if (method.isScreenshotTriggerMethod()) {
+ if (method.isScreenShotMethod()) {
proxy(classDef)
.mutableClass
.findMutableMethodOf(method)
- .hook()
+ .apply {
+ val showBannerIndex = indexOfShowBannerInstruction(this)
+ val booleanIndex = indexOfBooleanInstruction(this, showBannerIndex)
+ val booleanRegister =
+ getInstruction(booleanIndex).registerA
+
+ addInstructions(
+ booleanIndex + 1, """
+ invoke-static {v$booleanRegister}, $PATCHES_PATH/ScreenshotPopupPatch;->disableScreenshotPopup(Ljava/lang/Boolean;)Ljava/lang/Boolean;
+ move-result-object v$booleanRegister
+ """
+ )
+ hookCount++
+ }
}
}
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/Fingerprints.kt
index cae1dc6d8..bea8e6697 100644
--- a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/Fingerprints.kt
@@ -1,5 +1,6 @@
package app.revanced.patches.reddit.layout.subredditdialog
+import app.revanced.patches.reddit.utils.resourceid.nsfwDialogTitle
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
@@ -71,6 +72,14 @@ fun indexOfHasBeenVisitedInstruction(method: Method) =
reference.returnType == "Z"
}
+internal val nsfwAlertBuilderFingerprint = legacyFingerprint(
+ name = "nsfwAlertBuilderFingerprint",
+ literals = listOf(nsfwDialogTitle),
+ customFingerprint = { method, _ ->
+ method.definingClass.startsWith("Lcom/reddit/screen/nsfw")
+ }
+)
+
internal val redditAlertDialogsFingerprint = legacyFingerprint(
name = "redditAlertDialogsFingerprint",
returnType = "V",
diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt
index ccd7e4064..58e8d5fbc 100644
--- a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt
@@ -3,10 +3,12 @@ package app.revanced.patches.reddit.layout.subredditdialog
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
+import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.reddit.utils.extension.Constants.PATCHES_PATH
import app.revanced.patches.reddit.utils.patch.PatchList.REMOVE_SUBREDDIT_DIALOG
+import app.revanced.patches.reddit.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.reddit.utils.settings.is_2024_41_or_greater
import app.revanced.patches.reddit.utils.settings.is_2025_01_or_greater
import app.revanced.patches.reddit.utils.settings.is_2025_05_or_greater
@@ -14,7 +16,9 @@ import app.revanced.patches.reddit.utils.settings.is_2025_06_or_greater
import app.revanced.patches.reddit.utils.settings.settingsPatch
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
import app.revanced.util.fingerprint.methodOrThrow
+import app.revanced.util.fingerprint.mutableClassOrThrow
import app.revanced.util.getReference
+import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode
@@ -32,7 +36,10 @@ val subRedditDialogPatch = bytecodePatch(
) {
compatibleWith(COMPATIBLE_PACKAGE)
- dependsOn(settingsPatch)
+ dependsOn(
+ settingsPatch,
+ sharedResourceIdPatch,
+ )
execute {
@@ -82,6 +89,32 @@ val subRedditDialogPatch = bytecodePatch(
"""
)
}
+
+ var hookCount = 0
+
+ nsfwAlertBuilderFingerprint.mutableClassOrThrow().let {
+ it.methods.forEach { method ->
+ method.apply {
+ val showIndex = indexOfFirstInstruction {
+ opcode == Opcode.INVOKE_VIRTUAL &&
+ getReference()?.name == "show"
+ }
+ if (showIndex >= 0) {
+ val dialogRegister = getInstruction(showIndex + 1).registerA
+
+ addInstruction(
+ showIndex + 2,
+ "invoke-static {v$dialogRegister}, $EXTENSION_CLASS_DESCRIPTOR->dismissNSFWDialog(Ljava/lang/Object;)V"
+ )
+ hookCount++
+ }
+ }
+ }
+ }
+
+ if (hookCount == 0) {
+ throw PatchException("Failed to find hook method")
+ }
}
// Not used in latest Reddit client.
diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/utils/compatibility/Constants.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/utils/compatibility/Constants.kt
index acb655efa..2feb4a8c5 100644
--- a/patches/src/main/kotlin/app/revanced/patches/reddit/utils/compatibility/Constants.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/reddit/utils/compatibility/Constants.kt
@@ -11,7 +11,7 @@ internal object Constants {
setOf(
"2024.17.0", // This is the last version that can be patched without anti-split.
"2025.05.1", // This was the latest version supported by the previous RVX patch.
- "2025.12.0", // This is the latest version supported by the RVX patch.
+ "2025.12.1", // 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/reddit/utils/resourceid/SharedResourceIdPatch.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/utils/resourceid/SharedResourceIdPatch.kt
new file mode 100644
index 000000000..532b4326c
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/reddit/utils/resourceid/SharedResourceIdPatch.kt
@@ -0,0 +1,19 @@
+package app.revanced.patches.reddit.utils.resourceid
+
+import app.revanced.patcher.patch.resourcePatch
+import app.revanced.patches.shared.mapping.ResourceType.STRING
+import app.revanced.patches.shared.mapping.getResourceId
+import app.revanced.patches.shared.mapping.resourceMappingPatch
+
+var nsfwDialogTitle = -1L
+ private set
+
+internal val sharedResourceIdPatch = resourcePatch(
+ description = "sharedResourceIdPatch"
+) {
+ dependsOn(resourceMappingPatch)
+
+ execute {
+ nsfwDialogTitle = getResourceId(STRING, "nsfw_dialog_title")
+ }
+}
\ No newline at end of file
diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/gms/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/gms/Fingerprints.kt
index 1e79c58e2..3f2339393 100644
--- a/patches/src/main/kotlin/app/revanced/patches/shared/gms/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/shared/gms/Fingerprints.kt
@@ -106,3 +106,8 @@ internal val primesLifecycleEventFingerprint = legacyFingerprint(
} >= 0
}
)
+
+internal val primeMethodFingerprint = legacyFingerprint(
+ name = "primesLifecycleEventFingerprint",
+ strings = listOf("com.google.android.GoogleCamera", "com.android.vending")
+)
\ No newline at end of file
diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/gms/GmsCoreSupportPatch.kt
index 9fb601010..f82dd143a 100644
--- a/patches/src/main/kotlin/app/revanced/patches/shared/gms/GmsCoreSupportPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/shared/gms/GmsCoreSupportPatch.kt
@@ -4,6 +4,7 @@ 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.getInstruction
+import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatchBuilder
import app.revanced.patcher.patch.BytecodePatchContext
@@ -18,9 +19,13 @@ import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.shared.extension.Constants.PATCHES_PATH
import app.revanced.patches.shared.gms.Constants.ACTIONS
+import app.revanced.patches.shared.gms.Constants.ACTIONS_LEGACY
import app.revanced.patches.shared.gms.Constants.AUTHORITIES
+import app.revanced.patches.shared.gms.Constants.AUTHORITIES_LEGACY
import app.revanced.patches.shared.gms.Constants.PERMISSIONS
+import app.revanced.patches.shared.gms.Constants.PERMISSIONS_LEGACY
import app.revanced.util.Utils.trimIndentMultiline
+import app.revanced.util.fingerprint.methodOrNull
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.mutableClassOrThrow
import app.revanced.util.getReference
@@ -127,6 +132,14 @@ fun gmsCoreSupportPatch(
required = true
) { it!!.matches(Regex(PACKAGE_NAME_REGEX_PATTERN)) && it != ORIGINAL_PACKAGE_NAME_YOUTUBE_MUSIC }
+ val patchAllManifest by booleanOption(
+ key = "patchAllManifest",
+ default = true,
+ title = "Patch all manifest components",
+ description = "Patch all permissions, intents and content provider authorities supported by GmsCore.",
+ required = true,
+ )
+
dependsOn(
gmsCoreSupportResourcePatchFactory(
gmsCoreVendorGroupIdOption,
@@ -139,6 +152,20 @@ fun gmsCoreSupportPatch(
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
execute {
+ val patchAllManifestEnabled = patchAllManifest == true
+ val permissions = if (patchAllManifestEnabled)
+ PERMISSIONS
+ else
+ PERMISSIONS_LEGACY
+ val actions = if (patchAllManifestEnabled)
+ ACTIONS
+ else
+ ACTIONS_LEGACY
+ val authorities = if (patchAllManifestEnabled)
+ AUTHORITIES
+ else
+ AUTHORITIES_LEGACY
+
fun transformStringReferences(transform: (str: String) -> String?) = classes.forEach {
val mutableClass by lazy {
proxy(it).mutableClass
@@ -182,9 +209,9 @@ fun gmsCoreSupportPatch(
when (referencedString) {
"com.google",
"com.google.android.gms",
- in PERMISSIONS,
- in ACTIONS,
- in AUTHORITIES,
+ in permissions,
+ in actions,
+ in authorities,
-> referencedString.replace("com.google", gmsCoreVendorGroupId!!)
// TODO: Add this permission when bumping GmsCore
@@ -196,7 +223,7 @@ fun gmsCoreSupportPatch(
// only when content:// uri
if (str.startsWith("content://")) {
// check if matches any authority
- for (authority in AUTHORITIES) {
+ for (authority in authorities) {
val uriPrefix = "content://$authority"
if (str.startsWith(uriPrefix)) {
return str.replace(
@@ -223,40 +250,56 @@ fun gmsCoreSupportPatch(
}
}
- fun transformPrimeMethod() {
- setOf(
- primesBackgroundInitializationFingerprint,
- primesLifecycleEventFingerprint
- ).forEach { fingerprint ->
- fingerprint.methodOrThrow().apply {
- val exceptionIndex = indexOfFirstInstructionReversedOrThrow {
- opcode == Opcode.NEW_INSTANCE &&
- (this as? ReferenceInstruction)?.reference?.toString() == "Ljava/lang/IllegalStateException;"
+ fun transformPrimeMethod(packageName: String) {
+ if (patchAllManifestEnabled) {
+ primeMethodFingerprint.methodOrNull()?.apply {
+ var register = 2
+
+ val index = instructions.indexOfFirst {
+ if (it.getReference()?.string != fromPackageName) return@indexOfFirst false
+
+ register = (it as OneRegisterInstruction).registerA
+ return@indexOfFirst true
}
- val index =
- indexOfFirstInstructionReversedOrThrow(exceptionIndex, Opcode.IF_EQZ)
- val register = getInstruction(index).registerA
- addInstruction(
- index,
- "const/4 v$register, 0x1"
- )
+
+ replaceInstruction(index, "const-string v$register, \"$packageName\"")
}
- }
- primesApiFingerprint.mutableClassOrThrow().methods.filter { method ->
- method.name != "" &&
- method.returnType == "V"
- }.forEach { method ->
- method.apply {
- val index = if (MethodUtil.isConstructor(method))
- indexOfFirstInstructionOrThrow {
- opcode == Opcode.INVOKE_DIRECT &&
- getReference()?.name == ""
- } + 1
- else 0
- addInstruction(
- index,
- "return-void"
- )
+ } else {
+ setOf(
+ primesBackgroundInitializationFingerprint,
+ primesLifecycleEventFingerprint
+ ).forEach { fingerprint ->
+ fingerprint.methodOrThrow().apply {
+ val exceptionIndex = indexOfFirstInstructionReversedOrThrow {
+ opcode == Opcode.NEW_INSTANCE &&
+ (this as? ReferenceInstruction)?.reference?.toString() == "Ljava/lang/IllegalStateException;"
+ }
+ val index =
+ indexOfFirstInstructionReversedOrThrow(exceptionIndex, Opcode.IF_EQZ)
+ val register = getInstruction(index).registerA
+ addInstruction(
+ index,
+ "const/4 v$register, 0x1"
+ )
+ }
+ }
+
+ primesApiFingerprint.mutableClassOrThrow().methods.filter { method ->
+ method.name != "" &&
+ method.returnType == "V"
+ }.forEach { method ->
+ method.apply {
+ val index = if (MethodUtil.isConstructor(method))
+ indexOfFirstInstructionOrThrow {
+ opcode == Opcode.INVOKE_DIRECT &&
+ getReference()?.name == ""
+ } + 1
+ else 0
+ addInstruction(
+ index,
+ "return-void"
+ )
+ }
}
}
}
@@ -280,37 +323,40 @@ fun gmsCoreSupportPatch(
return@transform null
}
- // Return these methods early to prevent the app from crashing.
- setOf(
+ val earlyReturnFingerprints = mutableListOf(
castContextFetchFingerprint,
- castDynamiteModuleFingerprint,
- castDynamiteModuleV2Fingerprint,
googlePlayUtilityFingerprint,
- serviceCheckFingerprint,
- sslGuardFingerprint,
- ).forEach { it.methodOrThrow().returnEarly() }
+ serviceCheckFingerprint
+ )
- // Prevent spam logs.
- eCatcherFingerprint.methodOrThrow().apply {
- val index = indexOfFirstInstructionOrThrow(Opcode.NEW_ARRAY)
- addInstruction(index, "return-void")
+ if (patchAllManifestEnabled) {
+ earlyReturnFingerprints += listOf(sslGuardFingerprint)
+
+ // Prevent spam logs.
+ eCatcherFingerprint.methodOrThrow().apply {
+ val index = indexOfFirstInstructionOrThrow(Opcode.NEW_ARRAY)
+ addInstruction(index, "return-void")
+ }
}
+ // Return these methods early to prevent the app from crashing.
+ earlyReturnFingerprints.forEach { it.methodOrThrow().returnEarly() }
+
// Specific method that needs to be patched.
- transformPrimeMethod()
+ transformPrimeMethod(packageName)
// Verify GmsCore is installed and whitelisted for power optimizations and background usage.
- mainActivityOnCreateFingerprint.method.apply {
- // Temporary fix for patches with an extension patch that hook the onCreate method as well.
- val setContextIndex = indexOfFirstInstruction {
- val reference =
- getReference() ?: return@indexOfFirstInstruction false
+ if (checkGmsCore == true) {
+ mainActivityOnCreateFingerprint.method.apply {
+ // Temporary fix for patches with an extension patch that hook the onCreate method as well.
+ val setContextIndex = indexOfFirstInstruction {
+ val reference =
+ getReference() ?: return@indexOfFirstInstruction false
- reference.toString() == "Lapp/revanced/extension/shared/Utils;->setContext(Landroid/content/Context;)V"
- }
+ reference.toString() == "Lapp/revanced/extension/shared/Utils;->setContext(Landroid/content/Context;)V"
+ }
- // Add after setContext call, because this patch needs the context.
- if (checkGmsCore == true) {
+ // Add after setContext call, because this patch needs the context.
addInstructions(
if (setContextIndex < 0) 0 else setContextIndex + 1,
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" +
@@ -335,7 +381,30 @@ fun gmsCoreSupportPatch(
* that are present in GmsCore which need to be transformed.
*/
private object Constants {
+ /**
+ * All permissions.
+ */
val PERMISSIONS = setOf(
+ "com.google.android.c2dm.permission.RECEIVE",
+ "com.google.android.c2dm.permission.SEND",
+ "com.google.android.gms.auth.api.phone.permission.SEND",
+ "com.google.android.gms.permission.AD_ID",
+ "com.google.android.gms.permission.AD_ID_NOTIFICATION",
+ "com.google.android.gms.permission.CAR_FUEL",
+ "com.google.android.gms.permission.CAR_INFORMATION",
+ "com.google.android.gms.permission.CAR_MILEAGE",
+ "com.google.android.gms.permission.CAR_SPEED",
+ "com.google.android.gms.permission.CAR_VENDOR_EXTENSION",
+ "com.google.android.googleapps.permission.GOOGLE_AUTH",
+ "com.google.android.googleapps.permission.GOOGLE_AUTH.cp",
+ "com.google.android.googleapps.permission.GOOGLE_AUTH.local",
+ "com.google.android.googleapps.permission.GOOGLE_AUTH.mail",
+ "com.google.android.googleapps.permission.GOOGLE_AUTH.writely",
+ "com.google.android.gtalkservice.permission.GTALK_SERVICE",
+ "com.google.android.providers.gsf.permission.READ_GSERVICES",
+ )
+
+ val PERMISSIONS_LEGACY = setOf(
// C2DM / GCM
"com.google.android.c2dm.permission.RECEIVE",
"com.google.android.c2dm.permission.SEND",
@@ -349,7 +418,234 @@ private object Constants {
// "com.google.android.gms.permission.ACTIVITY_RECOGNITION",
)
+ /**
+ * All intent actions.
+ */
val ACTIONS = setOf(
+ "com.google.android.c2dm.intent.RECEIVE",
+ "com.google.android.c2dm.intent.REGISTER",
+ "com.google.android.c2dm.intent.REGISTRATION",
+ "com.google.android.c2dm.intent.UNREGISTER",
+ "com.google.android.contextmanager.service.ContextManagerService.START",
+ "com.google.android.gcm.intent.SEND",
+ "com.google.android.gms.accounts.ACCOUNT_SERVICE",
+ "com.google.android.gms.accountsettings.ACCOUNT_PREFERENCES_SETTINGS",
+ "com.google.android.gms.accountsettings.action.BROWSE_SETTINGS",
+ "com.google.android.gms.accountsettings.action.VIEW_SETTINGS",
+ "com.google.android.gms.accountsettings.MY_ACCOUNT",
+ "com.google.android.gms.accountsettings.PRIVACY_SETTINGS",
+ "com.google.android.gms.accountsettings.SECURITY_SETTINGS",
+ "com.google.android.gms.ads.gservice.START",
+ "com.google.android.gms.ads.identifier.service.EVENT_ATTESTATION",
+ "com.google.android.gms.ads.service.CACHE",
+ "com.google.android.gms.ads.service.CONSENT_LOOKUP",
+ "com.google.android.gms.ads.service.HTTP",
+ "com.google.android.gms.analytics.service.START",
+ "com.google.android.gms.app.settings.GoogleSettingsLink",
+ "com.google.android.gms.appstate.service.START",
+ "com.google.android.gms.appusage.service.START",
+ "com.google.android.gms.asterism.service.START",
+ "com.google.android.gms.audiomodem.service.AudioModemService.START",
+ "com.google.android.gms.audit.service.START",
+ "com.google.android.gms.auth.account.authapi.START",
+ "com.google.android.gms.auth.account.authenticator.auto.service.START",
+ "com.google.android.gms.auth.account.authenticator.chromeos.START",
+ "com.google.android.gms.auth.account.authenticator.tv.service.START",
+ "com.google.android.gms.auth.account.data.service.START",
+ "com.google.android.gms.auth.api.credentials.PICKER",
+ "com.google.android.gms.auth.api.credentials.service.START",
+ "com.google.android.gms.auth.api.identity.service.authorization.START",
+ "com.google.android.gms.auth.api.identity.service.credentialsaving.START",
+ "com.google.android.gms.auth.api.identity.service.signin.START",
+ "com.google.android.gms.auth.api.phone.service.InternalService.START",
+ "com.google.android.gms.auth.api.signin.service.START",
+ "com.google.android.gms.auth.be.appcert.AppCertService",
+ "com.google.android.gms.auth.blockstore.service.START",
+ "com.google.android.gms.auth.config.service.START",
+ "com.google.android.gms.auth.cryptauth.cryptauthservice.START",
+ "com.google.android.gms.auth.GOOGLE_SIGN_IN",
+ "com.google.android.gms.auth.login.LOGIN",
+ "com.google.android.gms.auth.proximity.devicesyncservice.START",
+ "com.google.android.gms.auth.proximity.securechannelservice.START",
+ "com.google.android.gms.auth.proximity.START",
+ "com.google.android.gms.auth.service.START",
+ "com.google.android.gms.backup.ACTION_BACKUP_SETTINGS",
+ "com.google.android.gms.backup.G1_BACKUP",
+ "com.google.android.gms.backup.G1_RESTORE",
+ "com.google.android.gms.backup.GMS_MODULE_RESTORE",
+ "com.google.android.gms.beacon.internal.IBleService.START",
+ "com.google.android.gms.car.service.START",
+ "com.google.android.gms.carrierauth.service.START",
+ "com.google.android.gms.cast.firstparty.START",
+ "com.google.android.gms.cast.remote_display.service.START",
+ "com.google.android.gms.cast.service.BIND_CAST_DEVICE_CONTROLLER_SERVICE",
+ "com.google.android.gms.cast_mirroring.service.START",
+ "com.google.android.gms.checkin.BIND_TO_SERVICE",
+ "com.google.android.gms.chromesync.service.START",
+ "com.google.android.gms.clearcut.service.START",
+ "com.google.android.gms.common.account.CHOOSE_ACCOUNT",
+ "com.google.android.gms.common.download.START",
+ "com.google.android.gms.common.service.START",
+ "com.google.android.gms.common.telemetry.service.START",
+ "com.google.android.gms.config.START",
+ "com.google.android.gms.constellation.service.START",
+ "com.google.android.gms.credential.manager.service.firstparty.START",
+ "com.google.android.gms.deviceconnection.service.START",
+ "com.google.android.gms.drive.ApiService.RESET_AFTER_BOOT",
+ "com.google.android.gms.drive.ApiService.START",
+ "com.google.android.gms.drive.ApiService.STOP",
+ "com.google.android.gms.droidguard.service.INIT",
+ "com.google.android.gms.droidguard.service.PING",
+ "com.google.android.gms.droidguard.service.START",
+ "com.google.android.gms.enterprise.loader.service.START",
+ "com.google.android.gms.facs.cache.service.START",
+ "com.google.android.gms.facs.internal.service.START",
+ "com.google.android.gms.feedback.internal.IFeedbackService",
+ "com.google.android.gms.fido.credentialstore.internal_service.START",
+ "com.google.android.gms.fido.fido2.privileged.START",
+ "com.google.android.gms.fido.fido2.regular.START",
+ "com.google.android.gms.fido.fido2.zeroparty.START",
+ "com.google.android.gms.fido.sourcedevice.service.START",
+ "com.google.android.gms.fido.targetdevice.internal_service.START",
+ "com.google.android.gms.fido.u2f.privileged.START",
+ "com.google.android.gms.fido.u2f.thirdparty.START",
+ "com.google.android.gms.fido.u2f.zeroparty.START",
+ "com.google.android.gms.fitness.BleApi",
+ "com.google.android.gms.fitness.ConfigApi",
+ "com.google.android.gms.fitness.GoalsApi",
+ "com.google.android.gms.fitness.GoogleFitnessService.START",
+ "com.google.android.gms.fitness.HistoryApi",
+ "com.google.android.gms.fitness.InternalApi",
+ "com.google.android.gms.fitness.RecordingApi",
+ "com.google.android.gms.fitness.SensorsApi",
+ "com.google.android.gms.fitness.SessionsApi",
+ "com.google.android.gms.fonts.service.START",
+ "com.google.android.gms.freighter.service.START",
+ "com.google.android.gms.games.internal.connect.service.START",
+ "com.google.android.gms.games.PLAY_GAMES_UPGRADE",
+ "com.google.android.gms.games.service.START",
+ "com.google.android.gms.gass.START",
+ "com.google.android.gms.gmscompliance.service.START",
+ "com.google.android.gms.googlehelp.HELP",
+ "com.google.android.gms.googlehelp.service.GoogleHelpService.START",
+ "com.google.android.gms.growth.service.START",
+ "com.google.android.gms.herrevad.services.LightweightNetworkQualityAndroidService.START",
+ "com.google.android.gms.icing.INDEX_SERVICE",
+ "com.google.android.gms.icing.LIGHTWEIGHT_INDEX_SERVICE",
+ "com.google.android.gms.identity.service.BIND",
+ "com.google.android.gms.inappreach.service.START",
+ "com.google.android.gms.instantapps.START",
+ "com.google.android.gms.kids.service.START",
+ "com.google.android.gms.languageprofile.service.START",
+ "com.google.android.gms.learning.internal.dynamitesupport.START",
+ "com.google.android.gms.learning.intservice.START",
+ "com.google.android.gms.learning.predictor.START",
+ "com.google.android.gms.learning.trainer.START",
+ "com.google.android.gms.learning.training.background.START",
+ "com.google.android.gms.location.places.GeoDataApi",
+ "com.google.android.gms.location.places.PlaceDetectionApi",
+ "com.google.android.gms.location.places.PlacesApi",
+ "com.google.android.gms.location.reporting.service.START",
+ "com.google.android.gms.location.settings.LOCATION_HISTORY",
+ "com.google.android.gms.location.settings.LOCATION_REPORTING_SETTINGS",
+ "com.google.android.gms.locationsharing.api.START",
+ "com.google.android.gms.locationsharingreporter.service.START",
+ "com.google.android.gms.lockbox.service.START",
+ "com.google.android.gms.matchstick.lighter.service.START",
+ "com.google.android.gms.mdm.services.DeviceManagerApiService.START",
+ "com.google.android.gms.mdm.services.START",
+ "com.google.android.gms.mdns.service.START",
+ "com.google.android.gms.measurement.START",
+ "com.google.android.gms.nearby.bootstrap.service.NearbyBootstrapService.START",
+ "com.google.android.gms.nearby.connection.service.START",
+ "com.google.android.gms.nearby.fastpair.START",
+ "com.google.android.gms.nearby.messages.service.NearbyMessagesService.START",
+ "com.google.android.gms.nearby.sharing.service.NearbySharingService.START",
+ "com.google.android.gms.nearby.sharing.START_SERVICE",
+ "com.google.android.gms.notifications.service.START",
+ "com.google.android.gms.ocr.service.internal.START",
+ "com.google.android.gms.ocr.service.START",
+ "com.google.android.gms.oss.licenses.service.START",
+ "com.google.android.gms.payse.service.BIND",
+ "com.google.android.gms.people.contactssync.service.START",
+ "com.google.android.gms.people.service.START",
+ "com.google.android.gms.phenotype.service.START",
+ "com.google.android.gms.photos.autobackup.service.START",
+ "com.google.android.gms.playlog.service.START",
+ "com.google.android.gms.plus.service.default.INTENT",
+ "com.google.android.gms.plus.service.image.INTENT",
+ "com.google.android.gms.plus.service.internal.START",
+ "com.google.android.gms.plus.service.START",
+ "com.google.android.gms.potokens.service.START",
+ "com.google.android.gms.pseudonymous.service.START",
+ "com.google.android.gms.rcs.START",
+ "com.google.android.gms.reminders.service.START",
+ "com.google.android.gms.romanesco.MODULE_BACKUP_AGENT",
+ "com.google.android.gms.romanesco.service.START",
+ "com.google.android.gms.safetynet.service.START",
+ "com.google.android.gms.scheduler.ACTION_PROXY_SCHEDULE",
+ "com.google.android.gms.search.service.SEARCH_AUTH_START",
+ "com.google.android.gms.semanticlocation.service.START_ODLH",
+ "com.google.android.gms.sesame.service.BIND",
+ "com.google.android.gms.settings.EXPOSURE_NOTIFICATION_SETTINGS",
+ "com.google.android.gms.setup.auth.SecondDeviceAuth.START",
+ "com.google.android.gms.signin.service.START",
+ "com.google.android.gms.smartdevice.d2d.SourceDeviceService.START",
+ "com.google.android.gms.smartdevice.d2d.TargetDeviceService.START",
+ "com.google.android.gms.smartdevice.directtransfer.SourceDirectTransferService.START",
+ "com.google.android.gms.smartdevice.directtransfer.TargetDirectTransferService.START",
+ "com.google.android.gms.smartdevice.postsetup.PostSetupService.START",
+ "com.google.android.gms.smartdevice.setup.accounts.AccountsService.START",
+ "com.google.android.gms.smartdevice.wifi.START_WIFI_HELPER_SERVICE",
+ "com.google.android.gms.social.location.activity.service.START",
+ "com.google.android.gms.speech.service.START",
+ "com.google.android.gms.statementservice.EXECUTE",
+ "com.google.android.gms.stats.ACTION_UPLOAD_DROPBOX_ENTRIES",
+ "com.google.android.gms.tapandpay.service.BIND",
+ "com.google.android.gms.telephonyspam.service.START",
+ "com.google.android.gms.testsupport.service.START",
+ "com.google.android.gms.thunderbird.service.START",
+ "com.google.android.gms.trustagent.BridgeApi.START",
+ "com.google.android.gms.trustagent.StateApi.START",
+ "com.google.android.gms.trustagent.trustlet.trustletmanagerservice.BIND",
+ "com.google.android.gms.trustlet.bluetooth.service.BIND",
+ "com.google.android.gms.trustlet.connectionlessble.service.BIND",
+ "com.google.android.gms.trustlet.face.service.BIND",
+ "com.google.android.gms.trustlet.nfc.service.BIND",
+ "com.google.android.gms.trustlet.onbody.service.BIND",
+ "com.google.android.gms.trustlet.place.service.BIND",
+ "com.google.android.gms.trustlet.voiceunlock.service.BIND",
+ "com.google.android.gms.udc.service.START",
+ "com.google.android.gms.update.START_API_SERVICE",
+ "com.google.android.gms.update.START_SERVICE",
+ "com.google.android.gms.update.START_SINGLE_USER_API_SERVICE",
+ "com.google.android.gms.update.START_TV_API_SERVICE",
+ "com.google.android.gms.usagereporting.service.START",
+ "com.google.android.gms.userlocation.service.START",
+ "com.google.android.gms.vehicle.cabin.service.START",
+ "com.google.android.gms.vehicle.climate.service.START",
+ "com.google.android.gms.vehicle.info.service.START",
+ "com.google.android.gms.wallet.service.BIND",
+ "com.google.android.gms.walletp2p.service.firstparty.BIND",
+ "com.google.android.gms.walletp2p.service.zeroparty.BIND",
+ "com.google.android.gms.wearable.BIND",
+ "com.google.android.gms.wearable.BIND_LISTENER",
+ "com.google.android.gms.wearable.DATA_CHANGED",
+ "com.google.android.gms.wearable.MESSAGE_RECEIVED",
+ "com.google.android.gms.wearable.NODE_CHANGED",
+ "com.google.android.gsf.action.GET_GLS",
+ "com.google.android.location.settings.LOCATION_REPORTING_SETTINGS",
+ "com.google.android.mdd.service.START",
+ "com.google.android.mdh.service.listener.START",
+ "com.google.android.mdh.service.START",
+ "com.google.android.mobstore.service.START",
+ "com.google.firebase.auth.api.gms.service.START",
+ "com.google.firebase.dynamiclinks.service.START",
+ "com.google.iid.TOKEN_REQUEST",
+ "com.google.android.gms.location.places.ui.PICK_PLACE",
+ )
+
+ val ACTIONS_LEGACY = setOf(
// C2DM / GCM
"com.google.android.c2dm.intent.REGISTER",
"com.google.android.c2dm.intent.REGISTRATION",
@@ -407,7 +703,19 @@ private object Constants {
"com.google.android.gms.droidguard.service.START",
)
+ /**
+ * All content provider authorities.
+ */
val AUTHORITIES = setOf(
+ "com.google.android.gms.auth.accounts",
+ "com.google.android.gms.chimera",
+ "com.google.android.gms.fonts",
+ "com.google.android.gms.phenotype",
+ "com.google.android.gsf.gservices",
+ "com.google.settings",
+ )
+
+ val AUTHORITIES_LEGACY = setOf(
// gsf
"com.google.android.gsf.gservices",
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 1ccc3e575..54f62a8e7 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
@@ -217,30 +217,24 @@ val customBrandingIconPatch = resourcePatch(
}
}
- val styleList = if (is_19_32_or_greater)
- listOf(
- Triple(
- "values-night-v31",
- "Theme.YouTube.Home",
- "@style/Base.V27.Theme.YouTube.Home"
- ),
- Triple(
- "values-v31",
- "Theme.YouTube.Home",
- "@style/Base.V27.Theme.YouTube.Home"
- ),
- )
- else
- listOf(
- Triple(
- "values-v31",
- "Base.Theme.YouTube.Launcher",
- "@style/Theme.AppCompat.DayNight.NoActionBar"
- ),
- )
+ val styleList = mutableListOf(
+ Pair(
+ "Base.Theme.YouTube.Launcher",
+ "@style/Theme.AppCompat.DayNight.NoActionBar"
+ ),
+ )
- styleList.forEach { (directory, nodeAttributeName, nodeAttributeParent) ->
- document("res/$directory/styles.xml").use { document ->
+ if (is_19_32_or_greater) {
+ styleList += listOf(
+ Pair(
+ "Theme.YouTube.Home",
+ "@style/Base.V27.Theme.YouTube.Home"
+ ),
+ )
+ }
+
+ styleList.forEach { (nodeAttributeName, nodeAttributeParent) ->
+ document("res/values-v31/styles.xml").use { document ->
val resourcesNode =
document.getElementsByTagName("resources").item(0) as Element
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/name/CustomBrandingNamePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/name/CustomBrandingNamePatch.kt
index a1fe0d202..1dca220ca 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/name/CustomBrandingNamePatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/name/CustomBrandingNamePatch.kt
@@ -4,7 +4,6 @@ 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_NAME_FOR_YOUTUBE
-import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusLabel
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.util.removeStringsElements
import app.revanced.util.valueOrThrow
@@ -53,7 +52,5 @@ val customBrandingNamePatch = resourcePatch(
.appendChild(stringElement)
}
- updatePatchStatusLabel(appName)
-
}
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/startupshortsreset/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/startupshortsreset/Fingerprints.kt
index a8408ede6..fbe79271e 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/startupshortsreset/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/startupshortsreset/Fingerprints.kt
@@ -1,41 +1,17 @@
package app.revanced.patches.youtube.shorts.startupshortsreset
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
/**
- * YouTube v18.15.40 ~ YouTube 19.46.42
+ * YouTube v18.15.40+
*/
-internal val userWasInShortsABConfigFingerprint = legacyFingerprint(
+internal val userWasInShortsConfigFingerprint = legacyFingerprint(
name = "userWasInShortsABConfigFingerprint",
- returnType = "V",
- strings = listOf("Failed to get offline response: "),
- customFingerprint = { method, _ ->
- indexOfOptionalInstruction(method) >= 0
- }
-)
-
-internal fun indexOfOptionalInstruction(method: Method) =
- method.indexOfFirstInstruction {
- opcode == Opcode.INVOKE_STATIC &&
- getReference().toString() == "Lj${'$'}/util/Optional;->of(Ljava/lang/Object;)Lj${'$'}/util/Optional;"
- }
-
-/**
- * YouTube 19.47.53 ~
- */
-internal val userWasInShortsABConfigAlternativeFingerprint = legacyFingerprint(
- name = "userWasInShortsABConfigAlternativeFingerprint",
- returnType = "V",
- parameters = listOf("I"),
- opcodes = listOf(Opcode.OR_INT_LIT8),
- strings = listOf("alias", "null"),
+ accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
+ returnType = "Z",
+ literals = listOf(45358360L)
)
/**
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/startupshortsreset/ResumingShortsOnStartupPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/startupshortsreset/ResumingShortsOnStartupPatch.kt
index bfbc72f85..b28a77764 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/startupshortsreset/ResumingShortsOnStartupPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/startupshortsreset/ResumingShortsOnStartupPatch.kt
@@ -4,14 +4,10 @@ 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.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
-import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
-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.SHORTS_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.patch.PatchList.DISABLE_RESUMING_SHORTS_ON_STARTUP
-import app.revanced.patches.youtube.utils.playservice.is_19_46_or_greater
import app.revanced.patches.youtube.utils.playservice.is_20_02_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
@@ -19,10 +15,8 @@ import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
-import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
-import app.revanced.util.indexOfFirstStringInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@@ -42,50 +36,19 @@ val resumingShortsOnStartupPatch = bytecodePatch(
execute {
- fun MutableMethod.hookUserWasInShortsABConfig(startIndex: Int) {
- val walkerIndex = implementation!!.instructions.let {
- val subListIndex =
- it.subList(startIndex, startIndex + 20).indexOfFirst { instruction ->
- val reference = instruction.getReference()
- instruction.opcode == Opcode.INVOKE_VIRTUAL &&
- reference?.returnType == "Z" &&
- reference.definingClass != "Lj${'$'}/util/Optional;" &&
- reference.parameterTypes.isEmpty()
- }
- if (subListIndex < 0)
- throw PatchException("subListIndex not found")
-
- startIndex + subListIndex
- }
- val walkerMethod = getWalkerMethod(walkerIndex)
-
- // This method will only be called for the user being A/B tested.
- // Presumably a method that processes the ProtoDataStore value (boolean) for the 'user_was_in_shorts' key.
- walkerMethod.apply {
- addInstructionsWithLabels(
- 0, """
- invoke-static {}, $SHORTS_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer()Z
- move-result v0
- if-eqz v0, :show
- const/4 v0, 0x0
- return v0
- """, ExternalLabel("show", getInstruction(0))
- )
- }
- }
-
- if (is_19_46_or_greater) {
- userWasInShortsABConfigAlternativeFingerprint.methodOrThrow().apply {
- val stringIndex = indexOfFirstStringInstructionOrThrow("null")
- val startIndex = indexOfFirstInstructionOrThrow(stringIndex, Opcode.OR_INT_LIT8)
- hookUserWasInShortsABConfig(startIndex)
- }
- } else {
- userWasInShortsABConfigFingerprint.methodOrThrow().apply {
- val startIndex = indexOfOptionalInstruction(this)
- hookUserWasInShortsABConfig(startIndex)
- }
- }
+ userWasInShortsConfigFingerprint
+ .methodOrThrow()
+ .addInstructionsWithLabels(
+ 0, """
+ invoke-static {}, $SHORTS_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer()Z
+ move-result v0
+ if-eqz v0, :show
+ const/4 v0, 0x0
+ return v0
+ :show
+ nop
+ """
+ )
if (is_20_02_or_greater) {
userWasInShortsAlternativeFingerprint.matchOrThrow().let {
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/Fingerprints.kt
index 838a52ecf..eecf2a6cf 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/Fingerprints.kt
@@ -10,6 +10,7 @@ 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 fullScreenEngagementOverlayFingerprint = legacyFingerprint(
@@ -113,11 +114,17 @@ internal val playerGestureConfigSyntheticFingerprint = legacyFingerprint(
// This method is always called "a" because this kind of class always has a single method.
method.name == "a" &&
classDef.methods.count() == 2 &&
- method.indexOfFirstInstruction {
- val reference = getReference()
- reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" &&
- reference.parameterTypes.isEmpty() &&
- reference.returnType == "Z"
- } >= 0
+ indexOfPlayerConfigModelBooleanInstruction(method) >= 0
},
-)
\ No newline at end of file
+)
+
+internal fun indexOfPlayerConfigModelBooleanInstruction(
+ method: Method,
+ startIndex: Int = 0
+) = method.indexOfFirstInstruction(startIndex) {
+ val reference = getReference()
+ opcode == Opcode.INVOKE_VIRTUAL &&
+ reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" &&
+ reference.parameterTypes.isEmpty() &&
+ reference.returnType == "Z"
+}
\ No newline at end of file
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt
index 3b82fb404..ea22e133b 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt
@@ -30,7 +30,6 @@ import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
-import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.mutableClassOrThrow
import app.revanced.util.getReference
@@ -212,14 +211,17 @@ val swipeControlsPatch = bytecodePatch(
// region patch for disable swipe to enter fullscreen mode (in the player) and disable swipe to exit fullscreen mode
- playerGestureConfigSyntheticFingerprint.matchOrThrow().let {
- val endIndex = it.patternMatch!!.endIndex
+ playerGestureConfigSyntheticFingerprint.methodOrThrow().apply {
+ val disableSwipeToExitFullscreenModeIndex =
+ indexOfPlayerConfigModelBooleanInstruction(this)
+ val disableSwipeToEnterFullscreenModeInThePlayerIndex =
+ indexOfPlayerConfigModelBooleanInstruction(this, disableSwipeToExitFullscreenModeIndex + 1)
mapOf(
- 3 to "disableSwipeToEnterFullscreenModeInThePlayer",
- 9 to "disableSwipeToExitFullscreenMode"
- ).forEach { (offSet, methodName) ->
- it.getWalkerMethod(endIndex - offSet).apply {
+ disableSwipeToExitFullscreenModeIndex to "disableSwipeToExitFullscreenMode",
+ disableSwipeToEnterFullscreenModeInThePlayerIndex to "disableSwipeToEnterFullscreenModeInThePlayer"
+ ).forEach { (walkerIndex, methodName) ->
+ getWalkerMethod(walkerIndex).apply {
val index = implementation!!.instructions.lastIndex
val register = getInstruction(index).registerA
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/auth/AuthHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/auth/AuthHookPatch.kt
new file mode 100644
index 000000000..fe3e89272
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/auth/AuthHookPatch.kt
@@ -0,0 +1,34 @@
+package app.revanced.patches.youtube.utils.auth
+
+import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
+import app.revanced.patcher.patch.bytecodePatch
+import app.revanced.patches.youtube.utils.extension.Constants.EXTENSION_PATH
+import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch
+import app.revanced.patches.youtube.utils.request.buildRequestPatch
+import app.revanced.patches.youtube.utils.request.hookBuildRequest
+import app.revanced.util.fingerprint.methodOrThrow
+
+private const val EXTENSION_AUTH_UTILS_CLASS_DESCRIPTOR =
+ "$EXTENSION_PATH/utils/AuthUtils;"
+
+val authHookPatch = bytecodePatch(
+ description = "authHookPatch"
+) {
+ dependsOn(
+ sharedExtensionPatch,
+ buildRequestPatch,
+ )
+
+ execute {
+ // Get incognito status and data sync id.
+ accountIdentityFingerprint.methodOrThrow().addInstructions(
+ 1, """
+ sput-object p3, $EXTENSION_AUTH_UTILS_CLASS_DESCRIPTOR->dataSyncId:Ljava/lang/String;
+ sput-boolean p4, $EXTENSION_AUTH_UTILS_CLASS_DESCRIPTOR->isIncognito:Z
+ """
+ )
+
+ // Get the header to use the auth token.
+ hookBuildRequest("$EXTENSION_AUTH_UTILS_CLASS_DESCRIPTOR->setRequestHeaders(Ljava/lang/String;Ljava/util/Map;)V")
+ }
+}
\ No newline at end of file
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/auth/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/auth/Fingerprints.kt
new file mode 100644
index 000000000..85678373a
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/auth/Fingerprints.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.youtube.utils.auth
+
+import app.revanced.util.fingerprint.legacyFingerprint
+import app.revanced.util.or
+import com.android.tools.smali.dexlib2.AccessFlags
+
+internal val accountIdentityFingerprint = legacyFingerprint(
+ name = "accountIdentityFingerprint",
+ returnType = "V",
+ accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
+ customFingerprint = { method, _ ->
+ method.definingClass.endsWith("${'$'}AutoValue_AccountIdentity;")
+ }
+)
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/dismiss/DismissPlayerHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/dismiss/DismissPlayerHookPatch.kt
new file mode 100644
index 000000000..4545d4f8c
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/dismiss/DismissPlayerHookPatch.kt
@@ -0,0 +1,111 @@
+package app.revanced.patches.youtube.utils.dismiss
+
+import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
+import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
+import app.revanced.patcher.patch.bytecodePatch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.youtube.utils.extension.Constants.EXTENSION_PATH
+import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch
+import app.revanced.util.addStaticFieldToExtension
+import app.revanced.util.findMethodOrThrow
+import app.revanced.util.fingerprint.methodOrThrow
+import app.revanced.util.getReference
+import app.revanced.util.getWalkerMethod
+import app.revanced.util.indexOfFirstInstructionOrThrow
+import app.revanced.util.indexOfFirstInstructionReversedOrThrow
+import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
+import com.android.tools.smali.dexlib2.Opcode
+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_VIDEO_UTILS_CLASS_DESCRIPTOR =
+ "$EXTENSION_PATH/utils/VideoUtils;"
+
+private lateinit var dismissMethod: MutableMethod
+
+val dismissPlayerHookPatch = bytecodePatch(
+ description = "dismissPlayerHookPatch"
+) {
+ dependsOn(sharedExtensionPatch)
+
+ execute {
+ dismissPlayerOnClickListenerFingerprint.methodOrThrow().apply {
+ val literalIndex =
+ indexOfFirstLiteralInstructionOrThrow(DISMISS_PLAYER_LITERAL)
+ val dismissPlayerIndex = indexOfFirstInstructionOrThrow(literalIndex) {
+ val reference = getReference()
+ opcode == Opcode.INVOKE_VIRTUAL &&
+ reference?.returnType == "V" &&
+ reference.parameterTypes.isEmpty()
+ }
+
+ getWalkerMethod(dismissPlayerIndex).apply {
+ val jumpIndex = indexOfFirstInstructionReversedOrThrow {
+ opcode == Opcode.INVOKE_VIRTUAL &&
+ getReference()?.returnType == "V"
+ }
+ getWalkerMethod(jumpIndex).apply {
+ val jumpIndex = indexOfFirstInstructionReversedOrThrow {
+ opcode == Opcode.INVOKE_VIRTUAL &&
+ getReference()?.returnType == "V"
+ }
+ dismissMethod = getWalkerMethod(jumpIndex)
+ }
+ }
+
+ val dismissPlayerReference =
+ getInstruction(dismissPlayerIndex).reference as MethodReference
+ val dismissPlayerClass = dismissPlayerReference.definingClass
+
+ val fieldIndex =
+ indexOfFirstInstructionReversedOrThrow(dismissPlayerIndex) {
+ opcode == Opcode.IGET_OBJECT &&
+ getReference()?.type == dismissPlayerClass
+ }
+ val fieldReference =
+ getInstruction(fieldIndex).reference as FieldReference
+
+ findMethodOrThrow(fieldReference.definingClass).apply {
+ val insertIndex = indexOfFirstInstructionOrThrow {
+ opcode == Opcode.IPUT_OBJECT &&
+ getReference() == fieldReference
+ }
+ val insertRegister =
+ getInstruction(insertIndex).registerA
+
+ addInstruction(
+ insertIndex,
+ "sput-object v$insertRegister, $EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR->dismissPlayerClass:$dismissPlayerClass"
+ )
+
+ val smaliInstructions =
+ """
+ if-eqz v0, :ignore
+ invoke-virtual {v0}, $dismissPlayerReference
+ :ignore
+ return-void
+ """
+
+ addStaticFieldToExtension(
+ EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR,
+ "dismissPlayer",
+ "dismissPlayerClass",
+ dismissPlayerClass,
+ smaliInstructions,
+ false
+ )
+ }
+ }
+ }
+}
+
+/**
+ * This method is called when the video is closed.
+ */
+internal fun hookDismissObserver(descriptor: String) =
+ dismissMethod.addInstruction(
+ 0,
+ "invoke-static {}, $descriptor"
+ )
\ No newline at end of file
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/dismiss/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/dismiss/Fingerprints.kt
new file mode 100644
index 000000000..98de1131b
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/dismiss/Fingerprints.kt
@@ -0,0 +1,17 @@
+package app.revanced.patches.youtube.utils.dismiss
+
+import app.revanced.util.fingerprint.legacyFingerprint
+import app.revanced.util.or
+import com.android.tools.smali.dexlib2.AccessFlags
+
+internal const val DISMISS_PLAYER_LITERAL = 34699L
+
+internal val dismissPlayerOnClickListenerFingerprint = legacyFingerprint(
+ name = "dismissPlayerOnClickListenerFingerprint",
+ returnType = "V",
+ accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
+ literals = listOf(DISMISS_PLAYER_LITERAL),
+ customFingerprint = { method, _ ->
+ method.name == "onClick"
+ }
+)
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/splash/DarkModeSplashScreenPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/splash/DarkModeSplashScreenPatch.kt
index 2e3276993..5a193d9e6 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/splash/DarkModeSplashScreenPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/splash/DarkModeSplashScreenPatch.kt
@@ -3,6 +3,7 @@ package app.revanced.patches.youtube.utils.fix.splash
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.youtube.utils.playservice.is_19_32_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
+import app.revanced.patches.youtube.utils.settings.ResourceUtils.restoreOldSplashAnimationIncluded
import org.w3c.dom.Element
/**
@@ -19,21 +20,45 @@ val darkModeSplashScreenPatch = resourcePatch(
) {
dependsOn(versionCheckPatch)
- execute {
+ finalize {
if (!is_19_32_or_greater) {
- return@execute
+ return@finalize
}
- arrayOf(
- "values-night" to "@style/Base.V23.Theme.YouTube.Home",
- "values-night-v27" to "@style/Base.V27.Theme.YouTube.Home",
- ).forEach { (directory, parent) ->
- document("res/$directory/styles.xml").use { document ->
+ if (restoreOldSplashAnimationIncluded) {
+ document("res/values-night/styles.xml").use { document ->
+ val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
+ val childNodes = resourcesNode.childNodes
+
+ for (i in 0 until childNodes.length) {
+ val node = childNodes.item(i) as? Element ?: continue
+ val nodeAttributeName = node.getAttribute("name")
+ if (nodeAttributeName.startsWith("Theme.YouTube.Launcher")) {
+ val nodeAttributeParent = node.getAttribute("parent")
+
+ val style = document.createElement("style")
+ style.setAttribute("name", "Theme.YouTube.Home")
+ style.setAttribute("parent", nodeAttributeParent)
+
+ val windowItem = document.createElement("item")
+ windowItem.setAttribute("name", "android:windowBackground")
+ windowItem.textContent = "@color/yt_black1"
+ style.appendChild(windowItem)
+
+ resourcesNode.removeChild(node)
+ resourcesNode.appendChild(style)
+ }
+ }
+ }
+ } else {
+ document("res/values-night-v27/styles.xml").use { document ->
// Create a night mode specific override for the splash screen background.
val style = document.createElement("style")
style.setAttribute("name", "Theme.YouTube.Home")
- style.setAttribute("parent", parent)
+ style.setAttribute("parent", "@style/Base.V27.Theme.YouTube.Home")
+ // Fix status and navigation bar showing white on some Android devices,
+ // such as SDK 28 Android 10 medium tablet.
val colorSplashBackgroundColor = "@color/yt_black1"
arrayOf(
"android:navigationBarColor" to colorSplashBackgroundColor,
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/Fingerprints.kt
index 10c9e073d..9a55e8ecf 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/Fingerprints.kt
@@ -9,15 +9,6 @@ import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
-internal val accountIdentityFingerprint = legacyFingerprint(
- name = "accountIdentityFingerprint",
- returnType = "V",
- accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
- customFingerprint = { method, _ ->
- method.definingClass.endsWith("${'$'}AutoValue_AccountIdentity;")
- }
-)
-
internal val editPlaylistConstructorFingerprint = legacyFingerprint(
name = "editPlaylistConstructorFingerprint",
returnType = "V",
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/PlaylistPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/PlaylistPatch.kt
index c0327377b..8d9fa6fb8 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/PlaylistPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/PlaylistPatch.kt
@@ -7,11 +7,13 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.mainactivity.getMainActivityMethod
+import app.revanced.patches.youtube.utils.auth.authHookPatch
+import app.revanced.patches.youtube.utils.dismiss.dismissPlayerHookPatch
import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH
import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch
import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch
-import app.revanced.patches.youtube.utils.request.buildRequestPatch
-import app.revanced.patches.youtube.utils.request.hookBuildRequest
+import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch
+import app.revanced.patches.youtube.video.information.videoInformationPatch
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@@ -28,21 +30,13 @@ val playlistPatch = bytecodePatch(
dependsOn(
sharedExtensionPatch,
mainActivityResolvePatch,
- buildRequestPatch,
+ dismissPlayerHookPatch,
+ playerTypeHookPatch,
+ videoInformationPatch,
+ authHookPatch,
)
execute {
- // In Incognito mode, sending a request always seems to fail.
- accountIdentityFingerprint.methodOrThrow().addInstructions(
- 1, """
- sput-object p3, $EXTENSION_CLASS_DESCRIPTOR->dataSyncId:Ljava/lang/String;
- sput-boolean p4, $EXTENSION_CLASS_DESCRIPTOR->isIncognito:Z
- """
- )
-
- // Get the header to use the auth token.
- hookBuildRequest("$EXTENSION_CLASS_DESCRIPTOR->setRequestHeaders(Ljava/lang/String;Ljava/util/Map;)V")
-
// Open the queue manager by pressing and holding the back button.
getMainActivityMethod("onKeyLongPress")
.addInstructionsWithLabels(
@@ -56,12 +50,26 @@ val playlistPatch = bytecodePatch(
"""
)
+ setPivotBarVisibilityFingerprint
+ .matchOrThrow(setPivotBarVisibilityParentFingerprint)
+ .let {
+ it.method.apply {
+ val viewIndex = it.patternMatch!!.startIndex
+ val viewRegister = getInstruction(viewIndex).registerA
+ addInstruction(
+ viewIndex + 1,
+ "invoke-static {v$viewRegister}," +
+ " $EXTENSION_CLASS_DESCRIPTOR->setPivotBar(Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;)V",
+ )
+ }
+ }
+
+ // Users deleted videos via YouTube's flyout menu.
val setVideoIdReference = with(playlistEndpointFingerprint.methodOrThrow()) {
val setVideoIdIndex = indexOfSetVideoIdInstruction(this)
getInstruction(setVideoIdIndex).reference as FieldReference
}
- // Users deleted videos via YouTube's flyout menu.
editPlaylistFingerprint
.matchOrThrow(editPlaylistConstructorFingerprint)
.let {
@@ -86,19 +94,5 @@ val playlistPatch = bytecodePatch(
)
}
}
-
- setPivotBarVisibilityFingerprint
- .matchOrThrow(setPivotBarVisibilityParentFingerprint)
- .let {
- it.method.apply {
- val viewIndex = it.patternMatch!!.startIndex
- val viewRegister = getInstruction(viewIndex).registerA
- addInstruction(
- viewIndex + 1,
- "invoke-static {v$viewRegister}," +
- " $EXTENSION_CLASS_DESCRIPTOR->setPivotBar(Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;)V",
- )
- }
- }
}
}
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 19bf4774f..11e9b7239 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
@@ -55,6 +55,8 @@ var is_19_36_or_greater = false
private set
var is_19_41_or_greater = false
private set
+var is_19_42_or_greater = false
+ private set
var is_19_43_or_greater = false
private set
var is_19_44_or_greater = false
@@ -116,7 +118,8 @@ val versionCheckPatch = resourcePatch(
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_41_or_greater = 244205000 <= playStoreServicesVersion
+ is_19_42_or_greater = 244305000 <= playStoreServicesVersion
is_19_43_or_greater = 244405000 <= playStoreServicesVersion
is_19_44_or_greater = 244505000 <= playStoreServicesVersion
is_19_46_or_greater = 244705000 <= playStoreServicesVersion
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt
index 43cd0c4f1..094ca72fd 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt
@@ -88,9 +88,6 @@ internal object ResourceUtils {
updatePatchStatusSettings("Icon", "@string/revanced_icon_$iconName")
}
- fun updatePatchStatusLabel(appName: String) =
- updatePatchStatusSettings("Label", appName)
-
fun updatePatchStatusTheme(themeName: String) =
updatePatchStatusSettings("Theme", themeName)
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt
index d3fe3da10..e94490a05 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt
@@ -1,15 +1,18 @@
package app.revanced.patches.youtube.utils.settings
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
+import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
+import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableLongEncodedValue
import app.revanced.patches.shared.extension.Constants.EXTENSION_UTILS_CLASS_DESCRIPTOR
import app.revanced.patches.shared.extension.Constants.EXTENSION_UTILS_PATH
import app.revanced.patches.shared.mainactivity.injectConstructorMethodCall
import app.revanced.patches.shared.mainactivity.injectOnCreateMethodCall
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
+import app.revanced.patches.youtube.utils.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH
import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch
import app.revanced.patches.youtube.utils.fix.attributes.themeAttributesPatch
@@ -25,11 +28,13 @@ import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.copyResources
import app.revanced.util.copyXmlNode
import app.revanced.util.findInstructionIndicesReversedOrThrow
+import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.removeStringsElements
import app.revanced.util.valueOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
+import com.android.tools.smali.dexlib2.immutable.value.ImmutableLongEncodedValue
import org.w3c.dom.Element
import java.nio.file.Files
import java.util.jar.Manifest
@@ -81,14 +86,21 @@ private val settingsBytecodePatch = bytecodePatch(
EXTENSION_UTILS_CLASS_DESCRIPTOR,
"setActivity"
)
+
+ findMethodOrThrow(PATCH_STATUS_CLASS_DESCRIPTOR) {
+ name == "PatchedTime"
+ }.replaceInstruction(
+ 0,
+ "const-wide v0, ${MutableLongEncodedValue(ImmutableLongEncodedValue(System.currentTimeMillis()))}L"
+ )
}
}
-private const val DEFAULT_ELEMENT = "@string/about_key"
+private const val DEFAULT_ELEMENT = "@string/parent_tools_key"
private const val DEFAULT_LABEL = "RVX"
private val SETTINGS_ELEMENTS_MAP = mapOf(
- "Parent settings" to "@string/parent_tools_key",
+ "Parent settings" to DEFAULT_ELEMENT,
"General" to "@string/general_key",
"Account" to "@string/account_switcher_key",
"Data saving" to "@string/data_saving_settings_key",
@@ -109,7 +121,7 @@ private val SETTINGS_ELEMENTS_MAP = mapOf(
"Live chat" to "@string/live_chat_key",
"Captions" to "@string/captions_key",
"Accessibility" to "@string/accessibility_settings_key",
- "About" to DEFAULT_ELEMENT
+ "About" to "@string/about_key"
)
private lateinit var settingsLabel: String
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt
index 8052b44fd..faa0af987 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt
@@ -11,6 +11,8 @@ import app.revanced.patches.shared.customspeed.customPlaybackSpeedPatch
import app.revanced.patches.shared.litho.addLithoFilter
import app.revanced.patches.shared.litho.lithoFilterPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
+import app.revanced.patches.youtube.utils.dismiss.dismissPlayerHookPatch
+import app.revanced.patches.youtube.utils.dismiss.hookDismissObserver
import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH
import app.revanced.patches.youtube.utils.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.extension.Constants.VIDEO_PATH
@@ -25,7 +27,6 @@ import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.patches.youtube.video.information.hookBackgroundPlayVideoInformation
-import app.revanced.patches.youtube.video.information.hookShortsVideoInformation
import app.revanced.patches.youtube.video.information.hookVideoInformation
import app.revanced.patches.youtube.video.information.onCreateHook
import app.revanced.patches.youtube.video.information.speedSelectionInsertMethod
@@ -87,6 +88,7 @@ val videoPlaybackPatch = bytecodePatch(
),
flyoutMenuHookPatch,
lithoFilterPatch,
+ dismissPlayerHookPatch,
playerTypeHookPatch,
recyclerViewTreeObserverPatch,
shortsPlaybackPatch,
@@ -183,9 +185,9 @@ val videoPlaybackPatch = bytecodePatch(
}
hookBackgroundPlayVideoInformation("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->newVideoStarted(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZ)V")
- hookShortsVideoInformation("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->newShortsVideoStarted(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZ)V")
hookVideoInformation("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->newVideoStarted(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZ)V")
hookPlayerResponseVideoId("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->fetchMusicRequest(Ljava/lang/String;Z)V")
+ hookDismissObserver("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->onDismiss()V")
updatePatchStatus(PATCH_STATUS_CLASS_DESCRIPTOR, "RememberPlaybackSpeed")
diff --git a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt
index 1e5910f03..3b0b85818 100644
--- a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt
+++ b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt
@@ -22,12 +22,16 @@ import app.revanced.patches.shared.mapping.resourceMappingPatch
import app.revanced.util.Utils.printWarn
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
+import com.android.tools.smali.dexlib2.Opcode.*
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.MethodParameter
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
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.RegisterRangeInstruction
+import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction
+import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@@ -37,6 +41,7 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableField
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
import com.android.tools.smali.dexlib2.util.MethodUtil
+import java.util.EnumSet
const val REGISTER_TEMPLATE_REPLACEMENT: String = "REGISTER_INDEX"
@@ -52,6 +57,121 @@ fun parametersEqual(
return true
}
+/**
+ * Starting from and including the instruction at index [startIndex],
+ * finds the next register that is wrote to and not read from. If a return instruction
+ * is encountered, then the lowest unused register is returned.
+ *
+ * This method can return a non 4-bit register, and the calling code may need to temporarily
+ * swap register contents if a 4-bit register is required.
+ *
+ * @param startIndex Inclusive starting index.
+ * @param registersToExclude Registers to exclude, and consider as used. For most use cases,
+ * all registers used in injected code should be specified.
+ * @throws IllegalArgumentException If a branch or conditional statement is encountered
+ * before a suitable register is found.
+ */
+internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude: Int): Int {
+ if (implementation == null) {
+ throw IllegalArgumentException("Method has no implementation: $this")
+ }
+ if (startIndex < 0 || startIndex >= instructions.count()) {
+ throw IllegalArgumentException("startIndex out of bounds: $startIndex")
+ }
+
+ // All registers used by an instruction.
+ fun Instruction.getRegistersUsed() = when (this) {
+ is FiveRegisterInstruction -> listOf(registerC, registerD, registerE, registerF, registerG)
+ is ThreeRegisterInstruction -> listOf(registerA, registerB, registerC)
+ is TwoRegisterInstruction -> listOf(registerA, registerB)
+ is OneRegisterInstruction -> listOf(registerA)
+ is RegisterRangeInstruction -> (startRegister until (startRegister + registerCount)).toList()
+ else -> emptyList()
+ }
+
+ // Register that is written to by an instruction.
+ fun Instruction.getRegisterWritten() = when (this) {
+ is ThreeRegisterInstruction -> registerA
+ is TwoRegisterInstruction -> registerA
+ is OneRegisterInstruction -> registerA
+ else -> throw IllegalStateException("Not a write instruction: $this")
+ }
+
+ val writeOpcodes = EnumSet.of(
+ NEW_INSTANCE, NEW_ARRAY,
+ MOVE, MOVE_FROM16, MOVE_16, MOVE_WIDE, MOVE_WIDE_FROM16, MOVE_WIDE_16, MOVE_OBJECT,
+ MOVE_OBJECT_FROM16, MOVE_OBJECT_16, MOVE_RESULT, MOVE_RESULT_WIDE, MOVE_RESULT_OBJECT, MOVE_EXCEPTION,
+ IGET, IGET_WIDE, IGET_OBJECT, IGET_BOOLEAN, IGET_BYTE, IGET_CHAR, IGET_SHORT,
+ SGET, SGET_WIDE, SGET_OBJECT, SGET_BOOLEAN, SGET_BYTE, SGET_CHAR, SGET_SHORT,
+ )
+
+ val branchOpcodes = EnumSet.of(
+ GOTO, GOTO_16, GOTO_32,
+ IF_EQ, IF_NE, IF_LT, IF_GE, IF_GT, IF_LE,
+ IF_EQZ, IF_NEZ, IF_LTZ, IF_GEZ, IF_GTZ, IF_LEZ,
+ )
+
+ val returnOpcodes = EnumSet.of(
+ RETURN_VOID, RETURN, RETURN_WIDE, RETURN_OBJECT,
+ )
+
+ // Highest 4-bit register available, exclusive. Ideally return a free register less than this.
+ val maxRegister4Bits = 16
+ var bestFreeRegisterFound: Int? = null
+ val usedRegisters = registersToExclude.toMutableSet()
+
+ for (i in startIndex until instructions.count()) {
+ val instruction = getInstruction(i)
+
+ if (instruction.opcode in returnOpcodes) {
+ // Method returns. Use lowest register that hasn't been encountered.
+ val freeRegister = (0 until implementation!!.registerCount).find {
+ it !in usedRegisters
+ }
+ if (freeRegister != null) {
+ return freeRegister
+ }
+ if (bestFreeRegisterFound != null) {
+ return bestFreeRegisterFound;
+ }
+
+ // Somehow every method register was read from before any register was wrote to.
+ // In practice this never occurs.
+ throw IllegalArgumentException("Could not find a free register from startIndex: " +
+ "$startIndex excluding: $registersToExclude")
+ }
+
+ if (instruction.opcode in branchOpcodes) {
+ if (bestFreeRegisterFound != null) {
+ return bestFreeRegisterFound;
+ }
+ // This method is simple and does not follow branching.
+ throw IllegalArgumentException("Encountered a branch statement before a free register could be found")
+ }
+
+ if (instruction.opcode in writeOpcodes) {
+ val freeRegister = instruction.getRegisterWritten()
+ if (freeRegister !in usedRegisters) {
+ if (freeRegister < maxRegister4Bits) {
+ // Found an ideal register.
+ return freeRegister
+ }
+
+ // Continue searching for a 4-bit register if available.
+ if (bestFreeRegisterFound == null || freeRegister < bestFreeRegisterFound) {
+ bestFreeRegisterFound = freeRegister
+ }
+ }
+ }
+
+ usedRegisters.addAll(instruction.getRegistersUsed())
+ }
+
+ // Cannot be reached since a branch or return statement will
+ // be encountered before the end of the method.
+ throw IllegalStateException()
+}
+
/**
* Find the [MutableMethod] from a given [Method] in a [MutableClass].
*
diff --git a/patches/src/main/resources/music/translations/ko-rKR/strings.xml b/patches/src/main/resources/music/translations/ko-rKR/strings.xml
index 7f7bbfbef..992bef881 100644
--- a/patches/src/main/resources/music/translations/ko-rKR/strings.xml
+++ b/patches/src/main/resources/music/translations/ko-rKR/strings.xml
@@ -183,7 +183,7 @@
이전 보관함 선반으로 복원
이전 보관함 탭으로 복원합니다. (실험 기능)
시청 경고 다이얼로그 제거
- "다음 콘텐츠를 재생하기 전에 표시되는 시청 경고 다이얼로그를 제거합니다:\n• 연령 제한 콘텐츠\n• 혐오감을 주는 콘텐츠\n• 자살 또는 자해와 관련된 콘텐츠 ...\n\n이 설정은 다이얼로그를 자동으로 허용하기만 하며 연령 제한(성인인증 절차)을 우회할 수 없습니다."
+ "다음 콘텐츠를 재생하기 전에 표시되는 시청 경고 다이얼로그를 제거합니다:\n• 연령 제한 콘텐츠\n• 자살 또는 자해와 관련된 콘텐츠, etc.\n\n이 설정은 다이얼로그를 자동으로 허용하기만 하며 연령 제한(성인인증 절차)을 우회할 수 없습니다."
앱 버전 변경
"이전 앱 버전으로 변경합니다.
diff --git a/patches/src/main/resources/youtube/settings/host/values/strings.xml b/patches/src/main/resources/youtube/settings/host/values/strings.xml
index cadaf9aef..daa701e47 100644
--- a/patches/src/main/resources/youtube/settings/host/values/strings.xml
+++ b/patches/src/main/resources/youtube/settings/host/values/strings.xml
@@ -26,11 +26,13 @@ Please download %2$s from the website."
Add to queue
Add to queue and open queue
Add to queue and play video
+ Add to queue and reload video
External downloader
Open queue
Queue
Remove from queue
Remove from queue and open queue
+ Remove from queue and reload video
Remove queue
Save queue
"Instead of opening an external downloader, open the queue manager dialog.
@@ -2301,8 +2303,11 @@ Click to see more information."
Patch information
Information about applied patches.
-
- Tool used
+
+ App info
+ App name
+ App version
+ Patched date
Others
@@ -2314,7 +2319,6 @@ Click to see more information."
Revancify Blue
Revancify Red
YouTube
- Stock
Excluded
Included
Stock
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 3b906b092..18ab51e47 100644
--- a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml
+++ b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml
@@ -912,10 +912,12 @@
-
+
+
+
+
-
@@ -999,7 +1001,6 @@
-
diff --git a/patches/src/main/resources/youtube/translations/ar/strings.xml b/patches/src/main/resources/youtube/translations/ar/strings.xml
index fead0585a..25f691922 100644
--- a/patches/src/main/resources/youtube/translations/ar/strings.xml
+++ b/patches/src/main/resources/youtube/translations/ar/strings.xml
@@ -22,11 +22,13 @@
إضافة إلى قائمة الانتظار
إضافة إلى قائمة الانتظار وفتح قائمة الانتظار
إضافة إلى قائمة الانتظار وتشغيل الفيديو
+ إضافة إلى قائمة الانتظار وإعادة تحميل الفيديو
أداة التنزيل الخارجي
فتح قائمة الانتظار
قائمة الإنتظار
إزالة من قائمة الانتظار
إزالة من قائمة الانتظار وفتح قائمة الانتظار
+ إزالة من قائمة الانتظار وإعادة تحميل الفيديو
إزالة قائمة الانتظار
حفظ قائمة الانتظار
"بدلاً من فتح برنامج تنزيل خارجي، افتح نافذة مدير قائمة الانتظار.
@@ -203,18 +205,18 @@
تم إخفاء الترقية لـ YouTube Premium.
يتم عرض الترقية لـ YouTube Premium.
- مُصغَّرات فيديو بديلة
+ مصغرات فيديو بديلة
علامة تبويب الصفحة الرئيسية
قوائم تشغيل المشغل، التوصيات
نتائج البحث
علامة تبويب الاشتراكات
علامة التبويب أنت
- المصّغرات الأصلية
- DeArrow & المصّغرات الأصلية
+ المصغرات الأصلية
+ DeArrow & المصغرات الأصلية
DeArrow & اللقطات الثابتة
اللقطات الثابتة
DeArrow
- "يوفر DeArrow مُصغَّرات فيديو تم جمعها من الجمهور لفيديوهات YouTube. غالبًا ما تكون مُصغَّرات الفيديو هذه ذات صلة أكثر من تلك التي يقدمها موقع YouTube.
+ "يوفر DeArrow مصغرات فيديو تم جمعها من الجمهور لفيديوهات YouTube. غالبًا ما تكون مصغرات الفيديو هذه ذات صلة أكثر من تلك التي يقدمها موقع YouTube.
اذا تم تمكين هذا الخيار، سيتم إرسال عناوين URL للفيديو إلى خادم API ولن يتم إرسال أي بيانات أخرى. إذا لم يكن الفيديو يحتوي على مُصغَّرات لـ DeArrow، فسيتم عرض اللقطات الأصلية أو الثابتة.
@@ -228,7 +230,7 @@
لقطات الفيديو الثابتة
يتم التقاط اللقطات الثابتة من بداية أو منتصف أو نهاية كل فيديو. هذه الصور مدمجة في YouTube ولا يتم استخدام أي واجهة برمجة تطبيقات خارجية.
استخدام اللقطات الثابتة السريعة
- استخدام اللقطات متوسطة الجودة. سيتم تحميل المُصغَّرات بشكل أسرع، ولكن البث المباشر أو المقاطع التي لم يتم إصدارها أو القديمة جدًا قد تعرض مُصغَّرات فارغة.
+ استخدام اللقطات متوسطة الجودة. سيتم تحميل المصغرات بشكل أسرع، ولكن البث المباشر أو المقاطع التي لم يتم إصدارها أو القديمة جدًا قد تعرض مصغرات فارغة.
استخدام لقطات الفيديو الثابتة بجودة عالية.
وقت الفيديو لأخذ اللقطات الثابتة منه
بداية الفيديو
@@ -246,9 +248,9 @@
إخفاء بطاقات الألبوم
تم إخفاء بطاقات الألبوم.
يتم عرض بطاقات الألبوم.
- إخفاء زر التَرْجَمَة
- تم إخفاء زر التَرْجَمَة.
- يتم عرض زر التَرْجَمَة.
+ إخفاء زر الترجمة
+ تم إخفاء زر الترجمة.
+ يتم عرض زر الترجمة.
إخفاء الرفوف الدوارة
"تم إخفاء الرفوف الدوارة، مثل:
• أخبار عاجلة
@@ -360,8 +362,8 @@
تم تمكين فلتر القائمة المنبثقة بالموجز.
تم تعطيل فلتر القائمة المنبثقة بالموجز.
نوع تصفية القائمة المنبثقة للموجز
- قم بالتصفية إذا كان يحتوي على.<br><br>لإخفاء القائمة <b>\'تشغيل التالي في قائمة المحتوى التالي\'</b>، يمكنك استخدام <b>\'تشغيل التالي\'</b> أو <b>\'في قائمة المحتوى الرئيسي\'</b> ككلمات رئيسية.
- قم بالتصفية إذا كانت هناك تطابقات.<br><br>لإخفاء القائمة <b>\'تشغيل التالي في قائمة المحتوى التالي\'</b>، يمكنك فقط استخدام <b>\'تشغيل التالي في قائمة المحتوى التالي\'</b> ككلمات رئيسية.
+ قم بالتصفية إذا كان يحتوي على.<br><br>لإخفاء القائمة <b>\'تشغيل التالي في قائمة المحتوى التالي\'</b>، يمكنك استخدام <b>\'تشغيل التالي\'</b> أو <b>\'في قائمة المحتوى الرئيسي\'</b> ككلمات مفتاحية.
+ قم بالتصفية إذا كانت هناك تطابقات.<br><br>لإخفاء القائمة <b>\'تشغيل التالي في قائمة المحتوى التالي\'</b>، يمكنك فقط استخدام <b>\'تشغيل التالي في قائمة المحتوى التالي\'</b> ككلمات مفتاحية.
تصفية القائمة المنبثقة بالموجز
قائمة بأسماء القائمة المنبثقة المراد تصفيتها، مفصولة بسطور جديدة.
@@ -562,8 +564,8 @@
"إخفاء عناصر قائمة الحساب وعلامة التبويب أنت.
قد لا يتم إخفاء بعض المكونات."
نوع تصفية قائمة الحساب
- قم بالتصفية إذا كان يحتوي على.<br><br>لإخفاء قائمة <b>\'الحصول على YouTube Premium\'</b>، يمكنك استخدام <b>\'YouTube Premium\'</b> أو <b>\'Premium\'</b> ككلمات رئيسية.
- قم بالتصفية إذا تطابقت النتائج.<br><br>لإخفاء قائمة <b>\'الحصول على YouTube Premium\'</b>، يمكنك فقط استخدام <b>\'الحصول على YouTube Premium\'</b> ككلمات رئيسية.
+ قم بالتصفية إذا كان يحتوي على.<br><br>لإخفاء قائمة <b>\'الحصول على YouTube Premium\'</b>، يمكنك استخدام <b>\'YouTube Premium\'</b> أو <b>\'Premium\'</b> ككلمات مفتاحية.
+ قم بالتصفية إذا تطابقت النتائج.<br><br>لإخفاء قائمة <b>\'الحصول على YouTube Premium\'</b>، يمكنك فقط استخدام <b>\'الحصول على YouTube Premium\'</b> ككلمات مفتاحية.
تعديل فلتر قائمة الحساب
قائمة بأسماء قائمة الحسابات المراد تصفيتها، مفصولة بسطور جديدة.
إخفاء الاسم المعرِّف
@@ -981,12 +983,12 @@
إخفاء قائمة المقطع الصوتي
تم إخفاء قائمة المقطع الصوتي.
يتم عرض قائمة المقطع الصوتي.
- إخفاء قائمة التَرْجَمَة
- تم إخفاء قائمة التَرْجَمَة.
- يتم عرض قائمة التَرْجَمَة.
- إخفاء تذييل قائمة التَرْجَمَة
- تم إخفاء تذييل قائمة التَرْجَمَة.
- يتم عرض تذييل قائمة التَرْجَمَة.
+ إخفاء قائمة الترجمة
+ تم إخفاء قائمة الترجمة.
+ يتم عرض قائمة الترجمة.
+ إخفاء تذييل قائمة الترجمة
+ تم إخفاء تذييل قائمة الترجمة.
+ يتم عرض تذييل قائمة الترجمة.
إخفاء قائمة قفل الشاشة
تم إخفاء قائمة قفل الشاشة.
يتم عرض قائمة قفل الشاشة.
@@ -1140,7 +1142,7 @@
نوع المشغل المصغر
معطَّل
الأصلي
- الحد الأدنى
+ Minimal
الجهاز اللوحي
حديث 1
حديث 2
@@ -1191,9 +1193,9 @@
إخفاء زر التشغيل التلقائي
تم إخفاء زر التشغيل التلقائي.
يتم عرض زر التشغيل التلقائي.
- إخفاء زر التَرْجَمَة
- تم إخفاء زر التَرْجَمَة.
- يتم عرض زر التَرْجَمَة.
+ إخفاء زر الترجمة
+ تم إخفاء زر الترجمة.
+ يتم عرض زر الترجمة.
إخفاء زر البث
تم إخفاء زر البث.
يتم عرض زر البث.
@@ -1637,9 +1639,7 @@
النسبة المئوية لمساحة الشاشة القابلة للتمرير السريع.\n\nملاحظة: سيؤدي هذا أيضًا إلى تغيير حجم مساحة الشاشة لإيماءة النقر المزدوج للتقديم أو التأخير.
لا يمكن أن يزيد حجم المنطقة القابلة للتمرير السريع عن 50.
مهلة واجهة إيماءة التمرير
- مقدار الوقت الذي تظهر فيه واجهة التمرير بعد التغيير (بجزء الثانية).
-
-الافتراضي:500
+ أجزاء الثانية التي تكون فيها الواجهة مرئية.
حساسية تمرير مستوى السطوع
تكوين الحد الأدنى للمسافة لتمرير السطوع بين 1 و1000 (%).\nكلما كانت المسافة الدنيا أقصر، كلما تغيرت مستويات السطوع بشكل أسرع.
يجب أن تكون حساسية تمرير مستوى السطوع بين 1-1000 (%).
@@ -1817,7 +1817,7 @@
SponsorBlock
تمكين SponsorBlock
- SponsorBlock مانِع الرُعَاة هو نظام جماعي لتخطي الأجزاء المزعجة من فيديوهات YouTube.
+ مانِع الرُعَاة هو نظام جماعي لتخطي الأجزاء المزعجة من فيديوهات YouTube.
المظهر
عرض زر التصويت
@@ -1905,7 +1905,7 @@
يتم عرض زر إنشاء مقطع جديد.
لا يتم عرض زر إنشاء مقطع جديد.
تعديل تقديم او تأخير المقطع الجديد
- أجزاء الثانية في الوقت الذي تتحرك فيها أزرار ضبط الوقت عند إنشاء مقاطع جديدة.
+ أجزاء الثانية في الوقت الذي تتحرك فيه أزرار ضبط الوقت عند إنشاء مقاطع جديدة.
يجب أن تكون القيمة رقمًا موجبًا.
عرض الإرشادات
الإرشادات تحتوي على نصائح حول تقديم المقاطع.
@@ -2154,8 +2154,11 @@ AVC لديه حد أقصى للدقة 1080p، لا يتوفر ترميز الص
معلومات التعديل
معلومات عن التعديلات المطبقة.
-
- الأداة المستخدمة
+
+ معلومات التطبيق
+ اسم التطبيق
+ إصدار التطبيق
+ تاريخ التعديل
أخرى
مخصص
@@ -2166,7 +2169,6 @@ AVC لديه حد أقصى للدقة 1080p، لا يتوفر ترميز الص
Revancify Blue
Revancify Red
YouTube
- الإفتراضي
مستبعد
مضمن
الإفتراضي
diff --git a/patches/src/main/resources/youtube/translations/bg-rBG/strings.xml b/patches/src/main/resources/youtube/translations/bg-rBG/strings.xml
index 7674cc0c9..62d426282 100644
--- a/patches/src/main/resources/youtube/translations/bg-rBG/strings.xml
+++ b/patches/src/main/resources/youtube/translations/bg-rBG/strings.xml
@@ -1794,8 +1794,7 @@ AVC има максимална разделителна способност о
Информация за корекции
Информация за приложените корекции.
-
- Ползвани инструменти
+
Други
По избор
@@ -1806,7 +1805,6 @@ AVC има максимална разделителна способност о
Revancify синя
Revancify Червена
YouTube
- По подразбиране
Изключване
Включване
По подразбиране
diff --git a/patches/src/main/resources/youtube/translations/de-rDE/strings.xml b/patches/src/main/resources/youtube/translations/de-rDE/strings.xml
index 530305072..10e78f6a5 100644
--- a/patches/src/main/resources/youtube/translations/de-rDE/strings.xml
+++ b/patches/src/main/resources/youtube/translations/de-rDE/strings.xml
@@ -22,11 +22,13 @@ Bitte lade %2$s von der Webseite herunter."
Zur Warteschlange hinzufügen
Zur Warteschlange hinzufügen & öffnen
Zur Warteschlange hinzufügen und Video abspielen
+ Zur Warteschlange hinzufügen und Video neu laden
Externer Downloader
Warteschlange öffnen
Warteschlange
Aus der Warteschlange entfernen
Aus der Warteschlange entfernen und die Warteschlange öffnen
+ Aus der Warteschlange entfernen und Video neu laden
Warteschlange löschen
Warteschlange speichern
"Öffnen Sie statt eines externen Downloaders den Warteschlangenmanager.
@@ -113,7 +115,7 @@ Bitte verwenden Sie sie nur zu Debugging-Zwecken."
""
Werbung
- Endbild-Banner ausblenden
+ Abspann Store Banner ausblenden
Store-Banner ist ausgeblendet.
Store-Banner wird angezeigt.
Vollbildwerbung verstecken
@@ -791,7 +793,7 @@ Information:
Autoplay kann in den YouTube-Einstellungen geändert werden:
Einstellungen → Autoplay → Nächstes Video automatisch abspielen"
- Empfohlene Video-Endbildschirm wird angezeigt.
+ Empfohlene Videos Endbildschirm wird angezeigt.
Autotoplay Countdown überspringen
Ist Autoplay aktiviert, wird das nächste Video sofort abgespielt.
Wenn Autoplay aktiviert ist, wird das nächste Video nach dem Countdown abgespielt.
@@ -1490,6 +1492,10 @@ Drücken und halten Sie die Schaltfläche Mehr, um den Dialog benutzerdefinierte
Zeige geöffnetes Video-Menü
Video-Menü wird angezeigt.
Video-Menü öffnen ist ausgeblendet.
+ Geschwindigkeitsdialog
+ Menü „Geschwindigkeitsdialog anzeigen“
+ Das Dialogmenü „Geschwindigkeit“ wird angezeigt.
+ Das „Geschwindigkeitsdialogmenü“ ist ausgeblendet.
Status wiederholen
Zeige Wiederholungsstatus Menü
Das Statusmenü wird angezeigt.
@@ -1594,6 +1600,7 @@ Keine Ränder oben und unten des Spielers."
Video
+ Codec
HDR-Video deaktivieren
HDR-Video ist deaktiviert
HDR-Video ist aktiviert
@@ -1607,6 +1614,7 @@ Keine Ränder oben und unten des Spielers."
Replace software AV1 codec
Replaces the software AV1 codec with the VP9 codec.
+ Wiedergabegeschwindigkeit
Standard Wiedergabegeschwindigkeit
Remember playback speed changes
Playback speed changes apply to all videos.
@@ -1614,6 +1622,7 @@ Keine Ränder oben und unten des Spielers."
Toast anzeigen
Beim Ändern der Standard-Wiedergabegeschwindigkeit wird ein Toast angezeigt.
Beim Ändern der Standard-Wiedergabegeschwindigkeit wird kein Toast angezeigt.
+ Standard Wiedergabegeschwindigkeit bei Shorts
Benutzerdefinierte Wiedergabegeschwindigkeit aktivieren
Benutzerdefinierte Wiedergabegeschwindigkeit ist aktiviert
Benutzerdefinierte Wiedergabegeschwindigkeit ist deaktiviert
@@ -1625,6 +1634,7 @@ Keine Ränder oben und unten des Spielers."
Ungültige benutzerdefinierte Wiedergabegeschwindigkeiten. Auf Standardwerte zurücksetzen.
Ungültige benutzerdefinierte Wiedergabegeschwindigkeiten. Auf Standardwerte zurücksetzen.
+ Videoqualität
Standard Videoqualität im Mobilfunk
Standard-Videoqualität im Wlan
Qualitätseinstellungen merken
@@ -1897,7 +1907,7 @@ Klicken Sie hier, um zu sehen, wie Sie einen API-Schlüssel ausgeben."
sponsor.ajay.app
Die Daten werden von der SponsorBlock API bereitgestellt. Tippen Sie hier, um mehr zu erfahren und Downloads für andere Plattformen zu sehen
- PreferenceScreen: Sonstiges
+ Sonstiges
URL-Weiterleitungen umgehen
URL-Weiterleitungen werden umgangen.
URL-Umleitungen werden nicht umgangen.
@@ -1937,7 +1947,7 @@ Drücke Weiter und deaktiviere Akku-Optimierungen."
Aktiviere den OPUS-Codec, wenn die Antwort des Players den OPUS-Codec enthält.
Einstellungen importieren / exportieren
- Einstellungen importieren / exportieren
+ Importieren / Exportieren der RVX Einstellungen.
Als Datei importieren / exportieren
Einstellungen exportieren
@@ -2042,8 +2052,11 @@ Klicken Sie hier, um weitere Informationen zu sehen."
Patch-Informationen
Informationen über angewandte Patches
-
- Werkzeug verwendet
+
+ App Info
+ App Name
+ App Version
+ Patch Datum
Andere
Benutzerdefiniert
@@ -2054,7 +2067,6 @@ Klicken Sie hier, um weitere Informationen zu sehen."
Revancify Blau
Revancify Rot
YouTube
- Stock
ausgeschlossen
Enthalten
Stock
diff --git a/patches/src/main/resources/youtube/translations/el-rGR/strings.xml b/patches/src/main/resources/youtube/translations/el-rGR/strings.xml
index 335baf89e..37bd8d3fe 100644
--- a/patches/src/main/resources/youtube/translations/el-rGR/strings.xml
+++ b/patches/src/main/resources/youtube/translations/el-rGR/strings.xml
@@ -22,11 +22,13 @@
Προσθήκη στην ουρά
Προσθήκη στην ουρά και άνοιγμα ουράς
Προσθήκη στην ουρά και αναπαραγωγή βίντεο
+ Προσθήκη στην ουρά και επαναφόρτωση του βίντεο
Εξωτερικό πρόγραμμα λήψης
Άνοιγμα ουράς
Ουρά
Αφαίρεση από την ουρά
Αφαίρεση από την ουρά και άνοιγμα ουράς
+ Αφαίρεση από την ουρά και επαναφόρτωση του βίντεο
Κατάργηση ουράς
Αποθήκευση ουράς
"Αντί να ανοίξετε ένα εξωτερικό πρόγραμμα λήψης, ανοίξτε το παράθυρο διαχείρισης ουράς.
@@ -2162,8 +2164,11 @@ Playlists
Πληροφορίες τροποποίησης
Πληροφορίες σχετικά με τις εφαρμοσμένες τροποποιήσεις.
-
- Χρησιμοποιούμενο εργαλείο
+
+ Πληροφορίες εφαρμογής
+ Όνομα εφαρμογής
+ Έκδοση εφαρμογής
+ Ημερομηνία τροποποίησης
Άλλα
Προσαρμοσμένο
@@ -2174,7 +2179,6 @@ Playlists
Revancify Blue
Revancify Red
YouTube
- Προεπιλογή
Εξαιρέθηκε
Συμπεριλήφθηκε
Προεπιλογή
diff --git a/patches/src/main/resources/youtube/translations/es-rES/strings.xml b/patches/src/main/resources/youtube/translations/es-rES/strings.xml
index 6ccc07135..89614e528 100644
--- a/patches/src/main/resources/youtube/translations/es-rES/strings.xml
+++ b/patches/src/main/resources/youtube/translations/es-rES/strings.xml
@@ -22,11 +22,13 @@ Por favor, descarga %2$s desde el sitio web."
Añadir a la cola
Añadir a la cola y abrir la cola
Añadir a la cola y reproducir vídeo
+ Añadir a la cola y recargar vídeo
Descargador externo
Abrir cola
Cola
Eliminar de la cola
Eliminar de la cola y abrir la cola
+ Eliminar de la cola y recargar vídeo
Eliminar cola
Guardar cola
"En lugar de abrir un descargador externo, abre el diálogo del gestor de colas.
@@ -2133,8 +2135,11 @@ Pulsa aquí para ver más información."
Información de parches
Información sobre los parches aplicados.
-
- Herramientas utilizadas
+
+ Información de la app
+ Nombre de la app
+ Versión de la app
+ Fecha de parcheado
Otros
Personalizado
@@ -2145,7 +2150,6 @@ Pulsa aquí para ver más información."
Revancify Blue
Revancify Red
YouTube
- Predeterminada
Excluidos
Incluidos
Predeterminado
diff --git a/patches/src/main/resources/youtube/translations/fr-rFR/strings.xml b/patches/src/main/resources/youtube/translations/fr-rFR/strings.xml
index 810163c3d..e5f3c363b 100644
--- a/patches/src/main/resources/youtube/translations/fr-rFR/strings.xml
+++ b/patches/src/main/resources/youtube/translations/fr-rFR/strings.xml
@@ -22,11 +22,13 @@ Veuillez télécharger %2$s à partir du site web."
Ajouter à la file d\'attente
Ajouter à la file d\'attente et ouvrir la file d\'attente
Ajouter à la file d\'attente et lire la vidéo
+ Ajouter à la file d\'attente et recharger la vidéo
Téléchargeur externe
Ouvrir la file d\'attente
File d\'attente
Retirer de la file d\'attente
Retirer de la file d\'attente et ouvrir la file d\'attente
+ Supprimer de la file d\'attente et recharger la vidéo
Retirer de la file d\'attente
Enregistrer la file d’attente
"Au lieu d’ouvrir un téléchargeur externe, ouvrez la boîte de dialogue du gestionnaire de file d’attente.
@@ -2148,8 +2150,11 @@ Cliquez pour plus d'informations."
Informations sur les patchs
Informations sur les patchs appliqués.
-
- Outil utilisé
+
+ Info sur l\'app
+ Nom de l\'application
+ Version de l\'appli
+ Date du patch
Autres
Personnalisé
@@ -2160,7 +2165,6 @@ Cliquez pour plus d'informations."
Revancify Bleu
Revancify Rouge
Youtube
- Officiel
Exclus
Appliqué
Officiel
diff --git a/patches/src/main/resources/youtube/translations/hu-rHU/strings.xml b/patches/src/main/resources/youtube/translations/hu-rHU/strings.xml
index 61f16bae5..596ca233a 100644
--- a/patches/src/main/resources/youtube/translations/hu-rHU/strings.xml
+++ b/patches/src/main/resources/youtube/translations/hu-rHU/strings.xml
@@ -1914,8 +1914,7 @@ Kattintson a további információkért."
Patch információ
Információ az alkalmazott patchekről.
-
- Használt eszköz
+
Egyebek
Egyéni
@@ -1926,7 +1925,6 @@ Kattintson a további információkért."
Revancify Kék
Revancify Piros
YouTube
- Alap
Kizárva
Beleértve
Alap
diff --git a/patches/src/main/resources/youtube/translations/id-rID/strings.xml b/patches/src/main/resources/youtube/translations/id-rID/strings.xml
index 8c9ecd0d9..6766196f5 100644
--- a/patches/src/main/resources/youtube/translations/id-rID/strings.xml
+++ b/patches/src/main/resources/youtube/translations/id-rID/strings.xml
@@ -400,6 +400,6 @@ Keterbatasan: Tombol Kembali pada bilah alat mungkin tidak berfungsi."
-
+
diff --git a/patches/src/main/resources/youtube/translations/in/strings.xml b/patches/src/main/resources/youtube/translations/in/strings.xml
index 8c9ecd0d9..6766196f5 100644
--- a/patches/src/main/resources/youtube/translations/in/strings.xml
+++ b/patches/src/main/resources/youtube/translations/in/strings.xml
@@ -400,6 +400,6 @@ Keterbatasan: Tombol Kembali pada bilah alat mungkin tidak berfungsi."
-
+
diff --git a/patches/src/main/resources/youtube/translations/it-rIT/strings.xml b/patches/src/main/resources/youtube/translations/it-rIT/strings.xml
index 3c5133350..6a4dab5e3 100644
--- a/patches/src/main/resources/youtube/translations/it-rIT/strings.xml
+++ b/patches/src/main/resources/youtube/translations/it-rIT/strings.xml
@@ -22,11 +22,13 @@ Si prega di scaricare %2$s dal sito web."
Aggiungi alla coda
Aggiungi alla coda e aprila
Aggiungi alla coda e riproduci il video
+ Aggiungi alla coda e ricarica il video
Downloader esterno
Apri la coda
Coda
Rimuovi dalla coda
Rimuovi dalla coda e aprila
+ Rimuovi dalla coda e ricarica il video
Rimuovi la coda
Salva la coda
"Invece di aprire un downloader esterno, apri la finestra di dialogo del gestore delle code.
@@ -2142,8 +2144,11 @@ Tocca per vedere maggiori informazioni."
Informazioni patch
Informazioni sulle patch applicate.
-
- Strumenti usati
+
+ Informazioni sull\'app
+ Nome
+ Versione
+ Data e ora della patch
Altri
Personalizzata
@@ -2154,7 +2159,6 @@ Tocca per vedere maggiori informazioni."
Revancify Blue
Revancify Red
YouTube
- Predefinita
Escluso
Incluso
Predefinito
diff --git a/patches/src/main/resources/youtube/translations/ja-rJP/strings.xml b/patches/src/main/resources/youtube/translations/ja-rJP/strings.xml
index 8bd5b2d92..f07abd14c 100644
--- a/patches/src/main/resources/youtube/translations/ja-rJP/strings.xml
+++ b/patches/src/main/resources/youtube/translations/ja-rJP/strings.xml
@@ -22,11 +22,13 @@
キューに追加
キューに追加してキューを開く
キューに追加して動画を再生
+ キューに追加して動画を再読み込み
外部ダウンローダー
キューを開く
キュー
キューから削除
キューから削除してキューを開く
+ キューから削除して動画を再読み込み
キューを削除
キューを保存
"外部ダウンローダーを開く代わりに、キューマネージャーダイアログを開きます。
@@ -51,7 +53,7 @@
キューを削除しました。
動画を削除しました。
キューは「%s」に保存されました。
- YouTube と Revanced Extended 設定の言語
+ Revanced Extended 設定の言語
アプリの設定言語に従う
""
""
@@ -410,7 +412,7 @@ DeArrow の詳細については、ここをタップしてください。"ショート
スポーツ
登録チャンネル
- トレンド
+ 急上昇
バーチャル リアリティ
後で見る
クリップ
@@ -838,9 +840,9 @@ DeArrow の詳細については、ここをタップしてください。"「共有」ボタンを非表示
「共有」ボタンを非表示にします。
「共有」ボタンを非表示にします。
- 「ショップ」ボタンを非表示
- 「ショップ」ボタンを非表示にします。
- 「ショップ」ボタンを非表示にします。
+ 「ショッピング」ボタンを非表示
+ 「ショッピング」ボタンを非表示にします。
+ 「ショッピング」ボタンを非表示にします。
「Thanks」ボタンを非表示
「Thanks」ボタンを非表示にします。
「Thanks」ボタンを非表示にします。
@@ -1394,9 +1396,9 @@ DeArrow の詳細については、ここをタップしてください。"「チャンネル登録」ボタンを非表示
「チャンネル登録」ボタンを非表示にします。
「チャンネル登録」ボタンを非表示にします。
- 「トレンド」ボタンを非表示
- 「トレンド」ボタンを非表示にします。
- 「トレンド」ボタンを非表示にします。
+ 「急上昇」ボタンを非表示
+ 「急上昇」ボタンを非表示にします。
+ 「急上昇」ボタンを非表示にします。
動画のタイトルを非表示
プレーヤー下部に表示される動画のタイトル名を非表示にします。
プレーヤー下部に表示される動画のタイトルを非表示にします。
@@ -1414,9 +1416,9 @@ DeArrow の詳細については、ここをタップしてください。"検索候補のボタンを非表示
検索候補のボタンを非表示にします。
検索候補のボタンを非表示にします。
- 「ショップ」ボタンを非表示
- 「ショップ」ボタンを非表示にします。
- 「ショップ」ボタンを非表示にします。
+ 「ショッピング」ボタンを非表示
+ 「ショッピング」ボタンを非表示にします。
+ 「ショッピング」ボタンを非表示にします。
「Super Thanks」ボタンを非表示
「Super Thanks」ボタンを非表示にします。
「Super Thanks」ボタンを非表示にします。
@@ -1664,8 +1666,8 @@ DeArrow の詳細については、ここをタップしてください。"トーストを表示
デフォルトの画質を変更した際にトーストが表示されるようにします。
デフォルトの画質を変更した際にトーストが表示されるようにします。
- モバイルデータ通信使用時のデフォルト画質
- Wi-Fi 使用時のデフォルト画質
+ モバイルデータ通信使用時のショートのデフォルト画質
+ Wi-Fi 使用時のショートのデフォルト画質
ショートの画質変更を記憶
現在の設定: 画質の変更はすべてのショートに適用されます。
現在の設定: 画質の変更は現在のショートにのみ適用されます。
@@ -2095,8 +2097,11 @@ iOS クライアント選択する場合は、これらの値が必要になる
パッチ情報
適用されたパッチに関する情報です。
-
- 使用されたツール
+
+ アプリ情報
+ アプリ名
+ アプリバージョン
+ パッチ適用日時
その他
カスタム
@@ -2107,7 +2112,6 @@ iOS クライアント選択する場合は、これらの値が必要になる
Revancify Blue
Revancify Red
YouTube
- オリジナル
除外されています
適用されています
オリジナル
diff --git a/patches/src/main/resources/youtube/translations/ko-rKR/strings.xml b/patches/src/main/resources/youtube/translations/ko-rKR/strings.xml
index 782ea8c39..f65ac6c2e 100644
--- a/patches/src/main/resources/youtube/translations/ko-rKR/strings.xml
+++ b/patches/src/main/resources/youtube/translations/ko-rKR/strings.xml
@@ -22,11 +22,13 @@
현재 재생목록에 추가
현재 재생목록에 추가하고 현재 재생목록 열기
현재 재생목록에 추가하고 동영상 재생
+ 현재 재생목록에 추가하고 동영상 다시 로드
외부 다운로더
현재 재생목록 열기
현재 재생목록
현재 재생목록에서 제거
현재 재생목록에서 제거하고 현재 재생목록 열기
+ 현재 재생목록에서 제거하고 동영상 다시 로드
현재 재생목록 제거
현재 재생목록 저장
"외부 다운로더가 아닌 현재 재생목록 관리자 다이얼로그를 실행할 수 있습니다.
@@ -498,7 +500,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요."
동영상들 사이에서 회색 구분선이 숨겨집니다.
동영상들 사이에서 회색 구분선이 표시됩니다.
시청 경고 다이얼로그 제거하기
- "다음 동영상을 시청하기 전에 표시되는 시청 경고 다이얼로그를 제거합니다:\n• 연령 제한 동영상\n• 혐오감을 주는 동영상\n• 자살 또는 자해와 관련된 동영상, etc.\n\n이 설정은 다이얼로그를 자동으로 허용하기만 하며 연령 제한(성인인증 절차)을 우회할 수 없습니다."
+ "다음 동영상을 시청하기 전에 표시되는 시청 경고 다이얼로그를 제거합니다:\n• 연령 제한 동영상\n• 자살 또는 자해와 관련된 동영상, etc.\n\n이 설정은 다이얼로그를 자동으로 허용하기만 하며 연령 제한(성인인증 절차)을 우회할 수 없습니다."
라이브 링 누르기 동작 변경하기
"라이브 링이 표시된 채널 아이콘을 누르면 채널 프로필으로 연결됩니다.
@@ -2158,8 +2160,11 @@ PoToken과 VisitorData를 발급받는 방법을 보려면 여기를 누르세
패치 정보
적용된 패치에 대한 정보입니다.
-
- 사용된 도구
+
+ 앱 정보
+ 앱 이름
+ 앱 버전
+ 패치된 날짜
기타
사용자 정의
@@ -2170,7 +2175,6 @@ PoToken과 VisitorData를 발급받는 방법을 보려면 여기를 누르세
Revancify Blue
Revancify Red
YouTube
- YouTube
제외된 패치
포함된 패치
YouTube
diff --git a/patches/src/main/resources/youtube/translations/pl-rPL/strings.xml b/patches/src/main/resources/youtube/translations/pl-rPL/strings.xml
index 43310e109..a4871199c 100644
--- a/patches/src/main/resources/youtube/translations/pl-rPL/strings.xml
+++ b/patches/src/main/resources/youtube/translations/pl-rPL/strings.xml
@@ -22,11 +22,13 @@ Pobierz %2$s ze strony internetowej."
Dodaj do kolejki
Dodaj do kolejki i otwórz kolejkę
Dodaj do kolejki i odtwórz film
+ Dodaj do kolejki i odśwież film
Zewnętrzna aplikacja od pobierania
Otwórz kolejkę
Kolejka
Usuń z kolejki
Usuń z kolejki i otwórz kolejkę
+ Usuń z kolejki i odśwież film
Usuń kolejkę
Zapisz kolejkę
"Zamiast otwierać zewnętrzną aplikację od pobierania, otwiera okno menedżera kolejki.
@@ -2153,8 +2155,11 @@ Kliknij, by zobaczyć więcej informacji."
Informacje o łatkach
Informacje na temat zastosowanych łatek.
-
- Użyte narzędzie
+
+ Informacje o aplikacji
+ Nazwa aplikacji
+ Wersja aplikacji
+ Data załatania
Inne
Własna
@@ -2165,7 +2170,6 @@ Kliknij, by zobaczyć więcej informacji."
Niebieska od Revancify
Czerwona od Revancify
YouTube
- Domyślne
Wykluczone
Zawarte
Domyślny
diff --git a/patches/src/main/resources/youtube/translations/pt-rBR/strings.xml b/patches/src/main/resources/youtube/translations/pt-rBR/strings.xml
index 08b66fcf1..c3bf01685 100644
--- a/patches/src/main/resources/youtube/translations/pt-rBR/strings.xml
+++ b/patches/src/main/resources/youtube/translations/pt-rBR/strings.xml
@@ -22,11 +22,13 @@ Por favor, baixe %2$s do site."
Adicionar à fila
Adicionar à fila e abrir a fila
Adicionar à fila e reproduzir o vídeo
+ Adicionar à fila e recarregar o vídeo
Download externo
Abrir fila
Fila
Remover da lista
Remover da fila e abrir a fila
+ Remover da fila e recarregar vídeo
Remover fila
Salvar fila
"Em vez de abrir um aplicativo de download externo, abra a caixa de diálogo do gerenciador de filas.
@@ -53,14 +55,50 @@ Use-o apenas para fins de depuração."
Fila salva com sucesso em \'%s\'.
Idioma do RVX
Idioma do app
+ "Catalão
+Català"
+ "Checo
+Čeština"
+ "Alemão
+Deutsch"
+ "Grego
+Ελληνικά"
+ "Inglês
+English"
+ "Espanhol
+Español"
"Francês
Français"
+ "Hebraico
+עברי"
+ "Italiano
+Italiano"
+ "Japonês
+日本語"
+ "Coreano
+한국어"
+ "Holandês
+Nederlands"
+ "Polonês
+Polski"
"Português
Português"
"Romeno
Română"
"Russo
Русский"
+ "Sérvio
+Српски"
+ "Sueco
+Svenska"
+ "Tailandês
+ไทย"
+ "Turco
+Türkçe"
+ "Ucraniano
+Українська"
+ "Chinês
+中文"
Anúncios
Ocultar banner da loja na tela final
@@ -1710,6 +1748,7 @@ Clique para ver como emitir uma chave de API."
Exibir botão de pular
Exibir na barra de progresso
Desativar
+ Opacidade:
Cor:
Cor alterada.
Redefinir cor.
@@ -1894,6 +1933,8 @@ Toque no botão continuar e desative as otimizações da bateria."
Android VR
"Android VR
(Sem autenticação)"
+ "iOS
+(Obsoleto)"
"iOS TV
(é necessário logar)"
Efeitos colaterais da falsificação
@@ -1926,6 +1967,7 @@ O AVC tem uma resolução máxima de 1080p, o codec de áudio Opus não está di
O cliente usado para buscar dados de streaming é mostrado em Estatísticas para nerds.
O cliente usado para buscar dados de streaming está oculto em Estatísticas para nerds.
Idioma padrão de áudio do VR
+ Você não pode estar logado.
PoToken / VisitorData
PoToken a ser utilizado
@@ -1957,8 +1999,11 @@ Clique para ver mais informações."
Informações do patch
Informações sobre as modificações aplicadas.
-
- Ferramenta usada
+
+ Informações do aplicativo
+ Nome do aplicativo
+ Versão do aplicativo
+ Data da atualização
Outros
Personalizado
@@ -1969,7 +2014,6 @@ Clique para ver mais informações."
Revancify Blue
Revancify Red
YouTube
- Padrão
Excluído
Incluído
Padrão
diff --git a/patches/src/main/resources/youtube/translations/ru-rRU/strings.xml b/patches/src/main/resources/youtube/translations/ru-rRU/strings.xml
index e87a1c83a..f1ae49ca9 100644
--- a/patches/src/main/resources/youtube/translations/ru-rRU/strings.xml
+++ b/patches/src/main/resources/youtube/translations/ru-rRU/strings.xml
@@ -22,11 +22,13 @@
Добавить в очередь
Добавить в очередь и открыть очередь
Добавить в очередь и посмотреть
+ Добавить в очередь и обновить
Внешний загрузчик
Открыть очередь
Очередь
Удалить из очереди
Удалить из очереди и открыть очередь
+ Удалить из очереди и обновить
Удалить очередь
Сохранить очередь
"Вместо открытия внешнего загрузчика открывается диалог управления очередью.
@@ -1248,7 +1250,7 @@ Shorts
"Коснитесь, чтобы создать список просмотра всех видео из канала.
Коснитесь и удерживайте, для отмены.
-Инфо:
+Информация:
• Может не работать в прямых трансляциях."
Сортировка создаваемого списка
Все (сорт. по времени, по возрастанию)
@@ -2104,7 +2106,7 @@ Shorts
Использовать клиент iOS
"Клиент iOS добавлен к доступным клиентам.
-ВНИМАНИЕ: Клиент iOS устаревший. Любые проблемы, возникающие при его использовании, на свой страх и риск."
+ПРЕДУПРЕЖДЕНИЕ: Клиент iOS устаревший. Любые проблемы, возникающие при его использовании, на свой страх и риск."
Клиент iOS не добавлен к доступным клиентам.
"При запросе конечных точек YouTube API с помощью iOS требуются токены Auth, выданные устройством iOS, и токены PoToken, выданные iOSGuard.
@@ -2164,8 +2166,11 @@ Shorts
Информация о патчах
Информация о примененных патчах.
-
- Используемые инструменты
+
+ О приложении
+ Имя приложения
+ Версия приложения
+ Дата применения патчей
Другие
Пользовательский
@@ -2176,7 +2181,6 @@ Shorts
Revancify синяя
Revancify красная
Иконка YouTube
- По умолчанию
Не применён
Применён
По умолчанию
diff --git a/patches/src/main/resources/youtube/translations/tr-rTR/strings.xml b/patches/src/main/resources/youtube/translations/tr-rTR/strings.xml
index 2ca246e47..17312e1df 100644
--- a/patches/src/main/resources/youtube/translations/tr-rTR/strings.xml
+++ b/patches/src/main/resources/youtube/translations/tr-rTR/strings.xml
@@ -1545,8 +1545,7 @@ Devam düğmesine dokunun ve pil optimizasyonlarını devre dışı bırakın."<
Yama Bilgileri
Uygulanmış Yamalar Hakkında Bilgi.
-
- Araç kullanıldı
+
Diğerleri
Özel
@@ -1557,7 +1556,6 @@ Devam düğmesine dokunun ve pil optimizasyonlarını devre dışı bırakın."<
Revancify Mavi
Revancify Kırmızı
YouTube
- Stok
Hariç tutulan
Dahil
Stok
diff --git a/patches/src/main/resources/youtube/translations/uk-rUA/strings.xml b/patches/src/main/resources/youtube/translations/uk-rUA/strings.xml
index 4d5174517..a46c46feb 100644
--- a/patches/src/main/resources/youtube/translations/uk-rUA/strings.xml
+++ b/patches/src/main/resources/youtube/translations/uk-rUA/strings.xml
@@ -22,11 +22,13 @@
Додати до черги
Додати до черги й відкрити чергу
Додати до черги й відтворити відео
+ Додати до черги та перевантажити відео
Зовнішній завантажувач
Відкрити чергу
Черга
Видалити з черги
Вилучити з черги й відкрити чергу
+ Видалити з черги та перевантажити відео
Вилучити чергу
Зберегти чергу
"Замість відкривання зовнішнього завантажувача відкриває діалог керування чергою.
@@ -2114,7 +2116,7 @@ AVC має максимальну роздільну здатність 1080p,
Клієнт, що використовується для отримання даних трансляції показується у Статистика для сисадмінів.
Клієнт, що використовується для отримання даних трансляції приховано у Статистика для сисадмінів.
Мова звукової доріжки для VR
- Не вдалося отримати якісь трансляції клієнта.
+ Не вдалося отримати які-небудь трансляції клієнта.
Не можливо авторизуватися.
PoToken / VisitorData
@@ -2147,8 +2149,11 @@ AVC має максимальну роздільну здатність 1080p,
Інформація про патчі
Інформація про застосовані патчі.
-
- Використано інструменти
+
+ Інформація про додаток
+ Назва додатка
+ Версія додатка
+ Дата пропатчування
Інше
Користувацька
@@ -2159,7 +2164,6 @@ AVC має максимальну роздільну здатність 1080p,
Revancify синя
Revancify червона
YouTube
- Стандартна
Виключено
Включено
Стандартна
diff --git a/patches/src/main/resources/youtube/translations/vi-rVN/strings.xml b/patches/src/main/resources/youtube/translations/vi-rVN/strings.xml
index d272eece7..e5bdc56fc 100644
--- a/patches/src/main/resources/youtube/translations/vi-rVN/strings.xml
+++ b/patches/src/main/resources/youtube/translations/vi-rVN/strings.xml
@@ -22,11 +22,13 @@
Thêm vào hàng chờ
Thêm vào hàng chờ và xem hàng chờ
Thêm vào hàng chờ và phát video
+ Thêm vào hàng chờ và tải lại video
Trình tải xuống bên ngoài
Xem hàng chờ
Hàng chờ
Xóa khỏi hàng chờ
Xóa khỏi hàng chờ và xem hàng chờ
+ Xoá khỏi hàng chờ và tải lại video
Xoá hàng chờ
Lưu hàng chờ
"Thay vì mở trình tải xuống bên ngoài, khi tương tác sẽ mở hộp thoại quản lý hàng chờ.
@@ -2150,8 +2152,11 @@ Nhấp vào đây để biết thêm chi tiết."
Thông tin bản vá
Thông tin về các bản vá được áp dụng.
-
- Công cụ được sử dụng
+
+ Thông tin ứng dụng
+ Tên ứng dụng
+ Phiên bản ứng dụng
+ Ngày vá
Khác
Tùy chỉnh
@@ -2162,7 +2167,6 @@ Nhấp vào đây để biết thêm chi tiết."
Revancify Blue
Revancify Red
YouTube
- Nguyên bản
Đã loại trừ
Đã bao gồm
Nguyên bản
diff --git a/patches/src/main/resources/youtube/translations/zh-rCN/strings.xml b/patches/src/main/resources/youtube/translations/zh-rCN/strings.xml
index a77aecfd8..c6684b1d5 100644
--- a/patches/src/main/resources/youtube/translations/zh-rCN/strings.xml
+++ b/patches/src/main/resources/youtube/translations/zh-rCN/strings.xml
@@ -1629,8 +1629,7 @@
补丁信息
已应用补丁的信息
-
- 使用的工具
+
其他
Custom
@@ -1641,7 +1640,6 @@
Revancify Blue
Revancify Red
YouTube
- 默认
Excluded
Included
Stock
diff --git a/patches/src/main/resources/youtube/translations/zh-rTW/strings.xml b/patches/src/main/resources/youtube/translations/zh-rTW/strings.xml
index 290bfe2cc..d9d10db66 100644
--- a/patches/src/main/resources/youtube/translations/zh-rTW/strings.xml
+++ b/patches/src/main/resources/youtube/translations/zh-rTW/strings.xml
@@ -22,11 +22,13 @@
加入佇列清單
加到佇列並打開佇列
加入佇列並播放影片
+ 加入佇列並重新載入影片
外部下載器
開啟佇列
佇列
從佇列中移除
從佇列中移除並打開佇列
+ 從佇列移除並重新載入影片
移除佇列
儲存佇列
"不要開啟外部下載器,而是開啟佇列管理器對話方塊。
@@ -506,15 +508,15 @@
平板
平板電腦 (最少 600 dp)
車用
- "改變包括:
+ "變更內容包括:
平板佈局
-• 社群貼文被隱藏。
+• 隱藏社群貼文。
車用佈局
-• 常規播放器中的短影音開啟。
-• 資訊流依主題和頻道進行組織。
-• 關閉「偽裝串流數據」時無法開啟影片說明。"
+• 一般播放器中的短影音開啟。
+• 動態消息按主題和頻道分類。
+• 關閉「偽裝串流數據」時無法開啟影片描述。"
禁用佈局更新
伺服器不會更新佈局。
佈局將由伺服器更新。
@@ -1362,9 +1364,9 @@
手動展開影片描述
短影音
- 停用 短影音 後台播放
- 短影音 後台播放已停用。
- 短影音 後台播放已啟用。
+ 停用短影音背景播放
+ 短影音背景播放已停用。
+ 短影音背景播放已啟用。
停用恢復短影音播放器
短影音播放器在應用程式啟動時不會恢復播放。
短影音播放器在應用程式啟動時會恢復播放。
@@ -1396,15 +1398,15 @@
在觀看歷史中隱藏
在觀看歷史中顯示
- 變更 短影音背景重複狀態
- 更改短影音重複播放狀態
+ 變更短影音背景重複狀態
+ 變更短影音重複播放狀態
自動播放
預設
暫停
重複播放
- 在一般播放器中開啟 Shorts
- 在一般播放器中開啟 Shorts。
- 不要在普通播放器中開啟 Shorts。
+ 在一般播放器中開啟短影音
+ 在一般播放器中開啟短影音。
+ 不要在一般播放器中開啟短影音。
短影音播放器
隱藏或顯示短影音播放器中的組件。
@@ -2138,8 +2140,11 @@ AVC 的最大解析度為 1080p,Opus 音訊編解碼器不可用,影片播
補丁訊息
已應用補丁的訊息
-
- 使用的工具 --唐懂翻譯
+
+ 應用程式資訊
+ 應用程式名稱
+ 應用程式版本
+ 修補日期
其他
自訂
@@ -2150,7 +2155,6 @@ AVC 的最大解析度為 1080p,Opus 音訊編解碼器不可用,影片播
復興藍
復興紅
YouTube
- 預設
排除
包括
預設