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 - 預設 排除 包括 預設