mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-04-30 14:44:30 +02:00
Merge branch 'dev' into revanced-extended
This commit is contained in:
commit
9146d9283b
99
README.md
99
README.md
@ -13,13 +13,16 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `Alternative thumbnails` | Adds options to replace video thumbnails using the DeArrow API or image captures from the video. | 18.29.38 ~ 19.44.39 |
|
||||
| `Ambient mode control` | Adds options to disable Ambient mode and to bypass Ambient mode restrictions. | 18.29.38 ~ 19.44.39 |
|
||||
| `Bypass URL redirects` | Adds an option to bypass URL redirects and open the original URL directly. | 18.29.38 ~ 19.44.39 |
|
||||
| `Bypass image region restrictions` | Adds an option to use a different host for static images, so that images blocked in some countries can be received. | 18.29.38 ~ 19.44.39 |
|
||||
| `Change layout` | Adds an option to change the dp in order to use a tablet or phone layout. | 18.29.38 ~ 19.44.39 |
|
||||
| `Change live ring click action` | Adds an option to open the channel instead of the live stream when clicking on the live ring. | 18.29.38 ~ 19.44.39 |
|
||||
| `Change player flyout menu toggles` | Adds an option to use text toggles instead of switch toggles within the additional settings menu. | 18.29.38 ~ 19.44.39 |
|
||||
| `Change share sheet` | Adds an option to change the in-app share sheet to the system share sheet. | 18.29.38 ~ 19.44.39 |
|
||||
| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 18.29.38 ~ 19.44.39 |
|
||||
| `Custom Shorts action buttons` | Changes, at compile time, the icon of the action buttons of the Shorts player. | 18.29.38 ~ 19.44.39 |
|
||||
| `Custom branding icon for YouTube` | Changes the YouTube app icon to the icon specified in patch options. | 18.29.38 ~ 19.44.39 |
|
||||
| `Custom branding name for YouTube` | Renames the YouTube app to the name specified in patch options. | 18.29.38 ~ 19.44.39 |
|
||||
| `Custom branding name for YouTube` | Changes the YouTube app name to the name specified in patch options. | 18.29.38 ~ 19.44.39 |
|
||||
| `Custom double tap length` | Adds Double-tap to seek values that are specified in patch options. | 18.29.38 ~ 19.44.39 |
|
||||
| `Custom header for YouTube` | Applies a custom header in the top left corner within the app. | 18.29.38 ~ 19.44.39 |
|
||||
| `Description components` | Adds options to hide and disable description components. | 18.29.38 ~ 19.44.39 |
|
||||
@ -29,11 +32,9 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
|
||||
| `Disable haptic feedback` | Adds options to disable haptic feedback when swiping in the video player. | 18.29.38 ~ 19.44.39 |
|
||||
| `Disable resuming Shorts on startup` | Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched. | 18.29.38 ~ 19.44.39 |
|
||||
| `Disable splash animation` | Adds an option to disable the splash animation on app startup. | 18.29.38 ~ 19.44.39 |
|
||||
| `Enable OPUS codec` | Adds an options to enable the OPUS audio codec if the player response includes it. | 18.29.38 ~ 19.44.39 |
|
||||
| `Enable OPUS codec` | Adds an option to enable the OPUS audio codec if the player response includes it. | 18.29.38 ~ 19.44.39 |
|
||||
| `Enable debug logging` | Adds an option to enable debug logging. | 18.29.38 ~ 19.44.39 |
|
||||
| `Enable external browser` | Adds an option to always open links in your browser instead of in the in-app-browser. | 18.29.38 ~ 19.44.39 |
|
||||
| `Enable gradient loading screen` | Adds an option to enable the gradient loading screen. | 18.29.38 ~ 19.44.39 |
|
||||
| `Enable open links directly` | Adds an option to skip over redirection URLs in external links. | 18.29.38 ~ 19.44.39 |
|
||||
| `Force hide player buttons background` | Removes, at compile time, the dark background surrounding the video player controls. | 18.29.38 ~ 19.44.39 |
|
||||
| `Fullscreen components` | Adds options to hide or change components related to fullscreen. | 18.29.38 ~ 19.44.39 |
|
||||
| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 18.29.38 ~ 19.44.39 |
|
||||
@ -49,20 +50,21 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
|
||||
| `Hide shortcuts` | Remove, at compile time, the app shortcuts that appears when the app icon is long pressed. | 18.29.38 ~ 19.44.39 |
|
||||
| `Hook YouTube Music actions` | Adds support for opening music in RVX Music using the in-app YouTube Music button. | 18.29.38 ~ 19.44.39 |
|
||||
| `Hook download actions` | Adds support to download videos with an external downloader app using the in-app download button. | 18.29.38 ~ 19.44.39 |
|
||||
| `Layout switch` | Adds an option to spoof the dpi in order to use a tablet or phone layout. | 18.29.38 ~ 19.44.39 |
|
||||
| `MaterialYou` | Applies the MaterialYou theme for Android 12+ devices. | 18.29.38 ~ 19.44.39 |
|
||||
| `Miniplayer` | Adds options to change the in-app minimized player, and if patching target 19.16+ adds options to use modern miniplayers. | 18.29.38 ~ 19.44.39 |
|
||||
| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 18.29.38 ~ 19.44.39 |
|
||||
| `Open links externally` | Adds an option to always open links in your browser instead of the in-app browser. | 18.29.38 ~ 19.44.39 |
|
||||
| `Overlay buttons` | Adds options to display useful overlay buttons in the video player. | 18.29.38 ~ 19.44.39 |
|
||||
| `Player components` | Adds options to hide or change components related to the video player. | 18.29.38 ~ 19.44.39 |
|
||||
| `Remove background playback restrictions` | Removes restrictions on background playback, including for music and kids videos. | 18.29.38 ~ 19.44.39 |
|
||||
| `Remove viewer discretion dialog` | Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction. | 18.29.38 ~ 19.44.39 |
|
||||
| `Return YouTube Dislike` | Adds an option to show the dislike count of videos using the Return YouTube Dislike API. | 18.29.38 ~ 19.44.39 |
|
||||
| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 18.29.38 ~ 19.44.39 |
|
||||
| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | 18.29.38 ~ 19.44.39 |
|
||||
| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 18.29.38 ~ 19.44.39 |
|
||||
| `Seekbar components` | Adds options to hide or change components related to the seekbar. | 18.29.38 ~ 19.44.39 |
|
||||
| `Settings for YouTube` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 18.29.38 ~ 19.44.39 |
|
||||
| `Shorts components` | Adds options to hide or change components related to YouTube Shorts. | 18.29.38 ~ 19.44.39 |
|
||||
| `Snack bar components` | Adds options to hide or change components related to the snack bar. | 18.29.38 ~ 19.44.39 |
|
||||
| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as sponsored content. | 18.29.38 ~ 19.44.39 |
|
||||
| `Spoof app version` | Adds options to spoof the YouTube client version. This can be used to restore old UI elements and features. | 18.29.38 ~ 19.44.39 |
|
||||
| `Spoof streaming data` | Adds options to spoof the streaming data to allow playback. | 18.29.38 ~ 19.44.39 |
|
||||
@ -80,47 +82,47 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `Bitrate default value` | Sets the audio quality to 'Always High' when you first install the app. | 6.20.51 ~ 7.25.53 |
|
||||
| `Bypass image region restrictions` | Adds an option to use a different host for static images, so that images blocked in some countries can be received. | 6.20.51 ~ 7.25.53 |
|
||||
| `Certificate spoof` | Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate. | 6.20.51 ~ 7.25.53 |
|
||||
| `Change share sheet` | Adds an option to change the in-app share sheet to the system share sheet. | 6.20.51 ~ 7.25.53 |
|
||||
| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 6.20.51 ~ 7.25.53 |
|
||||
| `Custom branding icon for YouTube Music` | Changes the YouTube Music app icon to the icon specified in patch options. | 6.20.51 ~ 7.25.53 |
|
||||
| `Custom branding name for YouTube Music` | Renames the YouTube Music app to the name specified in patch options. | 6.20.51 ~ 7.25.53 |
|
||||
| `Custom header for YouTube Music` | Applies a custom header in the top left corner within the app. | 6.20.51 ~ 7.25.53 |
|
||||
| `Dark theme` | Changes the app's dark theme to the values specified in patch options. | 6.20.51 ~ 7.25.53 |
|
||||
| `Disable Cairo splash animation` | Adds an option to disable Cairo splash animation. | 7.06.54 ~ 7.25.53 |
|
||||
| `Disable DRC audio` | Adds an option to disable DRC (Dynamic Range Compression) audio. | 6.20.51 ~ 7.25.53 |
|
||||
| `Disable dislike redirection` | Adds an option to disable redirection to the next track when clicking the Dislike button. | 6.20.51 ~ 7.25.53 |
|
||||
| `Disable forced auto captions` | Adds an option to disable captions from being automatically enabled. | 6.20.51 ~ 7.25.53 |
|
||||
| `Disable music video in album` | Adds option to redirect music videos from albums for non-premium users. | 6.20.51 ~ 7.25.53 |
|
||||
| `Enable OPUS codec` | Adds an options to enable the OPUS audio codec if the player response includes it. | 6.20.51 ~ 7.25.53 |
|
||||
| `Enable debug logging` | Adds an option to enable debug logging. | 6.20.51 ~ 7.25.53 |
|
||||
| `Enable landscape mode` | Adds an option to enable landscape mode when rotating the screen on phones. | 6.20.51 ~ 7.25.53 |
|
||||
| `Flyout menu components` | Adds options to hide or change flyout menu components. | 6.20.51 ~ 7.25.53 |
|
||||
| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 6.20.51 ~ 7.25.53 |
|
||||
| `Hide account components` | Adds options to hide components related to the account menu. | 6.20.51 ~ 7.25.53 |
|
||||
| `Hide action bar components` | Adds options to hide action bar components and replace the offline download button with an external download button. | 6.20.51 ~ 7.25.53 |
|
||||
| `Hide ads` | Adds options to hide ads. | 6.20.51 ~ 7.25.53 |
|
||||
| `Hide layout components` | Adds options to hide general layout components. | 6.20.51 ~ 7.25.53 |
|
||||
| `Hide overlay filter` | Removes, at compile time, the dark overlay that appears when player flyout menus are open. | 6.20.51 ~ 7.25.53 |
|
||||
| `Hide player overlay filter` | Removes, at compile time, the dark overlay that appears when single-tapping in the player. | 6.20.51 ~ 7.25.53 |
|
||||
| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 6.20.51 ~ 7.25.53 |
|
||||
| `Player components` | Adds options to hide or change components related to the player. | 6.20.51 ~ 7.25.53 |
|
||||
| `Remove background playback restrictions` | Removes restrictions on background playback, including for kids videos. | 6.20.51 ~ 7.25.53 |
|
||||
| `Remove viewer discretion dialog` | Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction. | 6.20.51 ~ 7.25.53 |
|
||||
| `Restore old style library shelf` | Adds an option to return the Library tab to the old style. | 6.20.51 ~ 7.25.53 |
|
||||
| `Return YouTube Dislike` | Adds an option to show the dislike count of songs using the Return YouTube Dislike API. | 6.20.51 ~ 7.25.53 |
|
||||
| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 6.20.51 ~ 7.25.53 |
|
||||
| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | 6.20.51 ~ 7.25.53 |
|
||||
| `Settings for YouTube Music` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 6.20.51 ~ 7.25.53 |
|
||||
| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections. | 6.20.51 ~ 7.25.53 |
|
||||
| `Bitrate default value` | Sets the audio quality to 'Always High' when you first install the app. | 6.20.51 ~ 8.02.53 |
|
||||
| `Bypass image region restrictions` | Adds an option to use a different host for static images, so that images blocked in some countries can be received. | 6.20.51 ~ 8.02.53 |
|
||||
| `Certificate spoof` | Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate. | 6.20.51 ~ 8.02.53 |
|
||||
| `Change share sheet` | Adds an option to change the in-app share sheet to the system share sheet. | 6.20.51 ~ 8.02.53 |
|
||||
| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 6.20.51 ~ 8.02.53 |
|
||||
| `Custom branding icon for YouTube Music` | Changes the YouTube Music app icon to the icon specified in patch options. | 6.20.51 ~ 8.02.53 |
|
||||
| `Custom branding name for YouTube Music` | Changes the YouTube Music app name to the name specified in patch options. | 6.20.51 ~ 8.02.53 |
|
||||
| `Custom header for YouTube Music` | Applies a custom header in the top left corner within the app. | 6.20.51 ~ 8.02.53 |
|
||||
| `Dark theme` | Changes the app's dark theme to the values specified in patch options. | 6.20.51 ~ 8.02.53 |
|
||||
| `Disable Cairo splash animation` | Adds an option to disable Cairo splash animation. | 7.06.54 ~ 8.02.53 |
|
||||
| `Disable DRC audio` | Adds an option to disable DRC (Dynamic Range Compression) audio. | 6.20.51 ~ 8.02.53 |
|
||||
| `Disable dislike redirection` | Adds an option to disable redirection to the next track when clicking the Dislike button. | 6.20.51 ~ 8.02.53 |
|
||||
| `Disable forced auto captions` | Adds an option to disable captions from being automatically enabled. | 6.20.51 ~ 8.02.53 |
|
||||
| `Disable music video in album` | Adds option to redirect music videos from albums for non-premium users. | 6.20.51 ~ 8.02.53 |
|
||||
| `Enable OPUS codec` | Adds an option to enable the OPUS audio codec if the player response includes it. | 6.20.51 ~ 8.02.53 |
|
||||
| `Enable debug logging` | Adds an option to enable debug logging. | 6.20.51 ~ 8.02.53 |
|
||||
| `Enable landscape mode` | Adds an option to enable landscape mode when rotating the screen on phones. | 6.20.51 ~ 8.02.53 |
|
||||
| `Flyout menu components` | Adds options to hide or change flyout menu components. | 6.20.51 ~ 8.02.53 |
|
||||
| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 6.20.51 ~ 8.02.53 |
|
||||
| `Hide account components` | Adds options to hide components related to the account menu. | 6.20.51 ~ 8.02.53 |
|
||||
| `Hide action bar components` | Adds options to hide action bar components and replace the offline download button with an external download button. | 6.20.51 ~ 8.02.53 |
|
||||
| `Hide ads` | Adds options to hide ads. | 6.20.51 ~ 8.02.53 |
|
||||
| `Hide layout components` | Adds options to hide general layout components. | 6.20.51 ~ 8.02.53 |
|
||||
| `Hide overlay filter` | Removes, at compile time, the dark overlay that appears when player flyout menus are open. | 6.20.51 ~ 8.02.53 |
|
||||
| `Hide player overlay filter` | Removes, at compile time, the dark overlay that appears when single-tapping in the player. | 6.20.51 ~ 8.02.53 |
|
||||
| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 6.20.51 ~ 8.02.53 |
|
||||
| `Player components` | Adds options to hide or change components related to the player. | 6.20.51 ~ 8.02.53 |
|
||||
| `Remove background playback restrictions` | Removes restrictions on background playback, including for kids videos. | 6.20.51 ~ 8.02.53 |
|
||||
| `Remove viewer discretion dialog` | Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction. | 6.20.51 ~ 8.02.53 |
|
||||
| `Restore old style library shelf` | Adds an option to return the Library tab to the old style. | 6.20.51 ~ 8.02.53 |
|
||||
| `Return YouTube Dislike` | Adds an option to show the dislike count of songs using the Return YouTube Dislike API. | 6.20.51 ~ 8.02.53 |
|
||||
| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 6.20.51 ~ 8.02.53 |
|
||||
| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 6.20.51 ~ 8.02.53 |
|
||||
| `Settings for YouTube Music` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 6.20.51 ~ 8.02.53 |
|
||||
| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections. | 6.20.51 ~ 8.02.53 |
|
||||
| `Spoof app version` | Adds options to spoof the YouTube Music client version. This can remove the radio mode restriction in Canadian regions or disable real-time lyrics. | 6.20.51 ~ 7.16.53 |
|
||||
| `Spoof client` | Adds options to spoof the client to allow playback. | 6.20.51 ~ 7.16.53 |
|
||||
| `Spoof streaming data` | Adds options to spoof the streaming data to allow playback. | 6.20.51 ~ 7.25.53 |
|
||||
| `Translations for YouTube Music` | Add translations or remove string resources. | 6.20.51 ~ 7.25.53 |
|
||||
| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 6.20.51 ~ 7.25.53 |
|
||||
| `Visual preferences icons for YouTube Music` | Adds icons to specific preferences in the settings. | 6.20.51 ~ 7.25.53 |
|
||||
| `Spoof streaming data` | Adds options to spoof the streaming data to allow playback. | 6.20.51 ~ 8.02.53 |
|
||||
| `Translations for YouTube Music` | Add translations or remove string resources. | 6.20.51 ~ 8.02.53 |
|
||||
| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 6.20.51 ~ 8.02.53 |
|
||||
| `Visual preferences icons for YouTube Music` | Adds icons to specific preferences in the settings. | 6.20.51 ~ 8.02.53 |
|
||||
</details>
|
||||
|
||||
### [📦 `com.reddit.frontpage`](https://play.google.com/store/apps/details?id=com.reddit.frontpage)
|
||||
@ -129,7 +131,7 @@ 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. | ALL |
|
||||
| `Custom branding name for Reddit` | Renames the Reddit app to the name specified in patch options. | ALL |
|
||||
| `Custom branding name for Reddit` | Changes the Reddit app name to the name specified in patch options. | ALL |
|
||||
| `Disable screenshot popup` | Adds an option to disable the popup that appears when taking a screenshot. | ALL |
|
||||
| `Hide Recently Visited shelf` | Adds an option to hide the Recently Visited shelf in the sidebar. | ALL |
|
||||
| `Hide ads` | Adds options to hide ads. | ALL |
|
||||
@ -139,7 +141,7 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
|
||||
| `Open links externally` | Adds an option to always open links in your browser instead of in the in-app-browser. | ALL |
|
||||
| `Premium icon` | Unlocks premium app icons. | ALL |
|
||||
| `Remove subreddit dialog` | Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically. | ALL |
|
||||
| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | ALL |
|
||||
| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | ALL |
|
||||
| `Settings for Reddit` | Applies mandatory patches to implement ReVanced Extended settings into the application. | ALL |
|
||||
</details>
|
||||
|
||||
@ -181,7 +183,8 @@ Example:
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
|
@ -12,6 +12,7 @@ import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
import app.revanced.extension.shared.utils.ResourceUtils;
|
||||
|
||||
/**
|
||||
* @noinspection ALL
|
||||
@ -19,6 +20,18 @@ import app.revanced.extension.music.settings.Settings;
|
||||
@SuppressWarnings("unused")
|
||||
public class GeneralPatch {
|
||||
|
||||
// region [Change header] patch
|
||||
|
||||
public static int getHeaderDrawableId(int original) {
|
||||
final int headerId = ResourceUtils.getDrawableIdentifier("action_bar_logo");
|
||||
|
||||
return headerId == 0
|
||||
? original
|
||||
: headerId;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region [Change start page] patch
|
||||
|
||||
public static String changeStartPage(final String browseId) {
|
||||
|
@ -13,7 +13,9 @@ import app.revanced.extension.music.patches.misc.requests.PipedRequester;
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
import app.revanced.extension.music.shared.VideoInformation;
|
||||
import app.revanced.extension.music.utils.VideoUtils;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AlbumMusicVideoPatch {
|
||||
@ -98,8 +100,17 @@ public class AlbumMusicVideoPatch {
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
// This hook is always called off the main thread,
|
||||
// but this can later be called for the same video id from the main thread.
|
||||
// This is not a concern, since the fetch will always be finished
|
||||
// and never block the main thread.
|
||||
// But if debugging, then still verify this is the situation.
|
||||
if (BaseSettings.ENABLE_DEBUG_LOGGING.get() && !request.fetchCompleted() && Utils.isCurrentlyOnMainThread()) {
|
||||
Logger.printException(() -> "Error: Blocking main thread");
|
||||
}
|
||||
String songId = request.getStream();
|
||||
if (songId == null) {
|
||||
Logger.printDebug(() -> "Official song not found, videoId: " + videoId);
|
||||
return;
|
||||
}
|
||||
synchronized (lastVideoIds) {
|
||||
|
@ -12,8 +12,7 @@ import org.json.JSONObject;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
@ -25,38 +24,21 @@ import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
|
||||
public class PipedRequester {
|
||||
/**
|
||||
* How long to keep fetches until they are expired.
|
||||
*/
|
||||
private static final long CACHE_RETENTION_TIME_MILLISECONDS = 60 * 1000; // 1 Minute
|
||||
|
||||
private static final long MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000; // 20 seconds
|
||||
private static final long MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 4 * 1000; // 4 seconds
|
||||
|
||||
@GuardedBy("itself")
|
||||
private static final Map<String, PipedRequester> cache = new HashMap<>();
|
||||
private static final Map<String, PipedRequester> cache = new LinkedHashMap<>() {
|
||||
private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 10;
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry eldest) {
|
||||
return size() > NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK;
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
public static void fetchRequestIfNeeded(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) {
|
||||
synchronized (cache) {
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
if (Utils.isSDKAbove(25)) {
|
||||
cache.values().removeIf(request -> {
|
||||
final boolean expired = request.isExpired(now);
|
||||
if (expired) Logger.printDebug(() -> "Removing expired stream: " + request.videoId);
|
||||
return expired;
|
||||
});
|
||||
} else {
|
||||
Iterator<Map.Entry<String, PipedRequester>> itr = cache.entrySet().iterator();
|
||||
while (itr.hasNext()) {
|
||||
Map.Entry<String, PipedRequester> entry = itr.next();
|
||||
if (entry.getValue().isExpired(now)) {
|
||||
Logger.printDebug(() -> "Removing expired fetch: " + entry.getValue().videoId);
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cache.containsKey(videoId)) {
|
||||
PipedRequester pipedRequester = new PipedRequester(videoId, playlistId, playlistIndex);
|
||||
cache.put(videoId, pipedRequester);
|
||||
@ -85,7 +67,7 @@ public class PipedRequester {
|
||||
private static JSONObject send(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
Logger.printDebug(() -> "Fetching piped instances (videoId: '" + videoId +
|
||||
"', playlistId: '" + playlistId + "', playlistIndex: '" + playlistIndex + "'");
|
||||
"', playlistId: '" + playlistId + "', playlistIndex: '" + playlistIndex + "')");
|
||||
|
||||
try {
|
||||
HttpURLConnection connection = PipedRoutes.getPlaylistConnectionFromRoute(playlistId);
|
||||
@ -121,6 +103,8 @@ public class PipedRequester {
|
||||
if (songId.isEmpty()) {
|
||||
handleConnectionError("Url is empty!");
|
||||
} else if (!songId.equals(videoId)) {
|
||||
Logger.printDebug(() -> "Video found (videoId: '" + videoId +
|
||||
"', songId: '" + songId + "')");
|
||||
return songId;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
@ -145,26 +129,12 @@ public class PipedRequester {
|
||||
/**
|
||||
* Time this instance and the fetch future was created.
|
||||
*/
|
||||
private final long timeFetched;
|
||||
private final String videoId;
|
||||
private final Future<String> future;
|
||||
|
||||
private PipedRequester(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) {
|
||||
this.timeFetched = System.currentTimeMillis();
|
||||
this.videoId = videoId;
|
||||
this.future = Utils.submitOnBackgroundThread(() -> fetch(videoId, playlistId, playlistIndex));
|
||||
}
|
||||
|
||||
public boolean isExpired(long now) {
|
||||
final long timeSinceCreation = now - timeFetched;
|
||||
if (timeSinceCreation > CACHE_RETENTION_TIME_MILLISECONDS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only expired if the fetch failed (API null response).
|
||||
return (fetchCompleted() && getStream() == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the fetch call has completed.
|
||||
*/
|
||||
|
@ -1,5 +1,6 @@
|
||||
package app.revanced.extension.music.patches.navigation;
|
||||
|
||||
import static app.revanced.extension.shared.utils.StringRef.str;
|
||||
import static app.revanced.extension.shared.utils.Utils.hideViewUnderCondition;
|
||||
|
||||
import android.graphics.Color;
|
||||
@ -11,6 +12,7 @@ import androidx.annotation.NonNull;
|
||||
import app.revanced.extension.music.patches.utils.PatchStatus;
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
import app.revanced.extension.shared.utils.ResourceUtils;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class NavigationPatch {
|
||||
@ -20,10 +22,18 @@ public class NavigationPatch {
|
||||
|
||||
public static Enum<?> lastPivotTab;
|
||||
|
||||
public static int enableBlackNavigationBar() {
|
||||
return Settings.ENABLE_BLACK_NAVIGATION_BAR.get()
|
||||
? Color.BLACK
|
||||
: colorGrey12;
|
||||
public static int enableCustomNavigationBarColor() {
|
||||
try {
|
||||
if (Settings.ENABLE_CUSTOM_NAVIGATION_BAR_COLOR.get()) {
|
||||
return Color.parseColor(Settings.ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE.get());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Utils.showToastShort(str("revanced_custom_navigation_bar_color_value_invalid_invalid_toast"));
|
||||
Utils.showToastShort(str("revanced_extended_reset_to_default_toast"));
|
||||
Settings.ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE.resetToDefault();
|
||||
}
|
||||
|
||||
return colorGrey12;
|
||||
}
|
||||
|
||||
public static void hideNavigationLabel(TextView textview) {
|
||||
|
@ -12,10 +12,10 @@ import app.revanced.extension.shared.utils.ResourceUtils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class DrawableColorPatch {
|
||||
private static final int[] DARK_VALUES = {
|
||||
-14606047, // comments box background
|
||||
-16579837, // button container background in album
|
||||
-16777216, // button container background in playlist
|
||||
private static final int[] DARK_COLORS = {
|
||||
0xFF212121, // comments box background
|
||||
0xFF030303, // button container background in album
|
||||
0xFF000000, // button container background in playlist
|
||||
};
|
||||
|
||||
// background colors
|
||||
@ -26,10 +26,10 @@ public class DrawableColorPatch {
|
||||
private static final int elementsContainerIdentifier =
|
||||
ResourceUtils.getIdIdentifier("elements_container");
|
||||
|
||||
public static int getLithoColor(int originalValue) {
|
||||
return ArrayUtils.contains(DARK_VALUES, originalValue)
|
||||
public static int getLithoColor(int colorValue) {
|
||||
return ArrayUtils.contains(DARK_COLORS, colorValue)
|
||||
? blackColor
|
||||
: originalValue;
|
||||
: colorValue;
|
||||
}
|
||||
|
||||
public static void setHeaderGradient(ViewGroup viewGroup) {
|
||||
|
@ -116,8 +116,9 @@ public class Settings extends BaseSettings {
|
||||
PatchStatus.SpoofAppVersionDefaultString(), true);
|
||||
|
||||
|
||||
// PreferenceScreen: Navigation bar
|
||||
public static final BooleanSetting ENABLE_BLACK_NAVIGATION_BAR = new BooleanSetting("revanced_enable_black_navigation_bar", FALSE);
|
||||
// PreferenceScreen: Navigation Bar
|
||||
public static final BooleanSetting ENABLE_CUSTOM_NAVIGATION_BAR_COLOR = new BooleanSetting("revanced_enable_custom_navigation_bar_color", FALSE, true);
|
||||
public static final StringSetting ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE = new StringSetting("revanced_custom_navigation_bar_color_value", "#000000", true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_HOME_BUTTON = new BooleanSetting("revanced_hide_navigation_home_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_SAMPLES_BUTTON = new BooleanSetting("revanced_hide_navigation_samples_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_EXPLORE_BUTTON = new BooleanSetting("revanced_hide_navigation_explore_button", FALSE, true);
|
||||
@ -251,6 +252,7 @@ public class Settings extends BaseSettings {
|
||||
CUSTOM_FILTER_STRINGS.key,
|
||||
CUSTOM_PLAYBACK_SPEEDS.key,
|
||||
DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE.key,
|
||||
ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE.key,
|
||||
EXTERNAL_DOWNLOADER_PACKAGE_NAME.key,
|
||||
HIDE_ACCOUNT_MENU_FILTER_STRINGS.key,
|
||||
SB_API_URL.key,
|
||||
|
@ -5,6 +5,7 @@ import static app.revanced.extension.music.settings.Settings.CHANGE_START_PAGE;
|
||||
import static app.revanced.extension.music.settings.Settings.CUSTOM_FILTER_STRINGS;
|
||||
import static app.revanced.extension.music.settings.Settings.CUSTOM_PLAYBACK_SPEEDS;
|
||||
import static app.revanced.extension.music.settings.Settings.DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE;
|
||||
import static app.revanced.extension.music.settings.Settings.ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE;
|
||||
import static app.revanced.extension.music.settings.Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME;
|
||||
import static app.revanced.extension.music.settings.Settings.HIDE_ACCOUNT_MENU_FILTER_STRINGS;
|
||||
import static app.revanced.extension.music.settings.Settings.OPEN_DEFAULT_APP_SETTINGS;
|
||||
@ -140,6 +141,7 @@ public class ReVancedPreferenceFragment extends PreferenceFragment {
|
||||
} else if (settings.equals(BYPASS_IMAGE_REGION_RESTRICTIONS_DOMAIN)
|
||||
|| settings.equals(CUSTOM_FILTER_STRINGS)
|
||||
|| settings.equals(CUSTOM_PLAYBACK_SPEEDS)
|
||||
|| settings.equals(ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE)
|
||||
|| settings.equals(HIDE_ACCOUNT_MENU_FILTER_STRINGS)
|
||||
|| settings.equals(RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY)) {
|
||||
ResettableEditTextPreference.showDialog(mActivity, stringSetting);
|
||||
|
@ -8,7 +8,6 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import app.revanced.extension.reddit.settings.Settings;
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ -31,20 +30,12 @@ public class RemoveSubRedditDialogPatch {
|
||||
clickViewDelayed(cancelButtonView);
|
||||
}
|
||||
|
||||
public static void dismissDialogV2(Object object) {
|
||||
if (!Settings.REMOVE_NOTIFICATION_DIALOG.get())
|
||||
return;
|
||||
|
||||
Utils.runOnMainThreadDelayed(() -> {
|
||||
try {
|
||||
dismissRedditDialogV2(object);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "dismissDialogV2 failed", ex);
|
||||
}
|
||||
}, 0);
|
||||
public static boolean spoofHasBeenVisitedStatus(boolean hasBeenVisited) {
|
||||
return Settings.REMOVE_NSFW_DIALOG.get() || hasBeenVisited;
|
||||
}
|
||||
|
||||
private static void dismissRedditDialogV2(Object object) {
|
||||
public static boolean spoofLoggedInStatus(boolean isLoggedIn) {
|
||||
return !Settings.REMOVE_NOTIFICATION_DIALOG.get() && isLoggedIn;
|
||||
}
|
||||
|
||||
private static void clickViewDelayed(View view) {
|
||||
|
@ -41,7 +41,7 @@ public class MiscellaneousPreferenceCategory extends ConditionalPreferenceCatego
|
||||
addPreference(new TogglePreference(
|
||||
context,
|
||||
"Sanitize sharing links",
|
||||
"Removes tracking query parameters from URLs when sharing links.",
|
||||
"Sanitizes sharing links by removing tracking query parameters.",
|
||||
Settings.SANITIZE_URL_QUERY
|
||||
));
|
||||
}
|
||||
|
@ -289,7 +289,7 @@ object AppClient {
|
||||
/**
|
||||
* Android SDK version, equivalent to [Build.VERSION.SDK] (System property: ro.build.version.sdk)
|
||||
*/
|
||||
val androidSdkVersion: String = Build.VERSION.SDK,
|
||||
val androidSdkVersion: String? = null,
|
||||
/**
|
||||
* App version.
|
||||
*/
|
||||
@ -298,10 +298,6 @@ object AppClient {
|
||||
* GmsCore versionCode.
|
||||
*/
|
||||
val gmscoreVersionCode: String? = null,
|
||||
/**
|
||||
* ChipSet.
|
||||
*/
|
||||
val chipset: String? = null,
|
||||
/**
|
||||
* If the client can access the API logged in.
|
||||
* If false, 'Authorization' must not be included.
|
||||
@ -316,6 +312,10 @@ object AppClient {
|
||||
* Whether a poToken is required to get playback for more than 1 minute.
|
||||
*/
|
||||
val requirePoToken: Boolean = false,
|
||||
/**
|
||||
* Client name for innertube body.
|
||||
*/
|
||||
val clientName: String,
|
||||
/**
|
||||
* Friendly name displayed in stats for nerds.
|
||||
*/
|
||||
@ -329,9 +329,21 @@ object AppClient {
|
||||
userAgent = USER_AGENT_ANDROID_VR,
|
||||
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_VR,
|
||||
clientVersion = CLIENT_VERSION_ANDROID_VR,
|
||||
chipset = CHIPSET_ANDROID_VR,
|
||||
clientName = "ANDROID_VR",
|
||||
friendlyName = "Android VR"
|
||||
),
|
||||
ANDROID_VR_NO_AUTH(
|
||||
id = 28,
|
||||
deviceMake = DEVICE_MAKE_ANDROID_VR,
|
||||
deviceModel = DEVICE_MODEL_ANDROID_VR,
|
||||
osVersion = OS_VERSION_ANDROID_VR,
|
||||
userAgent = USER_AGENT_ANDROID_VR,
|
||||
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_VR,
|
||||
clientVersion = CLIENT_VERSION_ANDROID_VR,
|
||||
supportsCookies = false,
|
||||
clientName = "ANDROID_VR",
|
||||
friendlyName = "Android VR No auth"
|
||||
),
|
||||
ANDROID_UNPLUGGED(
|
||||
id = 29,
|
||||
deviceMake = DEVICE_MAKE_ANDROID_UNPLUGGED,
|
||||
@ -341,8 +353,8 @@ object AppClient {
|
||||
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_UNPLUGGED,
|
||||
clientVersion = CLIENT_VERSION_ANDROID_UNPLUGGED,
|
||||
gmscoreVersionCode = GMS_CORE_VERSION_CODE_ANDROID_UNPLUGGED,
|
||||
chipset = CHIPSET_ANDROID_UNPLUGGED,
|
||||
requireAuth = true,
|
||||
clientName = "ANDROID_UNPLUGGED",
|
||||
friendlyName = "Android TV"
|
||||
),
|
||||
ANDROID_CREATOR(
|
||||
@ -354,8 +366,8 @@ object AppClient {
|
||||
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_CREATOR,
|
||||
clientVersion = CLIENT_VERSION_ANDROID_CREATOR,
|
||||
gmscoreVersionCode = GMS_CORE_VERSION_CODE_ANDROID_CREATOR,
|
||||
chipset = CHIPSET_ANDROID_CREATOR,
|
||||
requireAuth = true,
|
||||
clientName = "ANDROID_CREATOR",
|
||||
friendlyName = "Android Studio"
|
||||
),
|
||||
IOS_UNPLUGGED(
|
||||
@ -367,6 +379,7 @@ object AppClient {
|
||||
userAgent = USER_AGENT_IOS_UNPLUGGED,
|
||||
clientVersion = CLIENT_VERSION_IOS_UNPLUGGED,
|
||||
requireAuth = true,
|
||||
clientName = "IOS_UNPLUGGED",
|
||||
friendlyName = if (forceAVC())
|
||||
"iOS TV Force AVC"
|
||||
else
|
||||
@ -382,6 +395,7 @@ object AppClient {
|
||||
clientVersion = CLIENT_VERSION_IOS,
|
||||
supportsCookies = false,
|
||||
requirePoToken = true,
|
||||
clientName = "IOS",
|
||||
friendlyName = if (forceAVC())
|
||||
"iOS Force AVC"
|
||||
else
|
||||
@ -396,13 +410,11 @@ object AppClient {
|
||||
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_MUSIC,
|
||||
clientVersion = CLIENT_VERSION_ANDROID_MUSIC,
|
||||
gmscoreVersionCode = GMS_CORE_VERSION_CODE_ANDROID_MUSIC,
|
||||
chipset = CHIPSET_ANDROID_MUSIC,
|
||||
requireAuth = true,
|
||||
clientName = "ANDROID_MUSIC",
|
||||
friendlyName = "Android Music"
|
||||
);
|
||||
|
||||
val clientName: String = name
|
||||
|
||||
companion object {
|
||||
val CLIENT_ORDER_TO_USE_YOUTUBE: Array<ClientType> = arrayOf(
|
||||
ANDROID_VR,
|
||||
@ -410,6 +422,7 @@ object AppClient {
|
||||
IOS_UNPLUGGED,
|
||||
ANDROID_CREATOR,
|
||||
IOS,
|
||||
ANDROID_VR_NO_AUTH,
|
||||
)
|
||||
|
||||
internal val CLIENT_ORDER_TO_USE_YOUTUBE_MUSIC: Array<ClientType> = arrayOf(
|
||||
|
@ -61,7 +61,7 @@ public abstract class Filter {
|
||||
*/
|
||||
public boolean skip(String conversionContext, SpannableString spannableString, Object span, int start, int end,
|
||||
int flags, boolean isWord, SpanType spanType, StringFilterGroup matchedGroup) {
|
||||
if (BaseSettings.ENABLE_DEBUG_LOGGING.get()) {
|
||||
if (BaseSettings.ENABLE_DEBUG_BUFFER_LOGGING.get()) {
|
||||
String filterSimpleName = getClass().getSimpleName();
|
||||
Logger.printDebug(() -> filterSimpleName + " Removed setSpan: " + spanType.type);
|
||||
}
|
||||
|
@ -13,8 +13,10 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import app.revanced.extension.shared.patches.client.AppClient.ClientType;
|
||||
import app.revanced.extension.shared.patches.spoof.requests.StreamingDataRequest;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
|
||||
@ -107,16 +109,6 @@ public class SpoofStreamingDataPatch {
|
||||
return SPOOF_STREAMING_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static Object isSpoofingEnabled(Object original) {
|
||||
if (!SPOOF_STREAMING_DATA) {
|
||||
return original;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* This method is only invoked when playing a livestream on an iOS client.
|
||||
@ -341,4 +333,12 @@ public class SpoofStreamingDataPatch {
|
||||
|
||||
return videoFormat;
|
||||
}
|
||||
|
||||
public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability {
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return BaseSettings.SPOOF_STREAMING_DATA.get() &&
|
||||
BaseSettings.SPOOF_STREAMING_DATA_TYPE.get() == ClientType.ANDROID_VR_NO_AUTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import app.revanced.extension.shared.patches.client.WebClient
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.requests.Route
|
||||
import app.revanced.extension.shared.requests.Route.CompiledRoute
|
||||
import app.revanced.extension.shared.settings.BaseSettings
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
import app.revanced.extension.shared.utils.Utils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
@ -43,6 +44,14 @@ object PlayerRoutes {
|
||||
"&alt=proto"
|
||||
).compile()
|
||||
|
||||
@JvmField
|
||||
val GET_VIDEO_DETAILS: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
"player" +
|
||||
"?prettyPrint=false" +
|
||||
"&fields=videoDetails.channelId"
|
||||
).compile()
|
||||
|
||||
private const val YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/"
|
||||
|
||||
/**
|
||||
@ -77,21 +86,24 @@ object PlayerRoutes {
|
||||
client.put("clientVersion", clientType.clientVersion)
|
||||
client.put("osName", clientType.osName)
|
||||
client.put("osVersion", clientType.osVersion)
|
||||
if (clientType.osName != "iOS") {
|
||||
if (clientType.androidSdkVersion != null) {
|
||||
client.put("androidSdkVersion", clientType.androidSdkVersion)
|
||||
if (clientType.gmscoreVersionCode != null) {
|
||||
client.put("gmscoreVersionCode", clientType.gmscoreVersionCode)
|
||||
}
|
||||
if (clientType.chipset != null) {
|
||||
client.put("chipset", clientType.chipset)
|
||||
}
|
||||
client.put(
|
||||
"hl",
|
||||
if (setLocale) {
|
||||
BaseSettings.SPOOF_STREAMING_DATA_LANGUAGE.get().language
|
||||
} else {
|
||||
LOCALE_LANGUAGE
|
||||
}
|
||||
}
|
||||
if (setLocale) {
|
||||
client.put("hl", LOCALE_LANGUAGE)
|
||||
client.put("gl", LOCALE_COUNTRY)
|
||||
client.put("timeZone", TIME_ZONE_ID)
|
||||
client.put("utcOffsetMinutes", "$UTC_OFFSET_MINUTES")
|
||||
}
|
||||
)
|
||||
client.put("gl", LOCALE_COUNTRY)
|
||||
client.put("timeZone", TIME_ZONE_ID)
|
||||
client.put("utcOffsetMinutes", "$UTC_OFFSET_MINUTES")
|
||||
|
||||
val context = JSONObject()
|
||||
context.put("client", client)
|
||||
|
||||
|
@ -93,9 +93,14 @@ class StreamingDataRequest private constructor(
|
||||
"X-GOOG-API-FORMAT-VERSION",
|
||||
VISITOR_ID_HEADER
|
||||
)
|
||||
private val SPOOF_STREAMING_DATA_TYPE: AppClient.ClientType =
|
||||
BaseSettings.SPOOF_STREAMING_DATA_TYPE.get()
|
||||
|
||||
private val CLIENT_ORDER_TO_USE: Array<AppClient.ClientType> =
|
||||
availableClientTypes(BaseSettings.SPOOF_STREAMING_DATA_TYPE.get())
|
||||
availableClientTypes(SPOOF_STREAMING_DATA_TYPE)
|
||||
|
||||
private val DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH: Boolean =
|
||||
SPOOF_STREAMING_DATA_TYPE == AppClient.ClientType.ANDROID_VR_NO_AUTH
|
||||
|
||||
private var lastSpoofedClientType: AppClient.ClientType? = null
|
||||
|
||||
@ -177,8 +182,6 @@ class StreamingDataRequest private constructor(
|
||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
|
||||
val setLocale =
|
||||
!clientType.supportsCookies || playerHeaders[AUTHORIZATION_HEADER] == null
|
||||
val usePoToken =
|
||||
clientType.requirePoToken && !StringUtils.isAnyEmpty(botGuardPoToken, visitorId)
|
||||
|
||||
@ -209,7 +212,7 @@ class StreamingDataRequest private constructor(
|
||||
videoId = videoId,
|
||||
botGuardPoToken = botGuardPoToken,
|
||||
visitorId = visitorId,
|
||||
setLocale = setLocale
|
||||
setLocale = DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH,
|
||||
)
|
||||
Logger.printDebug { "Set poToken (botGuardPoToken):\n$botGuardPoToken" }
|
||||
} else {
|
||||
@ -217,9 +220,10 @@ class StreamingDataRequest private constructor(
|
||||
createApplicationRequestBody(
|
||||
clientType = clientType,
|
||||
videoId = videoId,
|
||||
setLocale = setLocale
|
||||
setLocale = DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH,
|
||||
)
|
||||
}
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
|
||||
@ -275,7 +279,7 @@ class StreamingDataRequest private constructor(
|
||||
} else {
|
||||
BufferedInputStream(connection.inputStream).use { inputStream ->
|
||||
ByteArrayOutputStream().use { stream ->
|
||||
val buffer = ByteArray(8192)
|
||||
val buffer = ByteArray(4096)
|
||||
var bytesRead: Int
|
||||
while ((inputStream.read(buffer)
|
||||
.also { bytesRead = it }) >= 0
|
||||
|
@ -0,0 +1,114 @@
|
||||
package app.revanced.extension.shared.settings;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum AppLanguage {
|
||||
/**
|
||||
* The current app language.
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
// Language codes found in locale_config.xml
|
||||
// All region specific variants have been removed.
|
||||
AF,
|
||||
AM,
|
||||
AR,
|
||||
AS,
|
||||
AZ,
|
||||
BE,
|
||||
BG,
|
||||
BN,
|
||||
BS,
|
||||
CA,
|
||||
CS,
|
||||
DA,
|
||||
DE,
|
||||
EL,
|
||||
EN,
|
||||
ES,
|
||||
ET,
|
||||
EU,
|
||||
FA,
|
||||
FI,
|
||||
FR,
|
||||
GL,
|
||||
GU,
|
||||
HI,
|
||||
HE, // App uses obsolete 'IW' and not the modern 'HE' ISO code.
|
||||
HR,
|
||||
HU,
|
||||
HY,
|
||||
ID,
|
||||
IS,
|
||||
IT,
|
||||
JA,
|
||||
KA,
|
||||
KK,
|
||||
KM,
|
||||
KN,
|
||||
KO,
|
||||
KY,
|
||||
LO,
|
||||
LT,
|
||||
LV,
|
||||
MK,
|
||||
ML,
|
||||
MN,
|
||||
MR,
|
||||
MS,
|
||||
MY,
|
||||
NE,
|
||||
NL,
|
||||
NB,
|
||||
OR,
|
||||
PA,
|
||||
PL,
|
||||
PT,
|
||||
RO,
|
||||
RU,
|
||||
SI,
|
||||
SK,
|
||||
SL,
|
||||
SQ,
|
||||
SR,
|
||||
SV,
|
||||
SW,
|
||||
TA,
|
||||
TE,
|
||||
TH,
|
||||
TL,
|
||||
TR,
|
||||
UK,
|
||||
UR,
|
||||
UZ,
|
||||
VI,
|
||||
ZH,
|
||||
ZU;
|
||||
|
||||
private final String language;
|
||||
|
||||
AppLanguage() {
|
||||
language = name().toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The 2 letter ISO 639_1 language code.
|
||||
*/
|
||||
public String getLanguage() {
|
||||
// Changing the app language does not force the app to completely restart,
|
||||
// so the default needs to be the current language and not a static field.
|
||||
if (this == DEFAULT) {
|
||||
return Locale.getDefault().getLanguage();
|
||||
}
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
if (this == DEFAULT) {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
|
||||
return Locale.forLanguageTag(language);
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import static app.revanced.extension.shared.patches.PatchStatus.HideFullscreenAd
|
||||
|
||||
import app.revanced.extension.shared.patches.ReturnYouTubeUsernamePatch.DisplayFormat;
|
||||
import app.revanced.extension.shared.patches.client.AppClient.ClientType;
|
||||
import app.revanced.extension.shared.patches.spoof.SpoofStreamingDataPatch.AudioStreamLanguageOverrideAvailability;
|
||||
|
||||
/**
|
||||
* Settings shared across multiple apps.
|
||||
@ -22,6 +23,8 @@ public class BaseSettings {
|
||||
public static final BooleanSetting ENABLE_DEBUG_BUFFER_LOGGING = new BooleanSetting("revanced_enable_debug_buffer_logging", FALSE);
|
||||
public static final BooleanSetting SETTINGS_INITIALIZED = new BooleanSetting("revanced_settings_initialized", FALSE, false, false);
|
||||
|
||||
public static final EnumSetting<AppLanguage> REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true);
|
||||
|
||||
/**
|
||||
* These settings are used by YouTube and YouTube Music.
|
||||
*/
|
||||
@ -37,6 +40,7 @@ public class BaseSettings {
|
||||
public static final StringSetting RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY = new StringSetting("revanced_return_youtube_username_youtube_data_api_v3_developer_key", "", true, false);
|
||||
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA = new BooleanSetting("revanced_spoof_streaming_data", TRUE, true, "revanced_spoof_streaming_data_user_dialog_message");
|
||||
public static final EnumSetting<AppLanguage> SPOOF_STREAMING_DATA_LANGUAGE = new EnumSetting<>("revanced_spoof_streaming_data_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_streaming_data_ios_force_avc", FALSE, true,
|
||||
"revanced_spoof_streaming_data_ios_force_avc_user_dialog_message");
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE);
|
||||
|
@ -43,6 +43,8 @@ import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import app.revanced.extension.shared.settings.AppLanguage;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import kotlin.text.Regex;
|
||||
|
||||
@ -280,14 +282,13 @@ public class Utils {
|
||||
}
|
||||
|
||||
public static Resources getResources() {
|
||||
if (context != null) {
|
||||
return context.getResources();
|
||||
}
|
||||
Activity mActivity = activityRef.get();
|
||||
if (mActivity != null) {
|
||||
return mActivity.getResources();
|
||||
}
|
||||
Context mContext = getContext();
|
||||
if (mContext != null) {
|
||||
return mContext.getResources();
|
||||
}
|
||||
throw new IllegalStateException("Get resources failed");
|
||||
}
|
||||
|
||||
@ -301,7 +302,7 @@ public class Utils {
|
||||
* @param mContext Context to check locale.
|
||||
* @return Context with locale applied.
|
||||
*/
|
||||
public static Context getLocalizedContextAndSetResources(Context mContext) {
|
||||
public static Context getLocalizedContext(Context mContext) {
|
||||
Activity mActivity = activityRef.get();
|
||||
if (mActivity == null) {
|
||||
return mContext;
|
||||
@ -310,19 +311,15 @@ public class Utils {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Locale of MainActivity.
|
||||
Locale applicationLocale;
|
||||
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
|
||||
|
||||
// Locale of Application.
|
||||
Locale applicationLocale = language == AppLanguage.DEFAULT
|
||||
? mActivity.getResources().getConfiguration().locale
|
||||
: language.getLocale();
|
||||
|
||||
// Locale of Context.
|
||||
Locale contextLocale;
|
||||
|
||||
if (isSDKAbove(24)) {
|
||||
applicationLocale = mActivity.getResources().getConfiguration().getLocales().get(0);
|
||||
contextLocale = mContext.getResources().getConfiguration().getLocales().get(0);
|
||||
} else {
|
||||
applicationLocale = mActivity.getResources().getConfiguration().locale;
|
||||
contextLocale = mContext.getResources().getConfiguration().locale;
|
||||
}
|
||||
Locale contextLocale = mContext.getResources().getConfiguration().locale;
|
||||
|
||||
// If they are identical, no need to override them.
|
||||
if (applicationLocale == contextLocale) {
|
||||
@ -350,6 +347,14 @@ public class Utils {
|
||||
|
||||
context = appContext;
|
||||
|
||||
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
|
||||
if (language != AppLanguage.DEFAULT) {
|
||||
// Create a new context with the desired language.
|
||||
Configuration config = appContext.getResources().getConfiguration();
|
||||
config.setLocale(language.getLocale());
|
||||
context = appContext.createConfigurationContext(config);
|
||||
}
|
||||
|
||||
// In some apps like TikTok, the Setting classes can load in weird orders due to cyclic class dependencies.
|
||||
// Calling the regular printDebug method here can cause a Settings context null pointer exception,
|
||||
// even though the context is already set before the call.
|
||||
|
@ -20,6 +20,7 @@ public final class CarouselShelfFilter extends Filter {
|
||||
private static final String BROWSE_ID_HOME = "FEwhat_to_watch";
|
||||
private static final String BROWSE_ID_LIBRARY = "FElibrary";
|
||||
private static final String BROWSE_ID_MOVIE = "FEstorefront";
|
||||
private static final String BROWSE_ID_NEWS = "FEnews_destination";
|
||||
private static final String BROWSE_ID_NOTIFICATION = "FEactivity";
|
||||
private static final String BROWSE_ID_NOTIFICATION_INBOX = "FEnotifications_inbox";
|
||||
private static final String BROWSE_ID_PLAYLIST = "VLPL";
|
||||
@ -38,6 +39,7 @@ public final class CarouselShelfFilter extends Filter {
|
||||
BROWSE_ID_COURSES,
|
||||
BROWSE_ID_LIBRARY,
|
||||
BROWSE_ID_MOVIE,
|
||||
BROWSE_ID_NEWS,
|
||||
BROWSE_ID_NOTIFICATION_INBOX,
|
||||
BROWSE_ID_PREMIUM
|
||||
);
|
||||
|
@ -19,12 +19,6 @@ public final class FeedComponentsFilter extends Filter {
|
||||
private static final String INLINE_EXPANSION_PATH = "inline_expansion";
|
||||
private static final String FEED_VIDEO_PATH = "video_lockup_with_attachment";
|
||||
|
||||
private static final ByteArrayFilterGroup inlineExpansion =
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_EXPANDABLE_CHIP,
|
||||
"inline_expansion"
|
||||
);
|
||||
|
||||
private static final ByteArrayFilterGroup mixPlaylists =
|
||||
new ByteArrayFilterGroup(
|
||||
null,
|
||||
@ -37,14 +31,11 @@ public final class FeedComponentsFilter extends Filter {
|
||||
"channel_profile"
|
||||
);
|
||||
private static final StringTrieSearch mixPlaylistsContextExceptions = new StringTrieSearch();
|
||||
|
||||
private static final StringTrieSearch communityPostsFeedGroupSearch = new StringTrieSearch();
|
||||
private final StringFilterGroup channelProfile;
|
||||
private final StringFilterGroup communityPosts;
|
||||
private final StringFilterGroup expandableChip;
|
||||
private final ByteArrayFilterGroup visitStoreButton;
|
||||
private final StringFilterGroup videoLockup;
|
||||
|
||||
private static final StringTrieSearch communityPostsFeedGroupSearch = new StringTrieSearch();
|
||||
private final StringFilterGroupList communityPostsFeedGroup = new StringFilterGroupList();
|
||||
|
||||
|
||||
@ -90,18 +81,12 @@ public final class FeedComponentsFilter extends Filter {
|
||||
"cell_button.eml"
|
||||
);
|
||||
|
||||
videoLockup = new StringFilterGroup(
|
||||
null,
|
||||
FEED_VIDEO_PATH
|
||||
);
|
||||
|
||||
addIdentifierCallbacks(
|
||||
chipsShelf,
|
||||
communityPosts,
|
||||
expandableShelf,
|
||||
feedSearchBar,
|
||||
tasteBuilder,
|
||||
videoLockup
|
||||
tasteBuilder
|
||||
);
|
||||
|
||||
// Paths.
|
||||
@ -207,8 +192,7 @@ public final class FeedComponentsFilter extends Filter {
|
||||
notifyMe,
|
||||
playables,
|
||||
subscriptionsChannelBar,
|
||||
ticketShelf,
|
||||
videoLockup
|
||||
ticketShelf
|
||||
);
|
||||
|
||||
final StringFilterGroup communityPostsHomeAndRelatedVideos =
|
||||
@ -259,16 +243,12 @@ public final class FeedComponentsFilter extends Filter {
|
||||
if (!communityPostsFeedGroupSearch.matches(allValue) && Settings.HIDE_COMMUNITY_POSTS_CHANNEL.get()) {
|
||||
return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
if (!communityPostsFeedGroup.check(allValue).isFiltered()) {
|
||||
return false;
|
||||
}
|
||||
} else if (matchedGroup == expandableChip) {
|
||||
if (path.startsWith(FEED_VIDEO_PATH)) {
|
||||
if (communityPostsFeedGroup.check(allValue).isFiltered()) {
|
||||
return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
} else if (matchedGroup == videoLockup) {
|
||||
if (contentIndex == 0 && path.startsWith("CellType|") && inlineExpansion.check(protobufBufferArray).isFiltered()) {
|
||||
} else if (matchedGroup == expandableChip) {
|
||||
if (path.startsWith(FEED_VIDEO_PATH)) {
|
||||
return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
|
@ -5,7 +5,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
|
||||
@ -36,11 +35,12 @@ import app.revanced.extension.youtube.shared.VideoInformation;
|
||||
public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
||||
|
||||
/**
|
||||
* Last unique video id's loaded. Value is ignored and Map is treated as a Set.
|
||||
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry().
|
||||
* Last unique video id's loaded.
|
||||
* Key is a String represeting the video id.
|
||||
* Value is a ByteArrayFilterGroup used for performing KMP pattern searching.
|
||||
*/
|
||||
@GuardedBy("itself")
|
||||
private static final Map<String, Boolean> lastVideoIds = new LinkedHashMap<>() {
|
||||
private static final Map<String, ByteArrayFilterGroup> lastVideoIds = new LinkedHashMap<>() {
|
||||
/**
|
||||
* Number of video id's to keep track of for searching thru the buffer.
|
||||
* A minimum value of 3 should be sufficient, but check a few more just in case.
|
||||
@ -101,8 +101,11 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
||||
return;
|
||||
}
|
||||
synchronized (lastVideoIds) {
|
||||
if (lastVideoIds.put(videoId, Boolean.TRUE) == null) {
|
||||
Logger.printDebug(() -> "New Short video id: " + videoId);
|
||||
if (!lastVideoIds.containsKey(videoId)) {
|
||||
Logger.printDebug(() -> "New Shorts video id: " + videoId);
|
||||
// Put a placeholder first
|
||||
lastVideoIds.put(videoId, null);
|
||||
lastVideoIds.put(videoId, new ByteArrayFilterGroup(null, videoId));
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@ -114,7 +117,12 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
||||
* This could use {@link TrieSearch}, but since the patterns are constantly changing
|
||||
* the overhead of updating the Trie might negate the search performance gain.
|
||||
*/
|
||||
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text) {
|
||||
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text,
|
||||
@Nullable ByteArrayFilterGroup videoIdFilter) {
|
||||
// If a video filter is available, check it first.
|
||||
if (videoIdFilter != null) {
|
||||
return videoIdFilter.check(array).isFiltered();
|
||||
}
|
||||
for (int i = 0, lastArrayStartIndex = array.length - text.length(); i <= lastArrayStartIndex; i++) {
|
||||
boolean found = true;
|
||||
for (int j = 0, textLength = text.length(); j < textLength; j++) {
|
||||
@ -154,8 +162,10 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
||||
@Nullable
|
||||
private String findVideoId(byte[] protobufBufferArray) {
|
||||
synchronized (lastVideoIds) {
|
||||
for (String videoId : lastVideoIds.keySet()) {
|
||||
if (byteArrayContainsString(protobufBufferArray, videoId)) {
|
||||
for (Map.Entry<String, ByteArrayFilterGroup> entry : lastVideoIds.entrySet()) {
|
||||
final String videoId = entry.getKey();
|
||||
final ByteArrayFilterGroup videoIdFilter = entry.getValue();
|
||||
if (byteArrayContainsString(protobufBufferArray, videoId, videoIdFilter)) {
|
||||
return videoId;
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
|
||||
@ -30,11 +29,12 @@ public final class ShortsCustomActionsFilter extends Filter {
|
||||
SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU_ENABLED || SHORTS_CUSTOM_ACTIONS_TOOLBAR_ENABLED;
|
||||
|
||||
/**
|
||||
* Last unique video id's loaded. Value is ignored and Map is treated as a Set.
|
||||
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry().
|
||||
* Last unique video id's loaded.
|
||||
* Key is a String represeting the video id.
|
||||
* Value is a ByteArrayFilterGroup used for performing KMP pattern searching.
|
||||
*/
|
||||
@GuardedBy("itself")
|
||||
private static final Map<String, Boolean> lastVideoIds = new LinkedHashMap<>() {
|
||||
private static final Map<String, ByteArrayFilterGroup> lastVideoIds = new LinkedHashMap<>() {
|
||||
/**
|
||||
* Number of video id's to keep track of for searching thru the buffer.
|
||||
* A minimum value of 3 should be sufficient, but check a few more just in case.
|
||||
@ -117,7 +117,11 @@ public final class ShortsCustomActionsFilter extends Filter {
|
||||
return;
|
||||
}
|
||||
synchronized (lastVideoIds) {
|
||||
lastVideoIds.putIfAbsent(videoId, Boolean.TRUE);
|
||||
if (!lastVideoIds.containsKey(videoId)) {
|
||||
// Put a placeholder first
|
||||
lastVideoIds.put(videoId, null);
|
||||
lastVideoIds.put(videoId, new ByteArrayFilterGroup(null, videoId));
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "newPlayerResponseVideoId failure", ex);
|
||||
@ -129,7 +133,12 @@ public final class ShortsCustomActionsFilter extends Filter {
|
||||
* This could use {@link TrieSearch}, but since the patterns are constantly changing
|
||||
* the overhead of updating the Trie might negate the search performance gain.
|
||||
*/
|
||||
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text) {
|
||||
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text,
|
||||
@Nullable ByteArrayFilterGroup videoIdFilter) {
|
||||
// If a video filter is available, check it first.
|
||||
if (videoIdFilter != null) {
|
||||
return videoIdFilter.check(array).isFiltered();
|
||||
}
|
||||
for (int i = 0, lastArrayStartIndex = array.length - text.length(); i <= lastArrayStartIndex; i++) {
|
||||
boolean found = true;
|
||||
for (int j = 0, textLength = text.length(); j < textLength; j++) {
|
||||
@ -164,9 +173,12 @@ public final class ShortsCustomActionsFilter extends Filter {
|
||||
|
||||
private void findVideoId(byte[] protobufBufferArray) {
|
||||
synchronized (lastVideoIds) {
|
||||
for (String videoId : lastVideoIds.keySet()) {
|
||||
if (byteArrayContainsString(protobufBufferArray, videoId)) {
|
||||
for (Map.Entry<String, ByteArrayFilterGroup> entry : lastVideoIds.entrySet()) {
|
||||
final String videoId = entry.getKey();
|
||||
final ByteArrayFilterGroup videoIdFilter = entry.getValue();
|
||||
if (byteArrayContainsString(protobufBufferArray, videoId, videoIdFilter)) {
|
||||
setShortsVideoId(videoId, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,9 +41,13 @@ public final class ChangeStartPagePatch {
|
||||
* Channel id, this can be used as a browseId.
|
||||
*/
|
||||
COURSES("UCtFRv9O2AHqOZjjynzrv-xg", TRUE),
|
||||
FASHION("UCrpQ4p1Ql_hG8rKXIKM1MOQ", TRUE),
|
||||
GAMING("UCOpNcN46UbXVtpKMrmU4Abg", TRUE),
|
||||
LIVE("UC4R8DWoMoI7CAwX8_LjQHig", TRUE),
|
||||
MUSIC("UC-9-kyTW8ZkZNDHQJ6FgpwQ", TRUE),
|
||||
NEWS("UCYfdidRxbB8Qhf0Nx7ioOYw", TRUE),
|
||||
PODCASTS("UCNVkxRPqsBNejO6B9thG9Xw", TRUE),
|
||||
SHOPPING("UCkYQyvc_i9hXEo4xic9Hh2g", TRUE),
|
||||
SPORTS("UCEgdi0XIXXZ-qJOFPf4JSKw", TRUE),
|
||||
|
||||
/**
|
||||
|
@ -188,10 +188,6 @@ public class GeneralPatch {
|
||||
return Settings.HIDE_FLOATING_MICROPHONE.get() || original;
|
||||
}
|
||||
|
||||
public static boolean hideSnackBar() {
|
||||
return Settings.HIDE_SNACK_BAR.get();
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region [Hide navigation bar components] patch
|
||||
|
@ -0,0 +1,81 @@
|
||||
package app.revanced.extension.youtube.patches.general;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.youtube.patches.general.requests.VideoDetailsRequest;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.utils.VideoUtils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class OpenChannelOfLiveAvatarPatch {
|
||||
private static final boolean CHANGE_LIVE_RING_CLICK_ACTION =
|
||||
Settings.CHANGE_LIVE_RING_CLICK_ACTION.get();
|
||||
|
||||
private static final AtomicBoolean engagementPanelOpen = new AtomicBoolean(false);
|
||||
private static volatile boolean liveChannelAvatarClicked = false;
|
||||
private static volatile String videoId = "";
|
||||
|
||||
public static void showEngagementPanel(@Nullable Object object) {
|
||||
engagementPanelOpen.set(object != null);
|
||||
}
|
||||
|
||||
public static void hideEngagementPanel() {
|
||||
engagementPanelOpen.compareAndSet(true, false);
|
||||
}
|
||||
|
||||
public static void liveChannelAvatarClicked() {
|
||||
liveChannelAvatarClicked = true;
|
||||
}
|
||||
|
||||
public static boolean openChannelOfLiveAvatar() {
|
||||
try {
|
||||
if (!CHANGE_LIVE_RING_CLICK_ACTION) {
|
||||
return false;
|
||||
}
|
||||
if (!liveChannelAvatarClicked) {
|
||||
return false;
|
||||
}
|
||||
if (engagementPanelOpen.get()) {
|
||||
return false;
|
||||
}
|
||||
VideoDetailsRequest request = VideoDetailsRequest.getRequestForVideoId(videoId);
|
||||
if (request != null) {
|
||||
String channelId = request.getInfo();
|
||||
if (channelId != null) {
|
||||
videoId = "";
|
||||
liveChannelAvatarClicked = false;
|
||||
VideoUtils.openChannel(channelId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "openChannelOfLiveAvatar failure", ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void openChannelOfLiveAvatar(String newlyLoadedVideoId) {
|
||||
try {
|
||||
if (!CHANGE_LIVE_RING_CLICK_ACTION) {
|
||||
return;
|
||||
}
|
||||
if (!liveChannelAvatarClicked) {
|
||||
return;
|
||||
}
|
||||
if (engagementPanelOpen.get()) {
|
||||
return;
|
||||
}
|
||||
if (newlyLoadedVideoId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
videoId = newlyLoadedVideoId;
|
||||
VideoDetailsRequest.fetchRequestIfNeeded(newlyLoadedVideoId);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "openChannelOfLiveAvatar failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -22,7 +22,8 @@ public final class SettingsMenuPatch extends BaseSettingsMenuPatch {
|
||||
GENERAL("general_key", Settings.HIDE_SETTINGS_MENU_GENERAL.get()),
|
||||
ACCOUNT("account_switcher_key", Settings.HIDE_SETTINGS_MENU_ACCOUNT.get()),
|
||||
DATA_SAVING("data_saving_settings_key", Settings.HIDE_SETTINGS_MENU_DATA_SAVING.get()),
|
||||
AUTOPLAY("auto_play_key", Settings.HIDE_SETTINGS_MENU_AUTOPLAY.get()),
|
||||
AUTOPLAY("auto_play_key", Settings.HIDE_SETTINGS_MENU_AUTOPLAY_PLAYBACK.get()),
|
||||
PLAYBACK("playback_key", Settings.HIDE_SETTINGS_MENU_AUTOPLAY_PLAYBACK.get()),
|
||||
VIDEO_QUALITY_PREFERENCES("video_quality_settings_key", Settings.HIDE_SETTINGS_MENU_VIDEO_QUALITY_PREFERENCES.get()),
|
||||
POST_PURCHASE("yt_unlimited_post_purchase_key", Settings.HIDE_SETTINGS_MENU_POST_PURCHASE.get()),
|
||||
OFFLINE("offline_key", Settings.HIDE_SETTINGS_MENU_OFFLINE.get()),
|
||||
|
@ -0,0 +1,115 @@
|
||||
package app.revanced.extension.youtube.patches.general;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import app.revanced.extension.shared.utils.ResourceUtils;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.utils.ThemeUtils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class SnackBarPatch {
|
||||
private static final boolean HIDE_SNACK_BAR =
|
||||
Settings.HIDE_SNACK_BAR.get();
|
||||
private static final boolean HIDE_SERVER_SIDE_SNACK_BAR =
|
||||
Settings.HIDE_SERVER_SIDE_SNACK_BAR.get();
|
||||
private static final boolean CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND =
|
||||
!HIDE_SNACK_BAR && !HIDE_SERVER_SIDE_SNACK_BAR && Settings.CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND.get();
|
||||
private static final boolean INVERT_SNACK_BAR_THEME =
|
||||
!HIDE_SNACK_BAR && Settings.INVERT_SNACK_BAR_THEME.get();
|
||||
private static final boolean INVERT_SERVER_SIDE_SNACK_BAR_THEME =
|
||||
!HIDE_SERVER_SIDE_SNACK_BAR && INVERT_SNACK_BAR_THEME;
|
||||
private static final int SNACK_BAR_BLACK_COLOR = 0xFF0F0F0F;
|
||||
private static final int SNACK_BAR_WHITE_COLOR = 0xFFF1F1F1;
|
||||
private static final AtomicBoolean lithoSnackBarLoaded = new AtomicBoolean(false);
|
||||
private static int blackColor = 0;
|
||||
private static int whiteColor = 0;
|
||||
|
||||
public static boolean hideSnackBar() {
|
||||
return HIDE_SNACK_BAR;
|
||||
}
|
||||
|
||||
public static void hideLithoSnackBar(FrameLayout frameLayout) {
|
||||
if (HIDE_SERVER_SIDE_SNACK_BAR) {
|
||||
Utils.hideViewByLayoutParams(frameLayout);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLithoSnackBarBackground(View view) {
|
||||
if (CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND) {
|
||||
int snackBarRoundedCornersBackgroundIdentifier =
|
||||
ResourceUtils.getDrawableIdentifier("snackbar_rounded_corners_background");
|
||||
Context mContext = invertSnackBarTheme(view.getContext());
|
||||
Drawable snackBarRoundedCornersBackground = mContext.getDrawable(snackBarRoundedCornersBackgroundIdentifier);
|
||||
if (snackBarRoundedCornersBackground != null) {
|
||||
view.setBackground(snackBarRoundedCornersBackground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLithoSnackBarBackgroundColor(FrameLayout frameLayout, int color) {
|
||||
if (CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND) {
|
||||
return;
|
||||
}
|
||||
frameLayout.setBackgroundColor(color);
|
||||
}
|
||||
|
||||
public static Context invertSnackBarTheme(Context mContext) {
|
||||
if (INVERT_SERVER_SIDE_SNACK_BAR_THEME) {
|
||||
String styleId = ThemeUtils.isDarkTheme()
|
||||
? "Base.Theme.YouTube.Light"
|
||||
: "Base.Theme.YouTube.Dark";
|
||||
int styleIdentifier = ResourceUtils.getStyleIdentifier(styleId);
|
||||
mContext = new ContextThemeWrapper(mContext, styleIdentifier);
|
||||
}
|
||||
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public static Enum<?> invertSnackBarTheme(Enum<?> appTheme, Enum<?> darkTheme) {
|
||||
if (INVERT_SNACK_BAR_THEME) {
|
||||
return appTheme == darkTheme
|
||||
? null
|
||||
: darkTheme;
|
||||
}
|
||||
|
||||
return appTheme;
|
||||
}
|
||||
|
||||
public static void lithoSnackBarLoaded() {
|
||||
lithoSnackBarLoaded.compareAndSet(false, true);
|
||||
}
|
||||
|
||||
public static int getLithoColor(int originalValue) {
|
||||
if (CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND &&
|
||||
lithoSnackBarLoaded.compareAndSet(true, false)) {
|
||||
if (originalValue == SNACK_BAR_BLACK_COLOR) {
|
||||
return INVERT_SERVER_SIDE_SNACK_BAR_THEME
|
||||
? getWhiteColor()
|
||||
: getBlackColor();
|
||||
} else if (originalValue == SNACK_BAR_WHITE_COLOR) {
|
||||
return INVERT_SERVER_SIDE_SNACK_BAR_THEME
|
||||
? getBlackColor()
|
||||
: getWhiteColor();
|
||||
}
|
||||
}
|
||||
|
||||
return originalValue;
|
||||
}
|
||||
|
||||
private static int getBlackColor() {
|
||||
if (blackColor == 0) blackColor = ResourceUtils.getColor("revanced_snack_bar_color_dark");
|
||||
return blackColor;
|
||||
}
|
||||
|
||||
private static int getWhiteColor() {
|
||||
if (whiteColor == 0) whiteColor = ResourceUtils.getColor("revanced_snack_bar_color_light");
|
||||
return whiteColor;
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
package app.revanced.extension.youtube.patches.general.requests
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.annotation.GuardedBy
|
||||
import app.revanced.extension.shared.patches.client.WebClient
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
import app.revanced.extension.shared.utils.Utils
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
class VideoDetailsRequest private constructor(
|
||||
private val videoId: String
|
||||
) {
|
||||
private val future: Future<String> = Utils.submitOnBackgroundThread {
|
||||
fetch(videoId)
|
||||
}
|
||||
|
||||
val info: String?
|
||||
get() {
|
||||
try {
|
||||
return future[MAX_MILLISECONDS_TO_WAIT_FOR_FETCH, TimeUnit.MILLISECONDS]
|
||||
} catch (ex: TimeoutException) {
|
||||
Logger.printInfo(
|
||||
{ "getInfo timed out" },
|
||||
ex
|
||||
)
|
||||
} catch (ex: InterruptedException) {
|
||||
Logger.printException(
|
||||
{ "getInfo interrupted" },
|
||||
ex
|
||||
)
|
||||
Thread.currentThread().interrupt() // Restore interrupt status flag.
|
||||
} catch (ex: ExecutionException) {
|
||||
Logger.printException(
|
||||
{ "getInfo failure" },
|
||||
ex
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000L // 20 seconds
|
||||
|
||||
@GuardedBy("itself")
|
||||
val cache: MutableMap<String, VideoDetailsRequest> = Collections.synchronizedMap(
|
||||
object : LinkedHashMap<String, VideoDetailsRequest>(100) {
|
||||
private val CACHE_LIMIT = 50
|
||||
|
||||
override fun removeEldestEntry(eldest: Map.Entry<String, VideoDetailsRequest>): Boolean {
|
||||
return size > CACHE_LIMIT // Evict the oldest entry if over the cache limit.
|
||||
}
|
||||
})
|
||||
|
||||
@JvmStatic
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
fun fetchRequestIfNeeded(videoId: String) {
|
||||
cache[videoId] = VideoDetailsRequest(videoId)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getRequestForVideoId(videoId: String): VideoDetailsRequest? {
|
||||
synchronized(cache) {
|
||||
return cache[videoId]
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleConnectionError(toastMessage: String, ex: Exception?) {
|
||||
Logger.printInfo({ toastMessage }, ex)
|
||||
}
|
||||
|
||||
private fun sendRequest(videoId: String): JSONObject? {
|
||||
val startTime = System.currentTimeMillis()
|
||||
val clientType = WebClient.ClientType.MWEB
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching video details request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.GET_VIDEO_DETAILS,
|
||||
clientType
|
||||
)
|
||||
val requestBody =
|
||||
PlayerRoutes.createWebInnertubeBody(clientType, videoId)
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
|
||||
val responseCode = connection.responseCode
|
||||
if (responseCode == 200) return Requester.parseJSONObject(connection)
|
||||
|
||||
handleConnectionError(
|
||||
(clientTypeName + " not available with response code: "
|
||||
+ responseCode + " message: " + connection.responseMessage),
|
||||
null
|
||||
)
|
||||
} catch (ex: SocketTimeoutException) {
|
||||
handleConnectionError("Connection timeout", ex)
|
||||
} catch (ex: IOException) {
|
||||
handleConnectionError("Network error", ex)
|
||||
} catch (ex: Exception) {
|
||||
Logger.printException({ "sendRequest failed" }, ex)
|
||||
} finally {
|
||||
Logger.printDebug { "video: " + videoId + " took: " + (System.currentTimeMillis() - startTime) + "ms" }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun parseResponse(videoDetailsJson: JSONObject): String? {
|
||||
try {
|
||||
return videoDetailsJson
|
||||
.getJSONObject("videoDetails")
|
||||
.getString("channelId")
|
||||
} catch (e: JSONException) {
|
||||
Logger.printException(
|
||||
{ "Fetch failed while processing response data for response: $videoDetailsJson" },
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun fetch(videoId: String): String? {
|
||||
val videoDetailsJson = sendRequest(videoId)
|
||||
if (videoDetailsJson != null) {
|
||||
return parseResponse(videoDetailsJson)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package app.revanced.extension.youtube.patches.misc;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ExternalBrowserPatch {
|
||||
|
||||
public static String enableExternalBrowser(final String original) {
|
||||
if (!Settings.ENABLE_EXTERNAL_BROWSER.get())
|
||||
return original;
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
@ -10,9 +10,9 @@ import app.revanced.extension.youtube.settings.Settings;
|
||||
public class OpenLinksDirectlyPatch {
|
||||
private static final String YOUTUBE_REDIRECT_PATH = "/redirect";
|
||||
|
||||
public static Uri enableBypassRedirect(String uri) {
|
||||
public static Uri parseRedirectUri(String uri) {
|
||||
final Uri parsed = Uri.parse(uri);
|
||||
if (!Settings.ENABLE_OPEN_LINKS_DIRECTLY.get())
|
||||
if (!Settings.BYPASS_URL_REDIRECTS.get())
|
||||
return parsed;
|
||||
|
||||
if (Objects.equals(parsed.getPath(), YOUTUBE_REDIRECT_PATH)) {
|
||||
|
@ -0,0 +1,15 @@
|
||||
package app.revanced.extension.youtube.patches.misc;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class OpenLinksExternallyPatch {
|
||||
|
||||
// renamed from 'enableExternalBrowser'
|
||||
public static String openLinksExternally(final String original) {
|
||||
if (!Settings.OPEN_LINKS_EXTERNALLY.get())
|
||||
return original;
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package app.revanced.extension.youtube.patches.player;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.VideoInformation;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ActionButtonsPatch {
|
||||
|
||||
public enum ActionButton {
|
||||
INDEX_7(Settings.HIDE_ACTION_BUTTON_INDEX_7, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_7, 7),
|
||||
INDEX_6(Settings.HIDE_ACTION_BUTTON_INDEX_6, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_6, 6),
|
||||
INDEX_5(Settings.HIDE_ACTION_BUTTON_INDEX_5, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_5, 5),
|
||||
INDEX_4(Settings.HIDE_ACTION_BUTTON_INDEX_4, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_4, 4),
|
||||
INDEX_3(Settings.HIDE_ACTION_BUTTON_INDEX_3, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_3, 3),
|
||||
INDEX_2(Settings.HIDE_ACTION_BUTTON_INDEX_2, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_2, 2),
|
||||
INDEX_1(Settings.HIDE_ACTION_BUTTON_INDEX_1, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_1, 1),
|
||||
INDEX_0(Settings.HIDE_ACTION_BUTTON_INDEX_0, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_0, 0);
|
||||
|
||||
private final BooleanSetting generalSetting;
|
||||
private final BooleanSetting liveSetting;
|
||||
private final int index;
|
||||
|
||||
ActionButton(final BooleanSetting generalSetting, final BooleanSetting liveSetting, final int index) {
|
||||
this.generalSetting = generalSetting;
|
||||
this.liveSetting = liveSetting;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TARGET_COMPONENT_TYPE = "LazilyConvertedElement";
|
||||
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
|
||||
|
||||
public static List<Object> hideActionButtonByIndex(@Nullable List<Object> list, @Nullable String identifier) {
|
||||
try {
|
||||
if (identifier != null &&
|
||||
identifier.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) &&
|
||||
list != null &&
|
||||
!list.isEmpty() &&
|
||||
list.get(0).toString().equals(TARGET_COMPONENT_TYPE)
|
||||
) {
|
||||
final int size = list.size();
|
||||
final boolean isLive = VideoInformation.getLiveStreamState();
|
||||
for (ActionButton button : ActionButton.values()) {
|
||||
if (size > button.index && (isLive ? button.liveSetting.get() : button.generalSetting.get())) {
|
||||
list.remove(button.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "hideActionButtonByIndex failure", ex);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
@ -27,23 +27,28 @@ public class SeekbarColorPatch {
|
||||
private static final int ORIGINAL_SEEKBAR_COLOR = 0xFFFF0000;
|
||||
|
||||
/**
|
||||
* Default colors of the gradient seekbar.
|
||||
* Feed default colors of the gradient seekbar.
|
||||
*/
|
||||
private static final int[] ORIGINAL_SEEKBAR_GRADIENT_COLORS = {0xFFFF0033, 0xFFFF2791};
|
||||
private static final int[] FEED_ORIGINAL_SEEKBAR_GRADIENT_COLORS = {0xFFFF0033, 0xFFFF2791};
|
||||
|
||||
/**
|
||||
* Default positions of the gradient seekbar.
|
||||
* Feed default positions of the gradient seekbar.
|
||||
*/
|
||||
private static final float[] ORIGINAL_SEEKBAR_GRADIENT_POSITIONS = {0.8f, 1.0f};
|
||||
private static final float[] FEED_ORIGINAL_SEEKBAR_GRADIENT_POSITIONS = {0.8f, 1.0f};
|
||||
|
||||
/**
|
||||
* Default YouTube seekbar color brightness.
|
||||
*/
|
||||
private static final float ORIGINAL_SEEKBAR_COLOR_BRIGHTNESS;
|
||||
|
||||
/**
|
||||
* Empty seekbar gradient, if hide seekbar in feed is enabled.
|
||||
*/
|
||||
private static final int[] HIDDEN_SEEKBAR_GRADIENT_COLORS = {0x00000000, 0x00000000};
|
||||
|
||||
/**
|
||||
* If {@link Settings#ENABLE_CUSTOM_SEEKBAR_COLOR} is enabled,
|
||||
* this is the color value of {@link Settings#ENABLE_CUSTOM_SEEKBAR_COLOR_VALUE}.
|
||||
* this is the color value of {@link Settings#CUSTOM_SEEKBAR_COLOR_VALUE}.
|
||||
* Otherwise this is {@link #ORIGINAL_SEEKBAR_COLOR}.
|
||||
*/
|
||||
private static int seekbarColor = ORIGINAL_SEEKBAR_COLOR;
|
||||
@ -53,6 +58,11 @@ public class SeekbarColorPatch {
|
||||
*/
|
||||
private static final float[] customSeekbarColorHSV = new float[3];
|
||||
|
||||
/**
|
||||
* Custom seekbar color, used for linear gradient replacements.
|
||||
*/
|
||||
private static final int[] customSeekbarColorInt = new int[2];
|
||||
|
||||
static {
|
||||
float[] hsv = new float[3];
|
||||
Color.colorToHSV(ORIGINAL_SEEKBAR_COLOR, hsv);
|
||||
@ -61,16 +71,18 @@ public class SeekbarColorPatch {
|
||||
if (CUSTOM_SEEKBAR_COLOR_ENABLED) {
|
||||
loadCustomSeekbarColor();
|
||||
}
|
||||
|
||||
Arrays.fill(customSeekbarColorInt, seekbarColor);
|
||||
}
|
||||
|
||||
private static void loadCustomSeekbarColor() {
|
||||
try {
|
||||
seekbarColor = Color.parseColor(Settings.ENABLE_CUSTOM_SEEKBAR_COLOR_VALUE.get());
|
||||
seekbarColor = Color.parseColor(Settings.CUSTOM_SEEKBAR_COLOR_VALUE.get());
|
||||
Color.colorToHSV(seekbarColor, customSeekbarColorHSV);
|
||||
} catch (Exception ex) {
|
||||
Utils.showToastShort(str("revanced_custom_seekbar_color_value_invalid_invalid_toast"));
|
||||
Utils.showToastShort(str("revanced_extended_reset_to_default_toast"));
|
||||
Settings.ENABLE_CUSTOM_SEEKBAR_COLOR_VALUE.resetToDefault();
|
||||
Settings.CUSTOM_SEEKBAR_COLOR_VALUE.resetToDefault();
|
||||
loadCustomSeekbarColor();
|
||||
}
|
||||
}
|
||||
@ -165,6 +177,33 @@ public class SeekbarColorPatch {
|
||||
return colorValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static int[] getLinearGradient(int[] original) {
|
||||
if (Settings.HIDE_SEEKBAR_THUMBNAIL.get()) {
|
||||
return HIDDEN_SEEKBAR_GRADIENT_COLORS;
|
||||
}
|
||||
return CUSTOM_SEEKBAR_COLOR_ENABLED
|
||||
? customSeekbarColorInt
|
||||
: original;
|
||||
}
|
||||
|
||||
private static String colorArrayToHex(int[] colors) {
|
||||
final int length = colors.length;
|
||||
StringBuilder builder = new StringBuilder(length * 10);
|
||||
builder.append("[");
|
||||
int i = 0;
|
||||
for (int color : colors) {
|
||||
builder.append(String.format("#%X", color));
|
||||
if (++i < length) {
|
||||
builder.append(", ");
|
||||
}
|
||||
}
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@ -174,15 +213,15 @@ public class SeekbarColorPatch {
|
||||
if (CUSTOM_SEEKBAR_COLOR_ENABLED || hideSeekbar) {
|
||||
// Most litho usage of linear gradients is hooked here,
|
||||
// so must only change if the values are those for the seekbar.
|
||||
if (Arrays.equals(ORIGINAL_SEEKBAR_GRADIENT_COLORS, colors)
|
||||
&& Arrays.equals(ORIGINAL_SEEKBAR_GRADIENT_POSITIONS, positions)) {
|
||||
if ((Arrays.equals(FEED_ORIGINAL_SEEKBAR_GRADIENT_COLORS, colors)
|
||||
&& Arrays.equals(FEED_ORIGINAL_SEEKBAR_GRADIENT_POSITIONS, positions))) {
|
||||
Arrays.fill(colors, hideSeekbar
|
||||
? 0x00000000
|
||||
: seekbarColor);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "Ignoring gradient colors: " + Arrays.toString(colors)
|
||||
Logger.printDebug(() -> "Ignoring gradient colors: " + colorArrayToHex(colors)
|
||||
+ " positions: " + Arrays.toString(positions));
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,14 @@ import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.ResourceUtils;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||
import app.revanced.extension.youtube.utils.VideoUtils;
|
||||
import kotlin.Unit;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ShortsPatch {
|
||||
@ -34,7 +37,7 @@ public class ShortsPatch {
|
||||
if (HIDE_SHORTS_NAVIGATION_BAR) {
|
||||
ShortsPlayerState.getOnChange().addObserver((ShortsPlayerState state) -> {
|
||||
setNavigationBarLayoutParams(state);
|
||||
return null;
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
final int bottomMargin = validateValue(
|
||||
@ -60,6 +63,10 @@ public class ShortsPatch {
|
||||
return Settings.DISABLE_RESUMING_SHORTS_PLAYER.get();
|
||||
}
|
||||
|
||||
public static boolean disableResumingStartupShortsPlayer(boolean original) {
|
||||
return !Settings.DISABLE_RESUMING_SHORTS_PLAYER.get() && original;
|
||||
}
|
||||
|
||||
public static boolean enableShortsTimeStamp(boolean original) {
|
||||
return ENABLE_TIME_STAMP || original;
|
||||
}
|
||||
@ -211,4 +218,38 @@ public class ShortsPatch {
|
||||
return !Settings.RESTORE_SHORTS_OLD_PLAYER_LAYOUT.get();
|
||||
}
|
||||
|
||||
public static boolean openShortInRegularPlayer(String videoId) {
|
||||
try {
|
||||
if (!Settings.OPEN_SHORTS_IN_REGULAR_PLAYER.get()) {
|
||||
return false; // Default unpatched behavior.
|
||||
}
|
||||
|
||||
if (videoId.isEmpty()) {
|
||||
// Shorts was opened using launcher app shortcut.
|
||||
//
|
||||
// This check will not detect if the Shorts app shortcut is used
|
||||
// while the app is running in the background (instead the regular player is opened).
|
||||
// To detect that the hooked method map parameter can be checked
|
||||
// if integer key 'com.google.android.apps.youtube.app.endpoint.flags'
|
||||
// has bitmask 16 set.
|
||||
//
|
||||
// This use case seems unlikely if the user has the Shorts
|
||||
// set to open in the regular player, so it's ignored as
|
||||
// checking the map makes the patch more complicated.
|
||||
Logger.printDebug(() -> "Ignoring Short with no videoId");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NavigationButton.getSelectedNavigationButton() == NavigationButton.SHORTS) {
|
||||
return false; // Always use Shorts player for the Shorts nav button.
|
||||
}
|
||||
|
||||
VideoUtils.openVideo(videoId, true);
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "openShortInRegularPlayer failure", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
package app.revanced.extension.youtube.patches.spans;
|
||||
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
|
||||
import app.revanced.extension.shared.patches.spans.Filter;
|
||||
import app.revanced.extension.shared.patches.spans.SpanType;
|
||||
import app.revanced.extension.shared.patches.spans.StringFilterGroup;
|
||||
import app.revanced.extension.shared.utils.ResourceUtils;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.utils.ThemeUtils;
|
||||
|
||||
@SuppressWarnings({"unused", "FieldCanBeLocal"})
|
||||
public final class SnackBarFilter extends Filter {
|
||||
private static final boolean HIDE_SNACK_BAR =
|
||||
Settings.HIDE_SNACK_BAR.get() || Settings.HIDE_SERVER_SIDE_SNACK_BAR.get();
|
||||
private static final boolean CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND =
|
||||
!HIDE_SNACK_BAR && Settings.CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND.get();
|
||||
private static final boolean INVERT_SNACK_BAR_THEME =
|
||||
!HIDE_SNACK_BAR && Settings.INVERT_SNACK_BAR_THEME.get();
|
||||
|
||||
private final ForegroundColorSpan foregroundColorSpanBlack =
|
||||
new ForegroundColorSpan(ResourceUtils.getColor("yt_black1"));
|
||||
private final ForegroundColorSpan foregroundColorSpanWhite =
|
||||
new ForegroundColorSpan(ResourceUtils.getColor("yt_white1"));
|
||||
|
||||
public SnackBarFilter() {
|
||||
addCallbacks(
|
||||
new StringFilterGroup(
|
||||
Settings.CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND,
|
||||
"snackbar.eml|"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private ForegroundColorSpan getForegroundColorSpan() {
|
||||
if (INVERT_SNACK_BAR_THEME) {
|
||||
return ThemeUtils.isDarkTheme()
|
||||
? foregroundColorSpanWhite
|
||||
: foregroundColorSpanBlack;
|
||||
}
|
||||
return ThemeUtils.isDarkTheme()
|
||||
? foregroundColorSpanBlack
|
||||
: foregroundColorSpanWhite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean skip(String conversionContext, SpannableString spannableString, Object span,
|
||||
int start, int end, int flags, boolean isWord, SpanType spanType, StringFilterGroup matchedGroup) {
|
||||
if (CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND && spanType == SpanType.FOREGROUND_COLOR) {
|
||||
spannableString.setSpan(
|
||||
getForegroundColorSpan(),
|
||||
start,
|
||||
end,
|
||||
flags
|
||||
);
|
||||
return super.skip(conversionContext, spannableString, span, start, end, flags, isWord, spanType, matchedGroup);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,34 +1,36 @@
|
||||
package app.revanced.extension.youtube.patches.utils;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import app.revanced.extension.shared.utils.ResourceUtils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class DrawableColorPatch {
|
||||
private static final int[] WHITE_VALUES = {
|
||||
-1, // comments chip background
|
||||
-394759, // music related results panel background
|
||||
-83886081 // video chapters list background
|
||||
private static final int[] DARK_COLORS = {
|
||||
0xFF282828, // drawer content view background
|
||||
0xFF212121, // comments chip background
|
||||
0xFF181818, // music related results panel background
|
||||
0xFF0F0F0F, // comments chip background (new layout)
|
||||
0xFA212121, // video chapters list background
|
||||
};
|
||||
|
||||
private static final int[] DARK_VALUES = {
|
||||
-14145496, // drawer content view background
|
||||
-14606047, // comments chip background
|
||||
-15198184, // music related results panel background
|
||||
-15790321, // comments chip background (new layout)
|
||||
-98492127 // video chapters list background
|
||||
private static final int[] LIGHT_COLORS = {
|
||||
-1, // comments chip background
|
||||
0xFFF9F9F9, // music related results panel background
|
||||
0xFAFFFFFF, // video chapters list background
|
||||
};
|
||||
|
||||
// background colors
|
||||
private static int whiteColor = 0;
|
||||
private static int blackColor = 0;
|
||||
|
||||
public static int getLithoColor(int originalValue) {
|
||||
if (anyEquals(originalValue, DARK_VALUES)) {
|
||||
public static int getLithoColor(int colorValue) {
|
||||
if (ArrayUtils.contains(DARK_COLORS, colorValue)) {
|
||||
return getBlackColor();
|
||||
} else if (anyEquals(originalValue, WHITE_VALUES)) {
|
||||
} else if (ArrayUtils.contains(LIGHT_COLORS, colorValue)) {
|
||||
return getWhiteColor();
|
||||
}
|
||||
return originalValue;
|
||||
return colorValue;
|
||||
}
|
||||
|
||||
private static int getBlackColor() {
|
||||
@ -40,11 +42,6 @@ public class DrawableColorPatch {
|
||||
if (whiteColor == 0) whiteColor = ResourceUtils.getColor("yt_white1");
|
||||
return whiteColor;
|
||||
}
|
||||
|
||||
private static boolean anyEquals(int value, int... of) {
|
||||
for (int v : of) if (value == v) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -27,6 +27,10 @@ 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;
|
||||
|
@ -24,7 +24,9 @@ public class VideoQualityPatch {
|
||||
* Injection point.
|
||||
*/
|
||||
public static void newVideoStarted() {
|
||||
setVideoQuality(0);
|
||||
VideoInformation.qualityNeedsUpdating = true;
|
||||
VideoInformation.videoQualities = null;
|
||||
setVideoQuality(250);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,7 +129,11 @@ class MusicRequest private constructor(
|
||||
clientType
|
||||
)
|
||||
val requestBody =
|
||||
PlayerRoutes.createApplicationRequestBody(clientType = clientType, videoId = videoId, playlistId = "RD$videoId")
|
||||
PlayerRoutes.createApplicationRequestBody(
|
||||
clientType = clientType,
|
||||
videoId = videoId,
|
||||
playlistId = "RD$videoId"
|
||||
)
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
@ -161,7 +165,7 @@ class MusicRequest private constructor(
|
||||
val startTime = System.currentTimeMillis()
|
||||
val clientType = WebClient.ClientType.MWEB
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching playability request for: $videoId, using client: $clientTypeName" }
|
||||
Logger.printDebug { "Fetching microformat request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
@ -226,9 +230,14 @@ class MusicRequest private constructor(
|
||||
.getJSONObject("watchEndpoint")
|
||||
|
||||
val playerParams: String? = watchEndpointJsonObject?.getString("playerParams")
|
||||
return playerParams != null && VideoInformation.isMixPlaylistsOpenedByUser(playerParams)
|
||||
return playerParams != null && VideoInformation.isMixPlaylistsOpenedByUser(
|
||||
playerParams
|
||||
)
|
||||
} catch (e: JSONException) {
|
||||
Logger.printException ({ "Fetch failed while processing Application response data for response: $playlistJson" }, e)
|
||||
Logger.printException(
|
||||
{ "Fetch failed while processing Application response data for response: $playlistJson" },
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
@ -242,7 +251,10 @@ class MusicRequest private constructor(
|
||||
.getString("category")
|
||||
.equals("Music")
|
||||
} catch (e: JSONException) {
|
||||
Logger.printException ({ "Fetch failed while processing Web response data for response: $microFormatJson" }, e)
|
||||
Logger.printException(
|
||||
{ "Fetch failed while processing Web response data for response: $microFormatJson" },
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -87,13 +87,12 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_MOVIE_SHELF = new BooleanSetting("revanced_hide_movie_shelf", FALSE);
|
||||
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", FALSE);
|
||||
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
||||
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_FEED_SEARCH_BAR = new BooleanSetting("revanced_hide_feed_search_bar", FALSE);
|
||||
public static final BooleanSetting HIDE_FEED_SURVEY = new BooleanSetting("revanced_hide_feed_survey", TRUE);
|
||||
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_SUBSCRIPTIONS_CAROUSEL = new BooleanSetting("revanced_hide_subscriptions_carousel", FALSE, true);
|
||||
public static final BooleanSetting HIDE_FEED_SURVEY = new BooleanSetting("revanced_hide_feed_survey", TRUE);
|
||||
public static final BooleanSetting HIDE_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", TRUE);
|
||||
|
||||
|
||||
// PreferenceScreen: Feed - Category bar
|
||||
public static final BooleanSetting HIDE_CATEGORY_BAR_IN_FEED = new BooleanSetting("revanced_hide_category_bar_in_feed", FALSE, true);
|
||||
public static final BooleanSetting HIDE_CATEGORY_BAR_IN_SEARCH = new BooleanSetting("revanced_hide_category_bar_in_search", FALSE, true);
|
||||
@ -152,12 +151,12 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting ENABLE_GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_enable_gradient_loading_screen", FALSE, true);
|
||||
public static final BooleanSetting HIDE_FLOATING_MICROPHONE = new BooleanSetting("revanced_hide_floating_microphone", TRUE, true);
|
||||
public static final BooleanSetting HIDE_GRAY_SEPARATOR = new BooleanSetting("revanced_hide_gray_separator", TRUE);
|
||||
public static final BooleanSetting HIDE_SNACK_BAR = new BooleanSetting("revanced_hide_snack_bar", FALSE);
|
||||
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE);
|
||||
|
||||
public static final EnumSetting<FormFactor> CHANGE_LAYOUT = new EnumSetting<>("revanced_change_layout", FormFactor.ORIGINAL, true);
|
||||
public static final BooleanSetting CHANGE_LIVE_RING_CLICK_ACTION = new BooleanSetting("revanced_change_live_ring_click_action", FALSE, true);
|
||||
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", false, true, "revanced_spoof_app_version_user_dialog_message");
|
||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "18.17.43", true, parent(SPOOF_APP_VERSION));
|
||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", PatchStatus.SpoofAppVersionDefaultString(), true, parent(SPOOF_APP_VERSION));
|
||||
|
||||
// PreferenceScreen: General - Account menu
|
||||
public static final BooleanSetting HIDE_ACCOUNT_MENU = new BooleanSetting("revanced_hide_account_menu", FALSE);
|
||||
@ -181,7 +180,7 @@ public class Settings extends BaseSettings {
|
||||
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, MINIPLAYER_ANY_MODERN);
|
||||
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, MINIPLAYER_TYPE.availability(MODERN_1));
|
||||
|
||||
// PreferenceScreen: General - Navigation bar
|
||||
// PreferenceScreen: General - Navigation Bar
|
||||
public static final BooleanSetting ENABLE_NARROW_NAVIGATION_BUTTONS = new BooleanSetting("revanced_enable_narrow_navigation_buttons", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_CREATE_BUTTON = new BooleanSetting("revanced_hide_navigation_create_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_HOME_BUTTON = new BooleanSetting("revanced_hide_navigation_home_button", FALSE, true);
|
||||
@ -209,7 +208,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_SETTINGS_MENU_GENERAL = new BooleanSetting("revanced_hide_settings_menu_general", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SETTINGS_MENU_ACCOUNT = new BooleanSetting("revanced_hide_settings_menu_account", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SETTINGS_MENU_DATA_SAVING = new BooleanSetting("revanced_hide_settings_menu_data_saving", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SETTINGS_MENU_AUTOPLAY = new BooleanSetting("revanced_hide_settings_menu_auto_play", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SETTINGS_MENU_AUTOPLAY_PLAYBACK = new BooleanSetting("revanced_hide_settings_menu_autoplay_playback", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SETTINGS_MENU_VIDEO_QUALITY_PREFERENCES = new BooleanSetting("revanced_hide_settings_menu_video_quality", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SETTINGS_MENU_OFFLINE = new BooleanSetting("revanced_hide_settings_menu_offline", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SETTINGS_MENU_WATCH_ON_TV = new BooleanSetting("revanced_hide_settings_menu_pair_with_tv", FALSE, true);
|
||||
@ -231,11 +230,17 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_SETTINGS_MENU_POST_PURCHASE = new BooleanSetting("revanced_hide_settings_menu_post_purchase", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SETTINGS_MENU_THIRD_PARTY = new BooleanSetting("revanced_hide_settings_menu_third_party", FALSE, true);
|
||||
|
||||
// PreferenceScreen: General - Snack bar
|
||||
public static final BooleanSetting HIDE_SNACK_BAR = new BooleanSetting("revanced_hide_snack_bar", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SERVER_SIDE_SNACK_BAR = new BooleanSetting("revanced_hide_server_side_snack_bar", FALSE, true);
|
||||
public static final BooleanSetting CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND = new BooleanSetting("revanced_change_server_side_snack_bar_background", FALSE, true, "revanced_change_server_side_snack_bar_background_user_dialog_message");
|
||||
public static final BooleanSetting INVERT_SNACK_BAR_THEME = new BooleanSetting("revanced_invert_snack_bar_theme", FALSE, true);
|
||||
|
||||
// PreferenceScreen: General - Toolbar
|
||||
public static final BooleanSetting CHANGE_YOUTUBE_HEADER = new BooleanSetting("revanced_change_youtube_header", TRUE, true);
|
||||
public static final BooleanSetting ENABLE_WIDE_SEARCH_BAR = new BooleanSetting("revanced_enable_wide_search_bar", FALSE, true);
|
||||
public static final BooleanSetting ENABLE_WIDE_SEARCH_BAR_WITH_HEADER = new BooleanSetting("revanced_enable_wide_search_bar_with_header", TRUE, true);
|
||||
public static final BooleanSetting ENABLE_WIDE_SEARCH_BAR_IN_YOU_TAB = new BooleanSetting("revanced_enable_wide_search_bar_in_you_tab", FALSE, true);
|
||||
public static final BooleanSetting ENABLE_WIDE_SEARCH_BAR_IN_YOU_TAB = new BooleanSetting("revanced_enable_wide_search_bar_in_you_tab", FALSE, true, "revanced_enable_wide_search_bar_in_you_tab_user_dialog_message");
|
||||
public static final BooleanSetting HIDE_TOOLBAR_CAST_BUTTON = new BooleanSetting("revanced_hide_toolbar_cast_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_TOOLBAR_CREATE_BUTTON = new BooleanSetting("revanced_hide_toolbar_create_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_TOOLBAR_NOTIFICATION_BUTTON = new BooleanSetting("revanced_hide_toolbar_notification_button", FALSE, true);
|
||||
@ -284,6 +289,23 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_SHOP_BUTTON = new BooleanSetting("revanced_hide_shop_button", FALSE);
|
||||
public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", FALSE);
|
||||
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_0 = new BooleanSetting("revanced_hide_action_button_index_0", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_1 = new BooleanSetting("revanced_hide_action_button_index_1", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_2 = new BooleanSetting("revanced_hide_action_button_index_2", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_3 = new BooleanSetting("revanced_hide_action_button_index_3", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_4 = new BooleanSetting("revanced_hide_action_button_index_4", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_5 = new BooleanSetting("revanced_hide_action_button_index_5", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_6 = new BooleanSetting("revanced_hide_action_button_index_6", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_7 = new BooleanSetting("revanced_hide_action_button_index_7", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_0 = new BooleanSetting("revanced_hide_action_button_index_live_0", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_1 = new BooleanSetting("revanced_hide_action_button_index_live_1", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_2 = new BooleanSetting("revanced_hide_action_button_index_live_2", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_3 = new BooleanSetting("revanced_hide_action_button_index_live_3", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_4 = new BooleanSetting("revanced_hide_action_button_index_live_4", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_5 = new BooleanSetting("revanced_hide_action_button_index_live_5", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_6 = new BooleanSetting("revanced_hide_action_button_index_live_6", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_7 = new BooleanSetting("revanced_hide_action_button_index_live_7", FALSE);
|
||||
|
||||
// PreferenceScreen: Player - Ambient mode
|
||||
public static final BooleanSetting BYPASS_AMBIENT_MODE_RESTRICTIONS = new BooleanSetting("revanced_bypass_ambient_mode_restrictions", FALSE);
|
||||
public static final BooleanSetting DISABLE_AMBIENT_MODE = new BooleanSetting("revanced_disable_ambient_mode", FALSE, true);
|
||||
@ -394,7 +416,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting REPLACE_TIME_STAMP_ACTION = new BooleanSetting("revanced_replace_time_stamp_action", TRUE, true, parent(APPEND_TIME_STAMP_INFORMATION));
|
||||
public static final BooleanSetting DISABLE_SEEKBAR_CHAPTERS = new BooleanSetting("revanced_disable_seekbar_chapters", FALSE, true);
|
||||
public static final BooleanSetting ENABLE_CUSTOM_SEEKBAR_COLOR = new BooleanSetting("revanced_enable_custom_seekbar_color", FALSE, true);
|
||||
public static final StringSetting ENABLE_CUSTOM_SEEKBAR_COLOR_VALUE = new StringSetting("revanced_custom_seekbar_color_value", "#FF0033", true, parent(ENABLE_CUSTOM_SEEKBAR_COLOR));
|
||||
public static final StringSetting CUSTOM_SEEKBAR_COLOR_VALUE = new StringSetting("revanced_custom_seekbar_color_value", "#FF0033", true, parent(ENABLE_CUSTOM_SEEKBAR_COLOR));
|
||||
public static final BooleanSetting ENABLE_SEEKBAR_TAPPING = new BooleanSetting("revanced_enable_seekbar_tapping", TRUE);
|
||||
public static final BooleanSetting HIDE_SEEKBAR_CHAPTER_LABEL = new BooleanSetting("revanced_hide_seekbar_chapter_label", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE, true);
|
||||
@ -432,6 +454,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_SHORTS_SHELF_HISTORY = new BooleanSetting("revanced_hide_shorts_shelf_history", TRUE);
|
||||
public static final EnumSetting<ShortsLoopBehavior> CHANGE_SHORTS_BACKGROUND_REPEAT_STATE = new EnumSetting<>("revanced_change_shorts_background_repeat_state", ShortsLoopBehavior.UNKNOWN);
|
||||
public static final EnumSetting<ShortsLoopBehavior> CHANGE_SHORTS_REPEAT_STATE = new EnumSetting<>("revanced_change_shorts_repeat_state", ShortsLoopBehavior.UNKNOWN);
|
||||
public static final BooleanSetting OPEN_SHORTS_IN_REGULAR_PLAYER = new BooleanSetting("revanced_open_shorts_in_regular_player", FALSE);
|
||||
|
||||
// PreferenceScreen: Shorts - Shorts player components
|
||||
public static final BooleanSetting HIDE_SHORTS_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE);
|
||||
@ -550,8 +573,8 @@ public class Settings extends BaseSettings {
|
||||
|
||||
|
||||
// PreferenceScreen: Miscellaneous
|
||||
public static final BooleanSetting ENABLE_EXTERNAL_BROWSER = new BooleanSetting("revanced_enable_external_browser", TRUE, true);
|
||||
public static final BooleanSetting ENABLE_OPEN_LINKS_DIRECTLY = new BooleanSetting("revanced_enable_open_links_directly", TRUE);
|
||||
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
|
||||
public static final BooleanSetting OPEN_LINKS_EXTERNALLY = new BooleanSetting("revanced_open_links_externally", TRUE, true);
|
||||
|
||||
// Experimental Flags
|
||||
public static final BooleanSetting CHANGE_SHARE_SHEET = new BooleanSetting("revanced_change_share_sheet", FALSE, true);
|
||||
|
@ -69,9 +69,9 @@ public final class VideoInformation {
|
||||
* The available qualities of the current video in human readable form: [1080, 720, 480]
|
||||
*/
|
||||
@Nullable
|
||||
private static List<Integer> videoQualities;
|
||||
public static volatile List<Integer> videoQualities;
|
||||
|
||||
private static boolean qualityNeedsUpdating;
|
||||
public static volatile boolean qualityNeedsUpdating;
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
|
@ -23,6 +23,7 @@ import app.revanced.extension.youtube.patches.player.PlayerPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment;
|
||||
import kotlin.Unit;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SponsorBlockViewController {
|
||||
@ -44,7 +45,7 @@ public class SponsorBlockViewController {
|
||||
static {
|
||||
PlayerType.getOnChange().addObserver((PlayerType type) -> {
|
||||
playerTypeChanged(type);
|
||||
return null;
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
|
||||
defaultBottomMargin = getDimension("brand_interaction_default_bottom_margin");
|
||||
|
@ -59,7 +59,7 @@ class ScreenBrightnessController(
|
||||
*/
|
||||
private var rawScreenBrightness: Float
|
||||
get() = host.window.attributes.screenBrightness
|
||||
private set(value) {
|
||||
set(value) {
|
||||
val attr = host.window.attributes
|
||||
attr.screenBrightness = value
|
||||
host.window.attributes = attr
|
||||
|
@ -29,12 +29,17 @@ import app.revanced.extension.youtube.shared.VideoInformation;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class VideoUtils extends IntentUtils {
|
||||
private static final String CHANNEL_URL = "https://www.youtube.com/channel/";
|
||||
private static final String PLAYLIST_URL = "https://www.youtube.com/playlist?list=";
|
||||
private static final String VIDEO_URL = "https://youtu.be/";
|
||||
private static final String VIDEO_SCHEME_INTENT_FORMAT = "vnd.youtube://%s?start=%d";
|
||||
private static final String VIDEO_SCHEME_LINK_FORMAT = "https://youtu.be/%s?t=%d";
|
||||
private static final AtomicBoolean isExternalDownloaderLaunched = new AtomicBoolean(false);
|
||||
|
||||
private static String getChannelUrl(String channelId) {
|
||||
return CHANNEL_URL + channelId;
|
||||
}
|
||||
|
||||
private static String getPlaylistUrl(String playlistId) {
|
||||
return PLAYLIST_URL + playlistId;
|
||||
}
|
||||
@ -119,6 +124,10 @@ public class VideoUtils extends IntentUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void openChannel(@NonNull String channelId) {
|
||||
launchView(getChannelUrl(channelId), getContext().getPackageName());
|
||||
}
|
||||
|
||||
public static void openVideo() {
|
||||
openVideo(VideoInformation.getVideoId());
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ public class VideoQualitySettingsActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(Utils.getLocalizedContextAndSetResources(base));
|
||||
super.attachBaseContext(Utils.getLocalizedContext(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -4,5 +4,5 @@ org.gradle.parallel = true
|
||||
android.useAndroidX = true
|
||||
kotlin.code.style = official
|
||||
kotlin.jvm.target.validation.mode = IGNORE
|
||||
version = 5.2.2
|
||||
version = 5.3.1
|
||||
|
||||
|
401
patches.json
401
patches.json
@ -57,7 +57,28 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Bypass URL redirects",
|
||||
"description": "Adds an option to bypass URL redirects and open the original URL directly.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for YouTube"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
"18.29.38",
|
||||
"18.33.40",
|
||||
"18.38.44",
|
||||
"18.48.39",
|
||||
"19.05.36",
|
||||
"19.16.39",
|
||||
"19.44.39"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -77,7 +98,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -115,7 +137,51 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Change layout",
|
||||
"description": "Adds an option to change the dp in order to use a tablet or phone layout.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for YouTube"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
"18.29.38",
|
||||
"18.33.40",
|
||||
"18.38.44",
|
||||
"18.48.39",
|
||||
"19.05.36",
|
||||
"19.16.39",
|
||||
"19.44.39"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Change live ring click action",
|
||||
"description": "Adds an option to open the channel instead of the live stream when clicking on the live ring.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for YouTube",
|
||||
"ResourcePatch",
|
||||
"BytecodePatch",
|
||||
"BytecodePatch"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
"18.29.38",
|
||||
"18.33.40",
|
||||
"18.38.44",
|
||||
"18.48.39",
|
||||
"19.05.36",
|
||||
"19.16.39",
|
||||
"19.44.39"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -180,7 +246,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -221,7 +288,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -376,7 +444,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -418,7 +487,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Custom branding name for Reddit",
|
||||
"description": "Renames the Reddit app to the name specified in patch options.",
|
||||
"description": "Changes the Reddit app name to the name specified in patch options.",
|
||||
"use": false,
|
||||
"dependencies": [],
|
||||
"compatiblePackages": {
|
||||
@ -441,7 +510,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Custom branding name for YouTube",
|
||||
"description": "Renames the YouTube app to the name specified in patch options.",
|
||||
"description": "Changes the YouTube app name to the name specified in patch options.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for YouTube"
|
||||
@ -476,7 +545,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Custom branding name for YouTube Music",
|
||||
"description": "Renames the YouTube Music app to the name specified in patch options.",
|
||||
"description": "Changes the YouTube Music app name to the name specified in patch options.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for YouTube Music"
|
||||
@ -488,7 +557,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -599,7 +669,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -630,7 +701,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -701,7 +773,8 @@
|
||||
"com.google.android.apps.youtube.music": [
|
||||
"7.06.54",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -721,7 +794,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -751,7 +825,8 @@
|
||||
"description": "Adds an option to disable redirection to the next track when clicking the Dislike button.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for YouTube Music"
|
||||
"Settings for YouTube Music",
|
||||
"ResourcePatch"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.apps.youtube.music": [
|
||||
@ -760,7 +835,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -800,7 +876,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -862,7 +939,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -872,7 +950,8 @@
|
||||
"description": "Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for YouTube"
|
||||
"Settings for YouTube",
|
||||
"ResourcePatch"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
@ -922,7 +1001,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Enable OPUS codec",
|
||||
"description": "Adds an options to enable the OPUS audio codec if the player response includes it.",
|
||||
"description": "Adds an option to enable the OPUS audio codec if the player response includes it.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"BytecodePatch",
|
||||
@ -935,14 +1014,15 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Enable OPUS codec",
|
||||
"description": "Adds an options to enable the OPUS audio codec if the player response includes it.",
|
||||
"description": "Adds an option to enable the OPUS audio codec if the player response includes it.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"BytecodePatch",
|
||||
@ -975,7 +1055,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1000,27 +1081,6 @@
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Enable external browser",
|
||||
"description": "Adds an option to always open links in your browser instead of in the in-app-browser.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"BytecodePatch",
|
||||
"Settings for YouTube"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
"18.29.38",
|
||||
"18.33.40",
|
||||
"18.38.44",
|
||||
"18.48.39",
|
||||
"19.05.36",
|
||||
"19.16.39",
|
||||
"19.44.39"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Enable gradient loading screen",
|
||||
"description": "Adds an option to enable the gradient loading screen.",
|
||||
@ -1056,27 +1116,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Enable open links directly",
|
||||
"description": "Adds an option to skip over redirection URLs in external links.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for YouTube"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
"18.29.38",
|
||||
"18.33.40",
|
||||
"18.38.44",
|
||||
"18.48.39",
|
||||
"19.05.36",
|
||||
"19.16.39",
|
||||
"19.44.39"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1102,7 +1143,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1169,7 +1211,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -1332,7 +1375,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1355,7 +1399,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1366,6 +1411,7 @@
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for YouTube",
|
||||
"BytecodePatch",
|
||||
"BytecodePatch"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
@ -1379,7 +1425,17 @@
|
||||
"19.44.39"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
"options": [
|
||||
{
|
||||
"key": "hideActionButtonByIndex",
|
||||
"title": "Hide action buttons by index",
|
||||
"description": "Add an option to hide action buttons by index.\n\nThis setting is still experimental, so use it only for debugging purposes.",
|
||||
"required": true,
|
||||
"type": "kotlin.Boolean",
|
||||
"default": false,
|
||||
"values": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Hide ads",
|
||||
@ -1399,7 +1455,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1476,7 +1533,9 @@
|
||||
"BytecodePatch",
|
||||
"ResourcePatch",
|
||||
"Settings for YouTube",
|
||||
"BytecodePatch"
|
||||
"BytecodePatch",
|
||||
"BytecodePatch",
|
||||
"ResourcePatch"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
@ -1530,7 +1589,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1587,7 +1647,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1652,7 +1713,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1769,26 +1831,6 @@
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Layout switch",
|
||||
"description": "Adds an option to spoof the dpi in order to use a tablet or phone layout.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for YouTube"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
"18.29.38",
|
||||
"18.33.40",
|
||||
"18.38.44",
|
||||
"18.48.39",
|
||||
"19.05.36",
|
||||
"19.16.39",
|
||||
"19.44.39"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "MaterialYou",
|
||||
"description": "Applies the MaterialYou theme for Android 12+ devices.",
|
||||
@ -1848,7 +1890,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1903,6 +1946,27 @@
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Open links externally",
|
||||
"description": "Adds an option to always open links in your browser instead of the in-app browser.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"BytecodePatch",
|
||||
"Settings for YouTube"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
"18.29.38",
|
||||
"18.33.40",
|
||||
"18.38.44",
|
||||
"18.48.39",
|
||||
"19.05.36",
|
||||
"19.16.39",
|
||||
"19.44.39"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Overlay buttons",
|
||||
"description": "Adds options to display useful overlay buttons in the video player.",
|
||||
@ -1913,8 +1977,7 @@
|
||||
"BytecodePatch",
|
||||
"ResourcePatch",
|
||||
"ResourcePatch",
|
||||
"Settings for YouTube",
|
||||
"ResourcePatch"
|
||||
"Settings for YouTube"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
@ -1944,20 +2007,20 @@
|
||||
{
|
||||
"key": "bottomMargin",
|
||||
"title": "Bottom margin",
|
||||
"description": "The bottom margin for the overlay buttons and timestamp. Supports from YouTube 18.29.38 to YouTube 19.16.39.",
|
||||
"description": "The bottom margin for the overlay buttons and timestamp.",
|
||||
"required": true,
|
||||
"type": "kotlin.String",
|
||||
"default": "2.5dip",
|
||||
"values": {
|
||||
"Default": "2.5dip",
|
||||
"None": "0.0dip",
|
||||
"Minimum": "0.1dip",
|
||||
"Wider": "5.0dip"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "widerButtonsSpace",
|
||||
"title": "Wider between-buttons space",
|
||||
"description": "Prevent adjacent button presses by increasing the horizontal spacing between buttons. Supports from YouTube 18.29.38 to YouTube 19.16.39.",
|
||||
"description": "Prevent adjacent button presses by increasing the horizontal spacing between buttons.",
|
||||
"required": true,
|
||||
"type": "kotlin.Boolean",
|
||||
"default": false,
|
||||
@ -1993,7 +2056,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2011,7 +2075,8 @@
|
||||
"ResourcePatch",
|
||||
"BytecodePatch",
|
||||
"BytecodePatch",
|
||||
"BytecodePatch"
|
||||
"BytecodePatch",
|
||||
"ResourcePatch"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
@ -2050,7 +2115,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2103,7 +2169,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2143,7 +2210,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2163,7 +2231,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2208,7 +2277,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2236,7 +2306,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Sanitize sharing links",
|
||||
"description": "Adds an option to remove tracking query parameters from URLs when sharing links.",
|
||||
"description": "Adds an option to sanitize sharing links by removing tracking query parameters.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"BytecodePatch",
|
||||
@ -2249,14 +2319,15 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Sanitize sharing links",
|
||||
"description": "Adds an option to remove tracking query parameters from URLs when sharing links.",
|
||||
"description": "Adds an option to sanitize sharing links by removing tracking query parameters.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for Reddit"
|
||||
@ -2268,7 +2339,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Sanitize sharing links",
|
||||
"description": "Adds an option to remove tracking query parameters from URLs when sharing links.",
|
||||
"description": "Adds an option to sanitize sharing links by removing tracking query parameters.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"BytecodePatch",
|
||||
@ -2416,7 +2487,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -2445,6 +2517,8 @@
|
||||
"BytecodePatch",
|
||||
"BytecodePatch",
|
||||
"BytecodePatch",
|
||||
"BytecodePatch",
|
||||
"BytecodePatch",
|
||||
"ResourcePatch",
|
||||
"BytecodePatch",
|
||||
"ResourcePatch",
|
||||
@ -2463,6 +2537,88 @@
|
||||
},
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "Snack bar components",
|
||||
"description": "Adds options to hide or change components related to the snack bar.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for YouTube",
|
||||
"BytecodePatch"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.google.android.youtube": [
|
||||
"18.29.38",
|
||||
"18.33.40",
|
||||
"18.38.44",
|
||||
"18.48.39",
|
||||
"19.05.36",
|
||||
"19.16.39",
|
||||
"19.44.39"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"key": "cornerRadius",
|
||||
"title": "Corner radius",
|
||||
"description": "Specify a corner radius for the snack bar.",
|
||||
"required": true,
|
||||
"type": "kotlin.String",
|
||||
"default": "8.0dip",
|
||||
"values": null
|
||||
},
|
||||
{
|
||||
"key": "darkThemeBackgroundColor",
|
||||
"title": "Dark theme background color",
|
||||
"description": "Specify a background color for the snack bar. You can specify hex color (#AARRGGBB) or color resource reference.",
|
||||
"required": true,
|
||||
"type": "kotlin.String",
|
||||
"default": "@color/yt_black3",
|
||||
"values": {
|
||||
"YouTube Dark": "@color/yt_black3",
|
||||
"Amoled Black": "@android:color/black",
|
||||
"Catppuccin (Mocha)": "#FF181825",
|
||||
"Dark Pink": "#FF290025",
|
||||
"Dark Blue": "#FF001029",
|
||||
"Dark Green": "#FF002905",
|
||||
"Dark Yellow": "#FF282900",
|
||||
"Dark Orange": "#FF291800",
|
||||
"Dark Red": "#FF290000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "lightThemeBackgroundColor",
|
||||
"title": "Light theme background color",
|
||||
"description": "Specify a background color for the snack bar. You can specify hex color (#AARRGGBB) or color resource reference.",
|
||||
"required": true,
|
||||
"type": "kotlin.String",
|
||||
"default": "@color/yt_white3",
|
||||
"values": {
|
||||
"YouTube Light": "@color/yt_white3",
|
||||
"White": "@android:color/white",
|
||||
"Catppuccin (Latte)": "#FFE6E9EF",
|
||||
"Light Pink": "#FFFCCFF3",
|
||||
"Light Blue": "#FFD1E0FF",
|
||||
"Light Green": "#FFCCFFCC",
|
||||
"Light Yellow": "#FFFDFFCC",
|
||||
"Light Orange": "#FFFFE6CC",
|
||||
"Light Red": "#FFFFD6D6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "strokeColor",
|
||||
"title": "Stroke color",
|
||||
"description": "Specify a stroke color for the snack bar. You can specify hex color.",
|
||||
"required": true,
|
||||
"type": "kotlin.String",
|
||||
"default": "",
|
||||
"values": {
|
||||
"None": "",
|
||||
"Blue": "?attr/ytThemedBlue",
|
||||
"Chip": "?attr/ytChipBackground"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SponsorBlock",
|
||||
"description": "Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections.",
|
||||
@ -2478,7 +2634,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2594,7 +2751,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2669,7 +2827,7 @@
|
||||
"key": "darkThemeBackgroundColor",
|
||||
"title": "Dark theme background color",
|
||||
"description": "Can be a hex color (#AARRGGBB) or a color resource reference.",
|
||||
"required": false,
|
||||
"required": true,
|
||||
"type": "kotlin.String",
|
||||
"default": "@android:color/black",
|
||||
"values": {
|
||||
@ -2688,7 +2846,7 @@
|
||||
"key": "lightThemeBackgroundColor",
|
||||
"title": "Light theme background color",
|
||||
"description": "Can be a hex color (#AARRGGBB) or a color resource reference.",
|
||||
"required": false,
|
||||
"required": true,
|
||||
"type": "kotlin.String",
|
||||
"default": "@android:color/white",
|
||||
"values": {
|
||||
@ -2790,7 +2948,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -2839,7 +2998,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2933,7 +3093,8 @@
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
"7.25.53"
|
||||
"7.25.53",
|
||||
"8.02.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
|
@ -48,6 +48,8 @@ public final class app/revanced/patches/music/general/oldstylelibraryshelf/OldSt
|
||||
|
||||
public final class app/revanced/patches/music/general/redirection/DislikeRedirectionPatchKt {
|
||||
public static final fun getDislikeRedirectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
public static final fun getOnClickReference ()Ljava/lang/String;
|
||||
public static final fun setOnClickReference (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/general/spoofappversion/SpoofAppVersionPatchKt {
|
||||
@ -58,6 +60,10 @@ public final class app/revanced/patches/music/general/startpage/ChangeStartPageP
|
||||
public static final fun getChangeStartPagePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/general/startpage/FingerprintsKt {
|
||||
public static final field DEFAULT_BROWSE_ID Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatchKt {
|
||||
public static final fun getCustomBrandingIconPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
@ -201,6 +207,8 @@ public final class app/revanced/patches/music/utils/playservice/VersionCheckPatc
|
||||
public static final fun is_7_20_or_greater ()Z
|
||||
public static final fun is_7_23_or_greater ()Z
|
||||
public static final fun is_7_25_or_greater ()Z
|
||||
public static final fun is_7_27_or_greater ()Z
|
||||
public static final fun is_7_29_or_greater ()Z
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/utils/resourceid/SharedResourceIdPatchKt {
|
||||
@ -339,8 +347,9 @@ public final class app/revanced/patches/reddit/layout/screenshotpopup/Screenshot
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/reddit/layout/subredditdialog/FingerprintsKt {
|
||||
public static final fun indexOfDismissScreenInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
|
||||
public static final fun indexOfHasBeenVisitedInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
|
||||
public static final fun indexOfSetBackgroundTintListInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
|
||||
public static final fun listOfIsLoggedInInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)Ljava/util/List;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatchKt {
|
||||
@ -386,6 +395,7 @@ 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
|
||||
public static final fun is_2024_41_or_greater ()Z
|
||||
public static final fun is_2025_01_or_greater ()Z
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/shared/FingerprintsKt {
|
||||
@ -615,6 +625,10 @@ public final class app/revanced/patches/youtube/general/layoutswitch/LayoutSwitc
|
||||
public static final fun getLayoutSwitchPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/general/livering/OpenChannelOfLiveAvatarPatchKt {
|
||||
public static final fun getOpenChannelOfLiveAvatarPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/general/loadingscreen/GradientLoadingScreenPatchKt {
|
||||
public static final fun getGradientLoadingScreenPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@ -631,6 +645,10 @@ public final class app/revanced/patches/youtube/general/navigation/NavigationBar
|
||||
public static final fun getNavigationBarComponentsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/general/snackbar/SnackBarComponentsPatchKt {
|
||||
public static final fun getSnackBarComponentsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/general/splashanimation/SplashAnimationPatchKt {
|
||||
public static final fun getSplashAnimationPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@ -712,12 +730,12 @@ public final class app/revanced/patches/youtube/misc/debugging/DebuggingPatchKt
|
||||
public static final fun getDebuggingPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/externalbrowser/OpenLinksExternallyPatchKt {
|
||||
public static final fun getOpenLinksExternallyPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
public final class app/revanced/patches/youtube/misc/openlinks/directly/OpenLinksDirectlyPatchKt {
|
||||
public static final fun getOpenLinksDirectlyPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/openlinksdirectly/OpenLinksDirectlyPatchKt {
|
||||
public static final fun getOpenLinksDirectlyPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
public final class app/revanced/patches/youtube/misc/openlinks/externally/OpenLinksExternallyPatchKt {
|
||||
public static final fun getOpenLinksExternallyPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/quic/QUICProtocolPatchKt {
|
||||
@ -817,6 +835,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/engagement/EngagementPanelHookPatchKt {
|
||||
public static final fun getEngagementPanelHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/utils/extension/SharedExtensionPatchKt {
|
||||
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@ -952,6 +974,8 @@ public final class app/revanced/patches/youtube/utils/playservice/VersionCheckPa
|
||||
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
|
||||
public static final fun is_19_49_or_greater ()Z
|
||||
public static final fun is_20_02_or_greater ()Z
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/utils/recyclerview/RecyclerViewTreeObserverPatchKt {
|
||||
@ -994,6 +1018,7 @@ public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceI
|
||||
public static final fun getDrawerResults ()J
|
||||
public static final fun getEasySeekEduContainer ()J
|
||||
public static final fun getEditSettingsAction ()J
|
||||
public static final fun getElementsImage ()J
|
||||
public static final fun getEmojiPickerIcon ()J
|
||||
public static final fun getEndScreenElementLayoutCircle ()J
|
||||
public static final fun getEndScreenElementLayoutIcon ()J
|
||||
@ -1010,6 +1035,7 @@ public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceI
|
||||
public static final fun getImageOnlyTab ()J
|
||||
public static final fun getInlineTimeBarColorizedBarPlayedColorDark ()J
|
||||
public static final fun getInlineTimeBarPlayedNotHighlightedColor ()J
|
||||
public static final fun getInsetElementsWrapper ()J
|
||||
public static final fun getInsetOverlayViewLayout ()J
|
||||
public static final fun getInterstitialsContainer ()J
|
||||
public static final fun getMenuItemView ()J
|
||||
@ -1072,6 +1098,7 @@ public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceI
|
||||
public static final fun getYtOutlineXWhite ()J
|
||||
public static final fun getYtPremiumWordMarkHeader ()J
|
||||
public static final fun getYtWordMarkHeader ()J
|
||||
public static final fun getYtYoutubeMagenta ()J
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/utils/returnyoutubedislike/ReturnYouTubeDislikePatchKt {
|
||||
@ -1121,6 +1148,14 @@ public final class app/revanced/patches/youtube/video/playback/VideoPlaybackPatc
|
||||
public static final fun getVideoPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/video/playbackstart/FingerprintsKt {
|
||||
public static final field PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/video/playbackstart/PlaybackStartDescriptorPatchKt {
|
||||
public static final fun getPlaybackStartDescriptorPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public abstract class app/revanced/patches/youtube/video/playerresponse/Hook {
|
||||
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun toString ()Ljava/lang/String;
|
||||
|
@ -9,6 +9,8 @@ import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKA
|
||||
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
|
||||
import app.revanced.patches.music.utils.patch.PatchList.DISABLE_DISLIKE_REDIRECTION
|
||||
import app.revanced.patches.music.utils.pendingIntentReceiverFingerprint
|
||||
import app.revanced.patches.music.utils.playservice.is_7_29_or_greater
|
||||
import app.revanced.patches.music.utils.playservice.versionCheckPatch
|
||||
import app.revanced.patches.music.utils.settings.CategoryType
|
||||
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
||||
import app.revanced.patches.music.utils.settings.addSwitchPreference
|
||||
@ -23,7 +25,8 @@ import com.android.tools.smali.dexlib2.Opcode
|
||||
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.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.Reference
|
||||
|
||||
var onClickReference = ""
|
||||
|
||||
@Suppress("unused")
|
||||
val dislikeRedirectionPatch = bytecodePatch(
|
||||
@ -32,11 +35,12 @@ val dislikeRedirectionPatch = bytecodePatch(
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(settingsPatch)
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
versionCheckPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
lateinit var onClickReference: Reference
|
||||
|
||||
pendingIntentReceiverFingerprint.methodOrThrow().apply {
|
||||
val startIndex = indexOfFirstStringInstructionOrThrow("YTM Dislike")
|
||||
val onClickRelayIndex =
|
||||
@ -49,27 +53,35 @@ val dislikeRedirectionPatch = bytecodePatch(
|
||||
val onClickMethod = getWalkerMethod(onClickMethodIndex)
|
||||
|
||||
onClickMethod.apply {
|
||||
val onClickIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference =
|
||||
((this as? ReferenceInstruction)?.reference as? MethodReference)
|
||||
val relativeIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()
|
||||
?.parameterTypes
|
||||
?.contains("Ljava/util/Map;") == true
|
||||
}
|
||||
val onClickIndex = indexOfFirstInstructionOrThrow(relativeIndex) {
|
||||
val reference = getReference<MethodReference>()
|
||||
|
||||
opcode == Opcode.INVOKE_INTERFACE &&
|
||||
reference?.returnType == "V" &&
|
||||
reference.parameterTypes.size == 1
|
||||
}
|
||||
onClickReference =
|
||||
getInstruction<ReferenceInstruction>(onClickIndex).reference
|
||||
getInstruction<ReferenceInstruction>(onClickIndex).reference.toString()
|
||||
|
||||
disableDislikeRedirection(onClickIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dislikeButtonOnClickListenerFingerprint.methodOrThrow().apply {
|
||||
val onClickIndex = indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.toString() == onClickReference.toString()
|
||||
}
|
||||
disableDislikeRedirection(onClickIndex)
|
||||
if (is_7_29_or_greater) {
|
||||
dislikeButtonOnClickListenerAlternativeFingerprint
|
||||
.methodOrThrow()
|
||||
.disableDislikeRedirection()
|
||||
} else {
|
||||
dislikeButtonOnClickListenerFingerprint
|
||||
.methodOrThrow()
|
||||
.disableDislikeRedirection()
|
||||
}
|
||||
|
||||
addSwitchPreference(
|
||||
@ -83,7 +95,15 @@ val dislikeRedirectionPatch = bytecodePatch(
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableMethod.disableDislikeRedirection(onClickIndex: Int) {
|
||||
private fun MutableMethod.disableDislikeRedirection(startIndex: Int = 0) {
|
||||
val onClickIndex =
|
||||
if (startIndex == 0) {
|
||||
indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.toString() == onClickReference
|
||||
}
|
||||
} else {
|
||||
startIndex
|
||||
}
|
||||
val targetIndex = indexOfFirstInstructionReversedOrThrow(onClickIndex, Opcode.IF_EQZ)
|
||||
val insertRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
|
@ -4,6 +4,8 @@ import app.revanced.util.containsLiteralInstruction
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
internal val dislikeButtonOnClickListenerFingerprint = legacyFingerprint(
|
||||
name = "dislikeButtonOnClickListenerFingerprint",
|
||||
@ -18,3 +20,31 @@ internal val dislikeButtonOnClickListenerFingerprint = legacyFingerprint(
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* YouTube Music 7.27.52 ~
|
||||
* TODO: Make this fingerprint more concise
|
||||
*/
|
||||
internal val dislikeButtonOnClickListenerAlternativeFingerprint = legacyFingerprint(
|
||||
name = "dislikeButtonOnClickListenerAlternativeFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("L", "Ljava/util/Map;"),
|
||||
customFingerprint = custom@{ method, classDef ->
|
||||
if (classDef.fields.count() != 7) {
|
||||
return@custom false
|
||||
}
|
||||
if (classDef.methods.count() != 5) {
|
||||
return@custom false
|
||||
}
|
||||
val implementation = method.implementation
|
||||
?: return@custom false
|
||||
val instructions = implementation.instructions
|
||||
val instructionCount = instructions.count()
|
||||
if (instructionCount < 50) {
|
||||
return@custom false
|
||||
}
|
||||
|
||||
((instructions.elementAt(0) as? ReferenceInstruction)?.reference as? FieldReference)?.name == "likeEndpoint"
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -2,7 +2,6 @@ package app.revanced.patches.music.general.startpage
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
|
||||
@ -11,8 +10,13 @@ import app.revanced.patches.music.utils.settings.CategoryType
|
||||
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
||||
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
|
||||
import app.revanced.patches.music.utils.settings.settingsPatch
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
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.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
@Suppress("unused")
|
||||
val changeStartPagePatch = bytecodePatch(
|
||||
@ -25,20 +29,20 @@ val changeStartPagePatch = bytecodePatch(
|
||||
|
||||
execute {
|
||||
|
||||
coldStartUpFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val targetIndex = it.patternMatch!!.endIndex
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
targetIndex + 1, """
|
||||
invoke-static {v$targetRegister}, $GENERAL_CLASS_DESCRIPTOR->changeStartPage(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$targetRegister
|
||||
return-object v$targetRegister
|
||||
"""
|
||||
)
|
||||
removeInstruction(targetIndex)
|
||||
coldStartUpFingerprint.methodOrThrow().apply {
|
||||
val defaultBrowseIdIndex = indexOfFirstStringInstructionOrThrow(DEFAULT_BROWSE_ID)
|
||||
val browseIdIndex = indexOfFirstInstructionReversedOrThrow(defaultBrowseIdIndex) {
|
||||
opcode == Opcode.IGET_OBJECT &&
|
||||
getReference<FieldReference>()?.type == "Ljava/lang/String;"
|
||||
}
|
||||
val browseIdRegister = getInstruction<TwoRegisterInstruction>(browseIdIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
browseIdIndex + 1, """
|
||||
invoke-static {v$browseIdRegister}, $GENERAL_CLASS_DESCRIPTOR->changeStartPage(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$browseIdRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
addPreferenceWithIntent(
|
||||
|
@ -5,16 +5,17 @@ import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
const val DEFAULT_BROWSE_ID = "FEmusic_home"
|
||||
|
||||
internal val coldStartUpFingerprint = legacyFingerprint(
|
||||
name = "coldStartUpFingerprint",
|
||||
returnType = "Ljava/lang/String;",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = emptyList(),
|
||||
opcodes = listOf(
|
||||
Opcode.GOTO,
|
||||
Opcode.CONST_STRING,
|
||||
Opcode.RETURN_OBJECT
|
||||
),
|
||||
strings = listOf("FEmusic_library_sideloaded_tracks", "FEmusic_home")
|
||||
strings = listOf("FEmusic_library_sideloaded_tracks", DEFAULT_BROWSE_ID)
|
||||
)
|
||||
|
||||
|
@ -4,8 +4,10 @@ import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
|
||||
import app.revanced.patches.music.utils.patch.PatchList.CUSTOM_HEADER_FOR_YOUTUBE_MUSIC
|
||||
import app.revanced.patches.music.utils.playservice.is_7_06_or_greater
|
||||
import app.revanced.patches.music.utils.playservice.is_7_27_or_greater
|
||||
import app.revanced.patches.music.utils.playservice.versionCheckPatch
|
||||
import app.revanced.patches.music.utils.resourceid.actionBarLogo
|
||||
import app.revanced.patches.music.utils.resourceid.actionBarLogoRingo2
|
||||
@ -15,14 +17,17 @@ import app.revanced.patches.music.utils.resourceid.ytmLogoRingo2
|
||||
import app.revanced.patches.music.utils.settings.ResourceUtils.getIconType
|
||||
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
||||
import app.revanced.patches.music.utils.settings.settingsPatch
|
||||
import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.Utils.printWarn
|
||||
import app.revanced.util.Utils.trimIndentMultiline
|
||||
import app.revanced.util.copyFile
|
||||
import app.revanced.util.copyResources
|
||||
import app.revanced.util.doRecursively
|
||||
import app.revanced.util.replaceLiteralInstructionCall
|
||||
import app.revanced.util.underBarOrThrow
|
||||
import app.revanced.util.valueOrThrow
|
||||
import org.w3c.dom.Element
|
||||
|
||||
private const val DEFAULT_HEADER_KEY = "Custom branding icon"
|
||||
private const val DEFAULT_HEADER_VALUE = "custom_branding_icon"
|
||||
@ -125,11 +130,26 @@ private val changeHeaderBytecodePatch = bytecodePatch(
|
||||
return@execute
|
||||
}
|
||||
|
||||
listOf(
|
||||
actionBarLogoRingo2 to actionBarLogo,
|
||||
ytmLogoRingo2 to ytmLogo,
|
||||
).forEach { (originalResource, replacementResource) ->
|
||||
replaceLiteralInstructionCall(originalResource, replacementResource)
|
||||
if (actionBarLogoRingo2 == -1L || ytmLogoRingo2 == -1L) {
|
||||
printWarn("Target resource not found!")
|
||||
return@execute
|
||||
}
|
||||
|
||||
if (is_7_27_or_greater) {
|
||||
replaceLiteralInstructionCall(
|
||||
actionBarLogoRingo2,
|
||||
"""
|
||||
invoke-static {v$REGISTER_TEMPLATE_REPLACEMENT}, $GENERAL_CLASS_DESCRIPTOR->getHeaderDrawableId(I)I
|
||||
move-result v$REGISTER_TEMPLATE_REPLACEMENT
|
||||
"""
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
actionBarLogoRingo2 to actionBarLogo,
|
||||
ytmLogoRingo2 to ytmLogo,
|
||||
).forEach { (originalResource, replacementResource) ->
|
||||
replaceLiteralInstructionCall(originalResource, replacementResource)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -186,6 +206,21 @@ val changeHeaderPatch = resourcePatch(
|
||||
printWarn(warnings)
|
||||
}
|
||||
|
||||
if (is_7_27_or_greater) {
|
||||
document("res/layout/signin_fragment.xml").use { document ->
|
||||
document.doRecursively node@{ node ->
|
||||
if (node !is Element) return@node
|
||||
|
||||
if (node.attributes.getNamedItem("android:id")?.nodeValue == "@id/logo") {
|
||||
node.getAttributeNode("android:src")
|
||||
?.let { attribute ->
|
||||
attribute.textContent = "@drawable/ytm_logo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePatchStatus(CUSTOM_HEADER_FOR_YOUTUBE_MUSIC)
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,9 @@ internal val audioVideoSwitchToggleConstructorFingerprint = legacyFingerprint(
|
||||
internal fun indexOfAudioVideoSwitchSetOnClickListenerInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.toString() == "Lcom/google/android/apps/youtube/music/player/AudioVideoSwitcherToggleView;->setOnClickListener(Landroid/view/View${'$'}OnClickListener;)V"
|
||||
getReference<MethodReference>()
|
||||
?.toString()
|
||||
?.endsWith("/AudioVideoSwitcherToggleView;->setOnClickListener(Landroid/view/View${'$'}OnClickListener;)V") == true
|
||||
}
|
||||
|
||||
internal val snackBarParentFingerprint = legacyFingerprint(
|
||||
|
@ -40,6 +40,7 @@ val cairoSplashAnimationPatch = bytecodePatch(
|
||||
"7.06.54",
|
||||
"7.16.53",
|
||||
"7.25.53",
|
||||
"8.02.53",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -14,6 +14,7 @@ import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
|
||||
import app.revanced.patches.music.utils.resourceid.text1
|
||||
import app.revanced.patches.music.utils.settings.CategoryType
|
||||
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
||||
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
|
||||
import app.revanced.patches.music.utils.settings.addSwitchPreference
|
||||
import app.revanced.patches.music.utils.settings.settingsPatch
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
@ -62,7 +63,7 @@ val navigationBarComponentsPatch = bytecodePatch(
|
||||
|
||||
execute {
|
||||
/**
|
||||
* Enable black navigation bar
|
||||
* Enable custom navigation bar color
|
||||
*/
|
||||
tabLayoutFingerprint.methodOrThrow().apply {
|
||||
val constIndex = indexOfFirstLiteralInstructionOrThrow(colorGrey)
|
||||
@ -74,7 +75,7 @@ val navigationBarComponentsPatch = bytecodePatch(
|
||||
|
||||
addInstructions(
|
||||
insertIndex, """
|
||||
invoke-static {}, $NAVIGATION_CLASS_DESCRIPTOR->enableBlackNavigationBar()I
|
||||
invoke-static {}, $NAVIGATION_CLASS_DESCRIPTOR->enableCustomNavigationBarColor()I
|
||||
move-result v$insertRegister
|
||||
"""
|
||||
)
|
||||
@ -129,8 +130,13 @@ val navigationBarComponentsPatch = bytecodePatch(
|
||||
|
||||
addSwitchPreference(
|
||||
CategoryType.NAVIGATION,
|
||||
"revanced_enable_black_navigation_bar",
|
||||
"true"
|
||||
"revanced_enable_custom_navigation_bar_color",
|
||||
"false"
|
||||
)
|
||||
addPreferenceWithIntent(
|
||||
CategoryType.NAVIGATION,
|
||||
"revanced_custom_navigation_bar_color_value",
|
||||
"revanced_enable_custom_navigation_bar_color"
|
||||
)
|
||||
addSwitchPreference(
|
||||
CategoryType.NAVIGATION,
|
||||
|
@ -23,7 +23,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
const val AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY =
|
||||
"Lcom/google/android/apps/youtube/music/player/AudioVideoSwitcherToggleView;->setVisibility(I)V"
|
||||
"/AudioVideoSwitcherToggleView;->setVisibility(I)V"
|
||||
|
||||
internal val audioVideoSwitchToggleFingerprint = legacyFingerprint(
|
||||
name = "audioVideoSwitchToggleFingerprint",
|
||||
@ -33,7 +33,9 @@ internal val audioVideoSwitchToggleFingerprint = legacyFingerprint(
|
||||
customFingerprint = { method, _ ->
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.toString() == AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY
|
||||
getReference<MethodReference>()
|
||||
?.toString()
|
||||
?.endsWith(AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY) == true
|
||||
} >= 0
|
||||
}
|
||||
)
|
||||
|
@ -799,7 +799,7 @@ val playerComponentsPatch = bytecodePatch(
|
||||
val reference = (instruction as? ReferenceInstruction)?.reference
|
||||
instruction.opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
reference is MethodReference &&
|
||||
reference.toString() == AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY
|
||||
reference.toString().endsWith(AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY)
|
||||
}
|
||||
.map { (index, _) -> index }
|
||||
.reversed()
|
||||
@ -995,7 +995,7 @@ val playerComponentsPatch = bytecodePatch(
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_INTERFACE &&
|
||||
reference?.returnType == "Z" &&
|
||||
reference.parameterTypes.size == 0
|
||||
reference.parameterTypes.isEmpty()
|
||||
} + 1
|
||||
val targetRegister =
|
||||
getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
@ -14,7 +14,8 @@ internal object Constants {
|
||||
"6.42.55", // This is the latest version that supports Android 7.0
|
||||
"6.51.53", // This is the latest version of YouTube Music 6.xx.xx
|
||||
"7.16.53", // This is the latest version that supports the 'Spoof app version' patch.
|
||||
"7.25.53", // This is the latest version supported by the RVX patch.
|
||||
"7.25.53", // This is the last supported version for 2024.
|
||||
"8.02.53", // This is the latest version supported by the RVX patch.
|
||||
)
|
||||
)
|
||||
}
|
@ -31,7 +31,7 @@ internal enum class PatchList(
|
||||
),
|
||||
CUSTOM_BRANDING_NAME_FOR_YOUTUBE_MUSIC(
|
||||
"Custom branding name for YouTube Music",
|
||||
"Renames the YouTube Music app to the name specified in patch options."
|
||||
"Changes the YouTube Music app name to the name specified in patch options."
|
||||
),
|
||||
CUSTOM_HEADER_FOR_YOUTUBE_MUSIC(
|
||||
"Custom header for YouTube Music",
|
||||
@ -63,7 +63,7 @@ internal enum class PatchList(
|
||||
),
|
||||
ENABLE_OPUS_CODEC(
|
||||
"Enable OPUS codec",
|
||||
"Adds an options to enable the OPUS audio codec if the player response includes it."
|
||||
"Adds an option to enable the OPUS audio codec if the player response includes it."
|
||||
),
|
||||
ENABLE_DEBUG_LOGGING(
|
||||
"Enable debug logging",
|
||||
@ -135,7 +135,7 @@ internal enum class PatchList(
|
||||
),
|
||||
SANITIZE_SHARING_LINKS(
|
||||
"Sanitize sharing links",
|
||||
"Adds an option to remove tracking query parameters from URLs when sharing links."
|
||||
"Adds an option to sanitize sharing links by removing tracking query parameters."
|
||||
),
|
||||
SETTINGS_FOR_YOUTUBE_MUSIC(
|
||||
"Settings for YouTube Music",
|
||||
|
@ -27,6 +27,10 @@ var is_7_23_or_greater = false
|
||||
private set
|
||||
var is_7_25_or_greater = false
|
||||
private set
|
||||
var is_7_27_or_greater = false
|
||||
private set
|
||||
var is_7_29_or_greater = false
|
||||
private set
|
||||
|
||||
val versionCheckPatch = resourcePatch(
|
||||
description = "versionCheckPatch",
|
||||
@ -53,5 +57,7 @@ val versionCheckPatch = resourcePatch(
|
||||
is_7_20_or_greater = 243899000 <= playStoreServicesVersion
|
||||
is_7_23_or_greater = 244199000 <= playStoreServicesVersion
|
||||
is_7_25_or_greater = 244399000 <= playStoreServicesVersion
|
||||
is_7_27_or_greater = 244515000 <= playStoreServicesVersion
|
||||
is_7_29_or_greater = 244799000 <= playStoreServicesVersion
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,33 @@
|
||||
package app.revanced.patches.music.utils.videotype
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal val videoTypeFingerprint = legacyFingerprint(
|
||||
name = "videoTypeFingerprint",
|
||||
returnType = "L",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
parameters = listOf("L"),
|
||||
opcodes = listOf(
|
||||
Opcode.IGET,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.GOTO,
|
||||
Opcode.SGET_OBJECT
|
||||
)
|
||||
customFingerprint = { method, _ ->
|
||||
indexOfGetEnumInstruction(method) >= 0
|
||||
}
|
||||
)
|
||||
|
||||
internal fun indexOfGetEnumInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_STATIC &&
|
||||
reference?.name == "a" &&
|
||||
reference.parameterTypes.firstOrNull() == "I" &&
|
||||
reference.definingClass == reference.returnType
|
||||
}
|
||||
|
||||
internal val videoTypeParentFingerprint = legacyFingerprint(
|
||||
name = "videoTypeParentFingerprint",
|
||||
returnType = "Z",
|
||||
|
@ -4,8 +4,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.music.utils.extension.Constants.UTILS_PATH
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
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.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$UTILS_PATH/VideoTypeHookPatch;"
|
||||
@ -17,22 +23,27 @@ val videoTypeHookPatch = bytecodePatch(
|
||||
|
||||
execute {
|
||||
|
||||
videoTypeFingerprint.matchOrThrow(videoTypeParentFingerprint).let {
|
||||
it.method.apply {
|
||||
val insertIndex = it.patternMatch!!.startIndex + 3
|
||||
val referenceIndex = insertIndex + 1
|
||||
val referenceInstruction =
|
||||
getInstruction<ReferenceInstruction>(referenceIndex).reference
|
||||
|
||||
addInstructionsWithLabels(
|
||||
insertIndex, """
|
||||
if-nez p0, :dismiss
|
||||
sget-object p0, $referenceInstruction
|
||||
:dismiss
|
||||
invoke-static {p0}, $EXTENSION_CLASS_DESCRIPTOR->setVideoType(Ljava/lang/Enum;)V
|
||||
"""
|
||||
)
|
||||
videoTypeFingerprint.methodOrThrow(videoTypeParentFingerprint).apply {
|
||||
val getEnumIndex = indexOfGetEnumInstruction(this)
|
||||
val enumClass = (getInstruction<ReferenceInstruction>(getEnumIndex).reference as MethodReference).definingClass
|
||||
val referenceIndex = indexOfFirstInstructionOrThrow(getEnumIndex) {
|
||||
opcode == Opcode.SGET_OBJECT &&
|
||||
getReference<FieldReference>()?.type == enumClass
|
||||
}
|
||||
val referenceInstruction =
|
||||
getInstruction<ReferenceInstruction>(referenceIndex).reference
|
||||
|
||||
val insertIndex = indexOfFirstInstructionOrThrow(getEnumIndex, Opcode.IF_NEZ)
|
||||
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstructionsWithLabels(
|
||||
insertIndex, """
|
||||
if-nez v$insertRegister, :dismiss
|
||||
sget-object v$insertRegister, $referenceInstruction
|
||||
:dismiss
|
||||
invoke-static {v$insertRegister}, $EXTENSION_CLASS_DESCRIPTOR->setVideoType(Ljava/lang/Enum;)V
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,12 +26,6 @@ internal val videoIdFingerprint = legacyFingerprint(
|
||||
returnType = "V",
|
||||
parameters = listOf("L", "Ljava/lang/String;"),
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_INTERFACE_RANGE,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_INTERFACE_RANGE,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
),
|
||||
strings = listOf("Null initialPlayabilityStatus")
|
||||
)
|
||||
|
||||
|
@ -191,7 +191,12 @@ val videoInformationPatch = bytecodePatch(
|
||||
*/
|
||||
videoIdFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val playerResponseModelIndex = it.patternMatch!!.startIndex
|
||||
val playerResponseModelIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
(opcode == Opcode.INVOKE_INTERFACE_RANGE || opcode == Opcode.INVOKE_INTERFACE) &&
|
||||
reference?.returnType == "Ljava/lang/String;" &&
|
||||
reference.parameterTypes.isEmpty()
|
||||
}
|
||||
|
||||
PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR =
|
||||
getInstruction(playerResponseModelIndex)
|
||||
|
@ -2,9 +2,19 @@ package app.revanced.patches.music.video.playerresponse
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.or
|
||||
import app.revanced.util.parametersEqual
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
private val PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST = listOf(
|
||||
"Ljava/lang/String;", // VideoId.
|
||||
"[B",
|
||||
"Ljava/lang/String;", // Player parameters proto buffer.
|
||||
"Ljava/lang/String;", // PlaylistId.
|
||||
"I", // PlaylistIndex.
|
||||
"I"
|
||||
)
|
||||
|
||||
/**
|
||||
* For targets 7.03 and later.
|
||||
*/
|
||||
@ -12,23 +22,21 @@ internal val playerParameterBuilderFingerprint = legacyFingerprint(
|
||||
name = "playerParameterBuilderFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "L",
|
||||
parameters = listOf(
|
||||
"Ljava/lang/String;", // VideoId.
|
||||
"[B",
|
||||
"Ljava/lang/String;", // Player parameters proto buffer.
|
||||
"Ljava/lang/String;", // PlaylistId.
|
||||
"I", // PlaylistIndex.
|
||||
"I",
|
||||
"L",
|
||||
"Ljava/util/Set;",
|
||||
"Ljava/lang/String;",
|
||||
"Ljava/lang/String;",
|
||||
"L",
|
||||
"Z",
|
||||
"Z",
|
||||
"Z", // Appears to indicate if the video id is being opened or is currently playing.
|
||||
),
|
||||
strings = listOf("psps")
|
||||
strings = listOf("psps"),
|
||||
customFingerprint = custom@{ method, _ ->
|
||||
val parameterTypes = method.parameterTypes
|
||||
val parameterSize = parameterTypes.size
|
||||
if (parameterSize < 13) {
|
||||
return@custom false
|
||||
}
|
||||
|
||||
val startsWithMethodParameterList = parameterTypes.slice(0..5)
|
||||
|
||||
parametersEqual(
|
||||
PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST,
|
||||
startsWithMethodParameterList
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -3,11 +3,11 @@ package app.revanced.patches.reddit.layout.subredditdialog
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.indexOfFirstInstructionReversed
|
||||
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.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal val frequentUpdatesSheetScreenFingerprint = legacyFingerprint(
|
||||
@ -26,38 +26,49 @@ internal val frequentUpdatesSheetScreenFingerprint = legacyFingerprint(
|
||||
}
|
||||
)
|
||||
|
||||
internal val frequentUpdatesSheetV2ScreenFingerprint = legacyFingerprint(
|
||||
name = "frequentUpdatesSheetV2ScreenFingerprint",
|
||||
returnType = "V",
|
||||
internal val frequentUpdatesHandlerFingerprint = legacyFingerprint(
|
||||
name = "frequentUpdatesHandlerFingerprint",
|
||||
returnType = "Ljava/lang/Object;",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
strings = listOf("subreddit_name"),
|
||||
customFingerprint = { method, classDef ->
|
||||
classDef.type == "Lcom/reddit/screens/pager/v2/FrequentUpdatesSheetV2Screen;"
|
||||
classDef.type.startsWith("Lcom/reddit/screens/pager/FrequentUpdatesHandler${'$'}handleFrequentUpdates${'$'}") &&
|
||||
method.name == "invokeSuspend" &&
|
||||
listOfIsLoggedInInstruction(method).isNotEmpty()
|
||||
}
|
||||
)
|
||||
|
||||
internal val frequentUpdatesSheetV2ScreenInvokeFingerprint = legacyFingerprint(
|
||||
name = "frequentUpdatesSheetV2ScreenInvokeFingerprint",
|
||||
returnType = "V",
|
||||
fun listOfIsLoggedInInstruction(method: Method) =
|
||||
method.implementation?.instructions
|
||||
?.withIndex()
|
||||
?.filter { (_, instruction) ->
|
||||
val reference = (instruction as? ReferenceInstruction)?.reference
|
||||
instruction.opcode == Opcode.INVOKE_INTERFACE &&
|
||||
reference is MethodReference &&
|
||||
reference.name == "isLoggedIn" &&
|
||||
reference.returnType == "Z"
|
||||
}
|
||||
?.map { (index, _) -> index }
|
||||
?.reversed()
|
||||
?: emptyList()
|
||||
|
||||
internal val nsfwAlertEmitFingerprint = legacyFingerprint(
|
||||
name = "nsfwAlertEmitFingerprint",
|
||||
returnType = "Ljava/lang/Object;",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
opcodes = listOf(
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.RETURN_VOID,
|
||||
),
|
||||
customFingerprint = { method, classDef ->
|
||||
classDef.type.startsWith("Lcom/reddit/screens/pager/v2/FrequentUpdatesSheetV2Screen${'$'}SheetContent${'$'}") &&
|
||||
method.name == "invoke" &&
|
||||
indexOfDismissScreenInstruction(method) >= 0
|
||||
strings = listOf("reddit://reddit/r/", "nsfwAlertDelegate"),
|
||||
customFingerprint = { method, _ ->
|
||||
method.name == "emit" &&
|
||||
indexOfHasBeenVisitedInstruction(method) >= 0
|
||||
}
|
||||
)
|
||||
|
||||
fun indexOfDismissScreenInstruction(method: Method) =
|
||||
method.indexOfFirstInstructionReversed {
|
||||
fun indexOfHasBeenVisitedInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
reference?.returnType == "V" &&
|
||||
reference.parameterTypes.isEmpty()
|
||||
reference?.name == "getHasBeenVisited" &&
|
||||
reference.returnType == "Z"
|
||||
}
|
||||
|
||||
internal val redditAlertDialogsFingerprint = legacyFingerprint(
|
||||
|
@ -3,15 +3,14 @@ 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.extensions.InstructionExtensions.removeInstruction
|
||||
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.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.settingsPatch
|
||||
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
@ -19,7 +18,6 @@ import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
@ -36,6 +34,25 @@ val subRedditDialogPatch = bytecodePatch(
|
||||
|
||||
execute {
|
||||
|
||||
if (is_2024_41_or_greater) {
|
||||
frequentUpdatesHandlerFingerprint
|
||||
.methodOrThrow()
|
||||
.apply {
|
||||
listOfIsLoggedInInstruction(this)
|
||||
.forEach { index ->
|
||||
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
|
||||
|
||||
addInstructions(
|
||||
index + 2, """
|
||||
invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->spoofLoggedInStatus(Z)Z
|
||||
move-result v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not used in latest Reddit client.
|
||||
frequentUpdatesSheetScreenFingerprint.methodOrThrow().apply {
|
||||
val index = indexOfFirstInstructionReversedOrThrow(Opcode.RETURN_OBJECT)
|
||||
val register =
|
||||
@ -43,42 +60,27 @@ val subRedditDialogPatch = bytecodePatch(
|
||||
|
||||
addInstruction(
|
||||
index,
|
||||
"invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->onDialogCreated(Landroid/view/View;)V"
|
||||
"invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->dismissDialog(Landroid/view/View;)V"
|
||||
)
|
||||
}
|
||||
|
||||
if (is_2024_41_or_greater) {
|
||||
val dismissReference = with (frequentUpdatesSheetV2ScreenInvokeFingerprint.methodOrThrow()) {
|
||||
val index = indexOfDismissScreenInstruction(this)
|
||||
getInstruction<ReferenceInstruction>(index).reference as MethodReference
|
||||
if (is_2025_01_or_greater) {
|
||||
nsfwAlertEmitFingerprint.methodOrThrow().apply {
|
||||
val hasBeenVisitedIndex = indexOfHasBeenVisitedInstruction(this)
|
||||
val hasBeenVisitedRegister =
|
||||
getInstruction<OneRegisterInstruction>(hasBeenVisitedIndex + 1).registerA
|
||||
|
||||
addInstructions(
|
||||
hasBeenVisitedIndex + 2, """
|
||||
invoke-static {v$hasBeenVisitedRegister}, $EXTENSION_CLASS_DESCRIPTOR->spoofHasBeenVisitedStatus(Z)Z
|
||||
move-result v$hasBeenVisitedRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
findMethodOrThrow(EXTENSION_CLASS_DESCRIPTOR) {
|
||||
name == "dismissRedditDialogV2"
|
||||
}.addInstructions(
|
||||
0, """
|
||||
check-cast p0, ${dismissReference.definingClass}
|
||||
invoke-virtual {p0}, $dismissReference
|
||||
"""
|
||||
)
|
||||
|
||||
frequentUpdatesSheetV2ScreenFingerprint
|
||||
.methodOrThrow()
|
||||
.apply {
|
||||
val targetIndex = implementation!!.instructions.lastIndex
|
||||
|
||||
addInstructions(
|
||||
targetIndex + 1, """
|
||||
invoke-static {p0}, $EXTENSION_CLASS_DESCRIPTOR->dismissDialogV2(Ljava/lang/Object;)V
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
removeInstruction(targetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// Not used in latest Reddit client.
|
||||
redditAlertDialogsFingerprint.second.methodOrNull?.apply {
|
||||
redditAlertDialogsFingerprint.methodOrThrow().apply {
|
||||
val backgroundTintIndex = indexOfSetBackgroundTintListInstruction(this)
|
||||
val insertIndex =
|
||||
indexOfFirstInstructionOrThrow(backgroundTintIndex) {
|
||||
|
@ -11,7 +11,7 @@ internal enum class PatchList(
|
||||
),
|
||||
CUSTOM_BRANDING_NAME_FOR_REDDIT(
|
||||
"Custom branding name for Reddit",
|
||||
"Renames the Reddit app to the name specified in patch options."
|
||||
"Changes the Reddit app name to the name specified in patch options."
|
||||
),
|
||||
DISABLE_SCREENSHOT_POPUP(
|
||||
"Disable screenshot popup",
|
||||
@ -55,7 +55,7 @@ internal enum class PatchList(
|
||||
),
|
||||
SANITIZE_SHARING_LINKS(
|
||||
"Sanitize sharing links",
|
||||
"Adds an option to remove tracking query parameters from URLs when sharing links."
|
||||
"Adds an option to sanitize sharing links by removing tracking query parameters."
|
||||
),
|
||||
SETTINGS_FOR_REDDIT(
|
||||
"Settings for Reddit",
|
||||
|
@ -37,6 +37,8 @@ var is_2024_26_or_greater = false
|
||||
private set
|
||||
var is_2024_41_or_greater = false
|
||||
private set
|
||||
var is_2025_01_or_greater = false
|
||||
private set
|
||||
|
||||
private val settingsBytecodePatch = bytecodePatch(
|
||||
description = "settingsBytecodePatch"
|
||||
@ -58,7 +60,8 @@ private val settingsBytecodePatch = bytecodePatch(
|
||||
.replace(".", "").toInt()
|
||||
|
||||
is_2024_26_or_greater = 2024260 <= versionNumber
|
||||
is_2024_41_or_greater = 2024100 <= versionNumber
|
||||
is_2024_41_or_greater = 2024410 <= versionNumber
|
||||
is_2025_01_or_greater = 2025010 <= versionNumber
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,13 +30,15 @@ val drawableColorHookPatch = bytecodePatch(
|
||||
}
|
||||
|
||||
internal fun addDrawableColorHook(
|
||||
methodDescriptor: String
|
||||
methodDescriptor: String,
|
||||
highPriority: Boolean = false
|
||||
) {
|
||||
insertMethod.addInstructions(
|
||||
insertIndex + offset, """
|
||||
invoke-static {v$insertRegister}, $methodDescriptor
|
||||
move-result v$insertRegister
|
||||
"""
|
||||
if (highPriority) insertIndex else insertIndex + offset,
|
||||
"""
|
||||
invoke-static {v$insertRegister}, $methodDescriptor
|
||||
move-result v$insertRegister
|
||||
"""
|
||||
)
|
||||
offset += 2
|
||||
}
|
||||
|
@ -389,6 +389,9 @@ private object Constants {
|
||||
|
||||
// misc
|
||||
"com.google.android.gms.clearcut.service.START",
|
||||
"com.google.android.gms.common.telemetry.service.START",
|
||||
"com.google.android.gms.gmscompliance.service.START",
|
||||
"com.google.android.gms.icing.LIGHTWEIGHT_INDEX_SERVICE",
|
||||
"com.google.android.gms.languageprofile.service.START",
|
||||
"com.google.android.gms.measurement.START",
|
||||
"com.google.android.gms.pseudonymous.service.START",
|
||||
|
@ -16,6 +16,7 @@ import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import app.revanced.util.indexOfFirstStringInstruction
|
||||
import app.revanced.util.indexOfFirstStringInstructionOrThrow
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
@ -27,6 +28,7 @@ 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
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
@ -42,6 +44,8 @@ private var filterCount = 0
|
||||
internal lateinit var addLithoFilter: (String) -> Unit
|
||||
private set
|
||||
|
||||
internal var emptyComponentLabel = ""
|
||||
|
||||
val lithoFilterPatch = bytecodePatch(
|
||||
description = "lithoFilterPatch",
|
||||
) {
|
||||
@ -73,6 +77,8 @@ val lithoFilterPatch = bytecodePatch(
|
||||
return-object v0
|
||||
"""
|
||||
|
||||
emptyComponentLabel = label
|
||||
|
||||
Pair(this, label)
|
||||
}
|
||||
}
|
||||
@ -121,17 +127,25 @@ val lithoFilterPatch = bytecodePatch(
|
||||
val stringBuilderRegister =
|
||||
getInstruction<TwoRegisterInstruction>(stringBuilderIndex).registerA
|
||||
|
||||
val emptyStringIndex = indexOfFirstStringInstructionOrThrow("")
|
||||
val emptyStringIndex = indexOfFirstStringInstruction("")
|
||||
val relativeIndex = if (emptyStringIndex > -1) {
|
||||
emptyStringIndex
|
||||
} else {
|
||||
val separatorIndex = indexOfFirstStringInstructionOrThrow("|")
|
||||
indexOfFirstInstructionOrThrow(separatorIndex) {
|
||||
opcode == Opcode.NEW_INSTANCE &&
|
||||
getReference<TypeReference>()?.type == "Ljava/lang/StringBuilder;"
|
||||
}
|
||||
}
|
||||
|
||||
val identifierRegister = getInstruction<TwoRegisterInstruction>(
|
||||
indexOfFirstInstructionReversedOrThrow(emptyStringIndex) {
|
||||
indexOfFirstInstructionReversedOrThrow(relativeIndex) {
|
||||
opcode == Opcode.IPUT_OBJECT
|
||||
&& getReference<FieldReference>()?.type == "Ljava/lang/String;"
|
||||
}
|
||||
).registerA
|
||||
val objectRegister = getInstruction<FiveRegisterInstruction>(
|
||||
indexOfFirstInstructionOrThrow(emptyStringIndex) {
|
||||
opcode == Opcode.INVOKE_VIRTUAL
|
||||
}
|
||||
indexOfFirstInstructionOrThrow(relativeIndex, Opcode.INVOKE_VIRTUAL)
|
||||
).registerC
|
||||
|
||||
val insertIndex = stringBuilderIndex + 1
|
||||
|
@ -14,14 +14,11 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMu
|
||||
import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
|
||||
import app.revanced.patches.shared.formatStreamModelConstructorFingerprint
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.definingClassOrThrow
|
||||
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
|
||||
import app.revanced.util.getWalkerMethod
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
@ -370,35 +367,4 @@ fun baseSpoofStreamingDataPatch(
|
||||
executeBlock()
|
||||
|
||||
}
|
||||
|
||||
finalize {
|
||||
gmsServiceBrokerFingerprint.methodOrThrow()
|
||||
.addInstructionsWithLabels(
|
||||
0, """
|
||||
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled()Z
|
||||
move-result v0
|
||||
if-eqz v0, :ignore
|
||||
return-void
|
||||
:ignore
|
||||
nop
|
||||
"""
|
||||
)
|
||||
|
||||
gmsServiceBrokerExceptionFingerprint.matchOrThrow().let {
|
||||
val walkerIndex = it.patternMatch!!.startIndex
|
||||
val walkerMethod = it.getWalkerMethod(walkerIndex)
|
||||
|
||||
walkerMethod.apply {
|
||||
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.CHECK_CAST)
|
||||
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
insertIndex + 1, """
|
||||
invoke-static {v$insertRegister}, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
move-result-object v$insertRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,20 +198,4 @@ internal val hlsCurrentTimeFingerprint = legacyFingerprint(
|
||||
literals = listOf(HLS_CURRENT_TIME_FEATURE_FLAG),
|
||||
)
|
||||
|
||||
internal val gmsServiceBrokerFingerprint = legacyFingerprint(
|
||||
name = "gmsServiceBrokerFingerprint",
|
||||
returnType = "V",
|
||||
strings = listOf("mServiceBroker is null, client disconnected")
|
||||
)
|
||||
|
||||
internal val gmsServiceBrokerExceptionFingerprint = legacyFingerprint(
|
||||
name = "gmsServiceBrokerExceptionFingerprint",
|
||||
returnType = "V",
|
||||
parameters = listOf("Ljava/lang/Exception;"),
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.RETURN_VOID
|
||||
),
|
||||
strings = listOf("Exception must not be null")
|
||||
)
|
||||
|
||||
|
@ -16,6 +16,7 @@ import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
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
|
||||
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
|
||||
@ -40,11 +41,11 @@ val textComponentPatch = bytecodePatch(
|
||||
spannedIndex = indexOfSpannableStringInstruction(this)
|
||||
spannedRegister = getInstruction<FiveRegisterInstruction>(spannedIndex).registerC
|
||||
spannedContextRegister =
|
||||
getInstruction<TwoRegisterInstruction>(0).registerA
|
||||
getInstruction<OneRegisterInstruction>(spannedIndex + 1).registerA
|
||||
|
||||
replaceInstruction(
|
||||
spannedIndex,
|
||||
"nop"
|
||||
"move-object/from16 v$spannedContextRegister, p0"
|
||||
)
|
||||
addInstruction(
|
||||
++spannedIndex,
|
||||
|
@ -49,6 +49,7 @@ fun ResourcePatchContext.baseTranslationsPatch(
|
||||
sourceDirectory: String,
|
||||
) {
|
||||
val resourceDirectory = get("res")
|
||||
val isYouTube = sourceDirectory == "youtube"
|
||||
|
||||
// Check if the custom translation path is valid.
|
||||
customTranslations?.takeIf { it.isNotEmpty() }?.let { customLang ->
|
||||
@ -90,10 +91,10 @@ fun ResourcePatchContext.baseTranslationsPatch(
|
||||
|
||||
// Filter the app languages to include both versions of locales (with and without 'r', en-rGB and en-GB)
|
||||
// and also handle locales with "b+" prefix
|
||||
val filteredAppLanguages = selectedStringResourcesArray.flatMap { language ->
|
||||
setOf(language, language.replace("-r", "-"),
|
||||
language.replace("b+", "").replace("+", "-"))
|
||||
}.toTypedArray()
|
||||
var filteredAppLanguages = (selectedStringResourcesArray + arrayOf("en"))
|
||||
.map { language ->
|
||||
language.replace("-r", "-").replace("b+", "").replace("+", "-")
|
||||
}.toHashSet().toTypedArray()
|
||||
|
||||
// Remove unselected app languages from UI
|
||||
document("res/xml/locales_config.xml").use { document ->
|
||||
@ -102,7 +103,7 @@ fun ResourcePatchContext.baseTranslationsPatch(
|
||||
document.doRecursively { node ->
|
||||
if (node is Element && node.tagName == "locale") {
|
||||
node.getAttributeNode("android:name")?.let { attribute ->
|
||||
if (attribute.textContent != "en" && attribute.textContent !in filteredAppLanguages) {
|
||||
if (attribute.textContent !in filteredAppLanguages) {
|
||||
nodesToRemove.add(node)
|
||||
}
|
||||
}
|
||||
@ -114,6 +115,45 @@ fun ResourcePatchContext.baseTranslationsPatch(
|
||||
node.parentNode?.removeChild(node)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isYouTube) return
|
||||
|
||||
filteredAppLanguages = filteredAppLanguages.map { language ->
|
||||
language.subSequence(0,2).toString().uppercase()
|
||||
}.toHashSet().toTypedArray()
|
||||
|
||||
// Remove unselected app languages from RVX Settings
|
||||
setOf(
|
||||
"revanced_language_entries",
|
||||
"revanced_language_entry_values",
|
||||
).forEach { attributeName ->
|
||||
document("res/values/arrays.xml").use { document ->
|
||||
with(document) {
|
||||
val nodesToRemove = mutableListOf<Node>()
|
||||
|
||||
val resourcesNode = getElementsByTagName("resources").item(0) as Element
|
||||
for (i in 0 until resourcesNode.childNodes.length) {
|
||||
val node = resourcesNode.childNodes.item(i) as? Element ?: continue
|
||||
|
||||
if (node.getAttribute("name") == attributeName) {
|
||||
for (j in 0 until node.childNodes.length) {
|
||||
val item = node.childNodes.item(j) as? Element ?: continue
|
||||
val text = item.textContent
|
||||
val length = text.length
|
||||
if (!text.endsWith("DEFAULT") && text.subSequence(length - 2, length) !in filteredAppLanguages) {
|
||||
nodesToRemove.add(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the collected nodes (avoids NullPointerException)
|
||||
for (n in nodesToRemove) {
|
||||
n.parentNode?.removeChild(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,13 +7,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
|
||||
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.shared.litho.addLithoFilter
|
||||
import app.revanced.patches.shared.litho.emptyComponentLabel
|
||||
import app.revanced.patches.shared.mainactivity.onCreateMethod
|
||||
import app.revanced.patches.youtube.utils.bottomsheet.bottomSheetHookPatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.engagementPanelBuilderFingerprint
|
||||
import app.revanced.patches.youtube.utils.engagement.engagementPanelHookPatch
|
||||
import app.revanced.patches.youtube.utils.engagement.hookEngagementPanelState
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.FEED_CLASS_DESCRIPTOR
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.FEED_PATH
|
||||
@ -21,6 +22,9 @@ import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch
|
||||
import app.revanced.patches.youtube.utils.navigation.navigationBarHookPatch
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_FEED_COMPONENTS
|
||||
import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch
|
||||
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.resourceid.bar
|
||||
import app.revanced.patches.youtube.utils.resourceid.captionToggleContainer
|
||||
import app.revanced.patches.youtube.utils.resourceid.channelListSubMenu
|
||||
@ -37,19 +41,16 @@ import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.fingerprint.mutableClassOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.getWalkerMethod
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
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.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
private const val CAROUSEL_SHELF_FILTER_CLASS_DESCRIPTOR =
|
||||
"$COMPONENTS_PATH/CarouselShelfFilter;"
|
||||
@ -78,6 +79,8 @@ val feedComponentsPatch = bytecodePatch(
|
||||
sharedResourceIdPatch,
|
||||
settingsPatch,
|
||||
bottomSheetHookPatch,
|
||||
engagementPanelHookPatch,
|
||||
versionCheckPatch,
|
||||
)
|
||||
execute {
|
||||
|
||||
@ -171,38 +174,6 @@ val feedComponentsPatch = bytecodePatch(
|
||||
|
||||
// region patch for hide relative video
|
||||
|
||||
fun Method.indexOfEngagementPanelBuilderInstruction(targetMethod: MutableMethod) =
|
||||
indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_DIRECT &&
|
||||
MethodUtil.methodSignaturesMatch(
|
||||
targetMethod,
|
||||
getReference<MethodReference>()!!
|
||||
)
|
||||
}
|
||||
|
||||
engagementPanelBuilderFingerprint.matchOrThrow().let {
|
||||
it.classDef.methods.filter { method ->
|
||||
method.indexOfEngagementPanelBuilderInstruction(it.method) >= 0
|
||||
}.forEach { method ->
|
||||
method.apply {
|
||||
val index = indexOfEngagementPanelBuilderInstruction(it.method)
|
||||
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
|
||||
|
||||
addInstruction(
|
||||
index + 2,
|
||||
"invoke-static {v$register}, " +
|
||||
"$RELATED_VIDEO_CLASS_DESCRIPTOR->showEngagementPanel(Ljava/lang/Object;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
engagementPanelUpdateFingerprint.methodOrThrow(engagementPanelBuilderFingerprint)
|
||||
.addInstruction(
|
||||
0,
|
||||
"invoke-static {}, $RELATED_VIDEO_CLASS_DESCRIPTOR->hideEngagementPanel()V"
|
||||
)
|
||||
|
||||
linearLayoutManagerItemCountsFingerprint.matchOrThrow().let {
|
||||
val methodWalker =
|
||||
it.getWalkerMethod(it.patternMatch!!.endIndex)
|
||||
@ -219,23 +190,28 @@ val feedComponentsPatch = bytecodePatch(
|
||||
}
|
||||
}
|
||||
|
||||
hookEngagementPanelState(RELATED_VIDEO_CLASS_DESCRIPTOR)
|
||||
|
||||
// endregion
|
||||
|
||||
// region patch for hide subscriptions channel section for tablet
|
||||
|
||||
arrayOf(
|
||||
channelListSubMenuTabletFingerprint,
|
||||
channelListSubMenuTabletSyntheticFingerprint
|
||||
).forEach { fingerprint ->
|
||||
fingerprint.methodOrThrow().apply {
|
||||
addInstructionsWithLabels(
|
||||
0, """
|
||||
invoke-static {}, $FEED_CLASS_DESCRIPTOR->hideSubscriptionsChannelSection()Z
|
||||
move-result v0
|
||||
if-eqz v0, :show
|
||||
return-void
|
||||
""", ExternalLabel("show", getInstruction(0))
|
||||
)
|
||||
// Integrated as a litho component since YouTube 20.02.
|
||||
if (!is_20_02_or_greater) {
|
||||
arrayOf(
|
||||
channelListSubMenuTabletFingerprint,
|
||||
channelListSubMenuTabletSyntheticFingerprint
|
||||
).forEach { fingerprint ->
|
||||
fingerprint.methodOrThrow().apply {
|
||||
addInstructionsWithLabels(
|
||||
0, """
|
||||
invoke-static {}, $FEED_CLASS_DESCRIPTOR->hideSubscriptionsChannelSection()Z
|
||||
move-result v0
|
||||
if-eqz v0, :show
|
||||
return-void
|
||||
""", ExternalLabel("show", getInstruction(0))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,30 +263,42 @@ val feedComponentsPatch = bytecodePatch(
|
||||
it.method.apply {
|
||||
val freeRegister = implementation!!.registerCount - parameters.size - 2
|
||||
val insertIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = ((this as? ReferenceInstruction)?.reference as? MethodReference)
|
||||
val reference = getReference<MethodReference>()
|
||||
|
||||
reference?.parameterTypes?.size == 1 &&
|
||||
reference.parameterTypes.first() == "[B" &&
|
||||
reference.returnType.startsWith("L")
|
||||
}
|
||||
|
||||
val objectIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_OBJECT)
|
||||
val objectRegister = getInstruction<TwoRegisterInstruction>(objectIndex).registerA
|
||||
if (is_19_46_or_greater) {
|
||||
val objectIndex = indexOfFirstInstructionReversedOrThrow(insertIndex, Opcode.IGET_OBJECT)
|
||||
val objectRegister = getInstruction<TwoRegisterInstruction>(objectIndex).registerA
|
||||
|
||||
val jumpIndex = it.patternMatch!!.startIndex
|
||||
addInstructionsWithLabels(
|
||||
insertIndex, """
|
||||
invoke-static {v$objectRegister, p3}, $FEED_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
|
||||
move-result v$freeRegister
|
||||
if-eqz v$freeRegister, :ignore
|
||||
""" + emptyComponentLabel,
|
||||
ExternalLabel("ignore", getInstruction(insertIndex))
|
||||
)
|
||||
} else {
|
||||
val objectIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_OBJECT)
|
||||
val objectRegister = getInstruction<TwoRegisterInstruction>(objectIndex).registerA
|
||||
val jumpIndex = it.patternMatch!!.startIndex
|
||||
|
||||
addInstructionsWithLabels(
|
||||
insertIndex, """
|
||||
invoke-static {v$objectRegister, v$freeRegister}, $FEED_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
|
||||
move-result v$freeRegister
|
||||
if-nez v$freeRegister, :filter
|
||||
""", ExternalLabel("filter", getInstruction(jumpIndex))
|
||||
)
|
||||
|
||||
addInstruction(
|
||||
0,
|
||||
"move-object/from16 v$freeRegister, p3"
|
||||
)
|
||||
addInstructionsWithLabels(
|
||||
insertIndex, """
|
||||
invoke-static {v$objectRegister, v$freeRegister}, $FEED_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
|
||||
move-result v$freeRegister
|
||||
if-nez v$freeRegister, :filter
|
||||
""", ExternalLabel("filter", getInstruction(jumpIndex))
|
||||
)
|
||||
addInstruction(
|
||||
0,
|
||||
"move-object/from16 v$freeRegister, p3"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,12 +11,9 @@ import app.revanced.patches.youtube.utils.resourceid.filterBarHeight
|
||||
import app.revanced.patches.youtube.utils.resourceid.horizontalCardList
|
||||
import app.revanced.patches.youtube.utils.resourceid.relatedChipCloudMargin
|
||||
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.reference.MethodReference
|
||||
|
||||
internal val breakingNewsFingerprint = legacyFingerprint(
|
||||
name = "breakingNewsFingerprint",
|
||||
@ -103,19 +100,6 @@ internal val elementParserParentFingerprint = legacyFingerprint(
|
||||
strings = listOf("Element tree missing id in debug mode.")
|
||||
)
|
||||
|
||||
internal val engagementPanelUpdateFingerprint = legacyFingerprint(
|
||||
name = "engagementPanelUpdateFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
|
||||
parameters = listOf("L", "Z"),
|
||||
customFingerprint = { method, _ ->
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>().toString() == "Ljava/util/ArrayDeque;->pop()Ljava/lang/Object;"
|
||||
} >= 0
|
||||
}
|
||||
)
|
||||
|
||||
internal val filterBarHeightFingerprint = legacyFingerprint(
|
||||
name = "filterBarHeightFingerprint",
|
||||
returnType = "V",
|
||||
|
@ -69,16 +69,6 @@ internal val appBlockingCheckResultToStringFingerprint = legacyFingerprint(
|
||||
strings = listOf("AppBlockingCheckResult{intent=")
|
||||
)
|
||||
|
||||
internal val bottomUiContainerFingerprint = legacyFingerprint(
|
||||
name = "bottomUiContainerFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("L", "L"),
|
||||
customFingerprint = { method, _ ->
|
||||
method.definingClass.endsWith("/BottomUiContainer;")
|
||||
}
|
||||
)
|
||||
|
||||
internal val floatingMicrophoneFingerprint = legacyFingerprint(
|
||||
name = "floatingMicrophoneFingerprint",
|
||||
returnType = "V",
|
||||
@ -87,7 +77,6 @@ internal val floatingMicrophoneFingerprint = legacyFingerprint(
|
||||
opcodes = listOf(
|
||||
Opcode.IGET_BOOLEAN,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.RETURN_VOID
|
||||
),
|
||||
literals = listOf(fab),
|
||||
)
|
||||
|
@ -2,12 +2,10 @@ package app.revanced.patches.youtube.general.components
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.shared.litho.addLithoFilter
|
||||
import app.revanced.patches.shared.litho.lithoFilterPatch
|
||||
import app.revanced.patches.shared.settingmenu.settingsMenuPatch
|
||||
@ -20,6 +18,7 @@ import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_LAYOUT_COMPONENTS
|
||||
import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.utils.resourceid.accountSwitcherAccessibility
|
||||
import app.revanced.patches.youtube.utils.resourceid.fab
|
||||
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
|
||||
@ -154,18 +153,17 @@ val layoutComponentsPatch = bytecodePatch(
|
||||
|
||||
// region patch for hide floating microphone
|
||||
|
||||
floatingMicrophoneFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val insertIndex = it.patternMatch!!.startIndex
|
||||
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
floatingMicrophoneFingerprint.methodOrThrow().apply {
|
||||
val literalIndex = indexOfFirstLiteralInstructionOrThrow(fab)
|
||||
val booleanIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.IGET_BOOLEAN)
|
||||
val insertRegister = getInstruction<TwoRegisterInstruction>(booleanIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
insertIndex + 1, """
|
||||
invoke-static {v$register}, $GENERAL_CLASS_DESCRIPTOR->hideFloatingMicrophone(Z)Z
|
||||
move-result v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
addInstructions(
|
||||
booleanIndex + 1, """
|
||||
invoke-static {v$insertRegister}, $GENERAL_CLASS_DESCRIPTOR->hideFloatingMicrophone(Z)Z
|
||||
move-result v$insertRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
@ -215,21 +213,6 @@ val layoutComponentsPatch = bytecodePatch(
|
||||
|
||||
// endregion
|
||||
|
||||
// region patch for hide snack bar
|
||||
|
||||
bottomUiContainerFingerprint.methodOrThrow().apply {
|
||||
addInstructionsWithLabels(
|
||||
0, """
|
||||
invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->hideSnackBar()Z
|
||||
move-result v0
|
||||
if-eqz v0, :show
|
||||
return-void
|
||||
""", ExternalLabel("show", getInstruction(0))
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region patch for hide tooltip content
|
||||
|
||||
tooltipContentFullscreenFingerprint.methodOrThrow().apply {
|
||||
|
@ -6,7 +6,7 @@ import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.shared.createPlayerRequestBodyWithModelFingerprint
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.LAYOUT_SWITCH
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.CHANGE_LAYOUT
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.util.fingerprint.definingClassOrThrow
|
||||
@ -24,8 +24,8 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
|
||||
@Suppress("unused")
|
||||
val layoutSwitchPatch = bytecodePatch(
|
||||
LAYOUT_SWITCH.title,
|
||||
LAYOUT_SWITCH.summary,
|
||||
CHANGE_LAYOUT.title,
|
||||
CHANGE_LAYOUT.summary,
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
@ -71,9 +71,9 @@ val layoutSwitchPatch = bytecodePatch(
|
||||
arrayOf(
|
||||
"PREFERENCE_SCREEN: GENERAL",
|
||||
"PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS",
|
||||
"SETTINGS: LAYOUT_SWITCH"
|
||||
"SETTINGS: CHANGE_LAYOUT"
|
||||
),
|
||||
LAYOUT_SWITCH
|
||||
CHANGE_LAYOUT
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
@ -0,0 +1,42 @@
|
||||
package app.revanced.patches.youtube.general.livering
|
||||
|
||||
import app.revanced.patches.youtube.utils.resourceid.elementsImage
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal val elementsImageFingerprint = legacyFingerprint(
|
||||
name = "elementsImageFingerprint",
|
||||
returnType = "Landroid/view/View;",
|
||||
accessFlags = AccessFlags.PRIVATE or AccessFlags.STATIC,
|
||||
parameters = listOf("Landroid/view/View;"),
|
||||
literals = listOf(elementsImage),
|
||||
)
|
||||
|
||||
internal val clientSettingEndpointFingerprint = legacyFingerprint(
|
||||
name = "clientSettingEndpointFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("L", "Ljava/util/Map;"),
|
||||
strings = listOf(
|
||||
"force_fullscreen",
|
||||
"PLAYBACK_START_DESCRIPTOR_MUTATOR",
|
||||
"VideoPresenterConstants.VIDEO_THUMBNAIL_BITMAP_KEY"
|
||||
),
|
||||
customFingerprint = { method, _ ->
|
||||
indexOfPlaybackStartDescriptorInstruction(method) >= 0
|
||||
}
|
||||
)
|
||||
|
||||
internal fun indexOfPlaybackStartDescriptorInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
reference?.returnType == "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;" &&
|
||||
reference.parameterTypes.isEmpty()
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package app.revanced.patches.youtube.general.livering
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.engagement.engagementPanelHookPatch
|
||||
import app.revanced.patches.youtube.utils.engagement.hookEngagementPanelState
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.CHANGE_LIVE_RING_CLICK_ACTION
|
||||
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.playbackstart.playbackStartDescriptorPatch
|
||||
import app.revanced.patches.youtube.video.playbackstart.playbackStartVideoIdReference
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$GENERAL_PATH/OpenChannelOfLiveAvatarPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val openChannelOfLiveAvatarPatch = bytecodePatch(
|
||||
CHANGE_LIVE_RING_CLICK_ACTION.title,
|
||||
CHANGE_LIVE_RING_CLICK_ACTION.summary,
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
sharedResourceIdPatch,
|
||||
playbackStartDescriptorPatch,
|
||||
engagementPanelHookPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
|
||||
elementsImageFingerprint.methodOrThrow().addInstruction(
|
||||
0,
|
||||
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->liveChannelAvatarClicked()V"
|
||||
)
|
||||
|
||||
hookEngagementPanelState(EXTENSION_CLASS_DESCRIPTOR)
|
||||
|
||||
clientSettingEndpointFingerprint.methodOrThrow().apply {
|
||||
val eqzIndex = indexOfFirstInstructionReversedOrThrow(Opcode.IF_EQZ)
|
||||
var freeIndex = indexOfFirstInstructionReversedOrThrow(eqzIndex, Opcode.NEW_INSTANCE)
|
||||
var freeRegister = getInstruction<OneRegisterInstruction>(freeIndex).registerA
|
||||
|
||||
addInstructionsWithLabels(
|
||||
eqzIndex, """
|
||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->openChannelOfLiveAvatar()Z
|
||||
move-result v$freeRegister
|
||||
if-eqz v$freeRegister, :ignore
|
||||
return-void
|
||||
:ignore
|
||||
nop
|
||||
"""
|
||||
)
|
||||
|
||||
val playbackStartIndex = indexOfPlaybackStartDescriptorInstruction(this) + 1
|
||||
val playbackStartRegister = getInstruction<OneRegisterInstruction>(playbackStartIndex).registerA
|
||||
|
||||
freeIndex = indexOfFirstInstructionOrThrow(playbackStartIndex, Opcode.CONST_STRING)
|
||||
freeRegister = getInstruction<OneRegisterInstruction>(freeIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
playbackStartIndex + 1, """
|
||||
invoke-virtual { v$playbackStartRegister }, $playbackStartVideoIdReference
|
||||
move-result-object v$freeRegister
|
||||
invoke-static { v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->openChannelOfLiveAvatar(Ljava/lang/String;)V
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
// region add settings
|
||||
|
||||
addPreference(
|
||||
arrayOf(
|
||||
"PREFERENCE_SCREEN: GENERAL",
|
||||
"PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS",
|
||||
"SETTINGS: CHANGE_LIVE_RING_CLICK_ACTION"
|
||||
),
|
||||
CHANGE_LIVE_RING_CLICK_ACTION
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
}
|
||||
}
|
@ -281,7 +281,7 @@ val miniplayerPatch = bytecodePatch(
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->setRoundedCorners(Z)Z"
|
||||
)
|
||||
|
||||
settingArray += "SETTINGS: MINIPLAYER_ROUNDED_CONERS"
|
||||
settingArray += "SETTINGS: MINIPLAYER_ROUNDED_CORNERS"
|
||||
}
|
||||
|
||||
if (is_19_43_or_greater) {
|
||||
|
@ -0,0 +1,68 @@
|
||||
package app.revanced.patches.youtube.general.snackbar
|
||||
|
||||
import app.revanced.patches.youtube.utils.resourceid.insetElementsWrapper
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionReversed
|
||||
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
|
||||
|
||||
private const val BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR =
|
||||
"Lcom/google/android/apps/youtube/app/common/ui/bottomui/BottomUiContainer;"
|
||||
|
||||
internal val bottomUiContainerFingerprint = legacyFingerprint(
|
||||
name = "bottomUiContainerFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("L", "L"),
|
||||
customFingerprint = { _, classDef ->
|
||||
classDef.type == BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR
|
||||
}
|
||||
)
|
||||
|
||||
internal val bottomUiContainerPreFingerprint = legacyFingerprint(
|
||||
name = "bottomUiContainerPreFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("L", "L", "L"),
|
||||
opcodes = listOf(
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.RETURN_VOID
|
||||
),
|
||||
customFingerprint = { _, classDef ->
|
||||
classDef.type == BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR
|
||||
}
|
||||
)
|
||||
|
||||
internal val bottomUiContainerThemeFingerprint = legacyFingerprint(
|
||||
name = "bottomUiContainerThemeFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf(BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR),
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.IF_NE,
|
||||
Opcode.CONST,
|
||||
),
|
||||
)
|
||||
|
||||
internal val lithoSnackBarFingerprint = legacyFingerprint(
|
||||
name = "lithoSnackBarFingerprint",
|
||||
returnType = "Landroid/view/View;",
|
||||
literals = listOf(insetElementsWrapper),
|
||||
customFingerprint = { method, _ ->
|
||||
indexOfBackGroundColor(method) >= 0
|
||||
}
|
||||
)
|
||||
|
||||
internal fun indexOfBackGroundColor(method: Method) =
|
||||
method.indexOfFirstInstructionReversed {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "setBackgroundColor"
|
||||
}
|
@ -0,0 +1,342 @@
|
||||
package app.revanced.patches.youtube.general.snackbar
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
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.MutableMethod
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.shared.drawable.addDrawableColorHook
|
||||
import app.revanced.patches.shared.drawable.drawableColorHookPatch
|
||||
import app.revanced.patches.shared.spans.addSpanFilter
|
||||
import app.revanced.patches.shared.spans.inclusiveSpanPatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.SPANS_PATH
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.SNACK_BAR_COMPONENTS
|
||||
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.util.findElementByAttributeValueOrThrow
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getNode
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.valueOrThrow
|
||||
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
|
||||
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 org.w3c.dom.Element
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$GENERAL_PATH/SnackBarPatch;"
|
||||
private const val FILTER_CLASS_DESCRIPTOR =
|
||||
"$SPANS_PATH/SnackBarFilter;"
|
||||
|
||||
private val snackBarComponentsBytecodePatch = bytecodePatch(
|
||||
description = "snackBarComponentsBytecodePatch"
|
||||
) {
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
sharedResourceIdPatch,
|
||||
drawableColorHookPatch,
|
||||
inclusiveSpanPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
bottomUiContainerFingerprint.methodOrThrow().apply {
|
||||
addInstructionsWithLabels(
|
||||
0, """
|
||||
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hideSnackBar()Z
|
||||
move-result v0
|
||||
if-eqz v0, :show
|
||||
return-void
|
||||
""", ExternalLabel("show", getInstruction(0))
|
||||
)
|
||||
}
|
||||
|
||||
bottomUiContainerPreFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val insertIndex = it.patternMatch!!.startIndex + 1
|
||||
|
||||
addInstruction(
|
||||
insertIndex,
|
||||
"invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->lithoSnackBarLoaded()V"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
bottomUiContainerThemeFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val startIndex = it.patternMatch!!.startIndex
|
||||
val appThemeIndex = startIndex + 1
|
||||
val darkThemeIndex = startIndex + 2
|
||||
val insertIndex = startIndex + 3
|
||||
|
||||
val appThemeRegister =
|
||||
getInstruction<OneRegisterInstruction>(appThemeIndex).registerA
|
||||
val darkThemeRegister =
|
||||
getInstruction<OneRegisterInstruction>(darkThemeIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
insertIndex, """
|
||||
invoke-static {v$appThemeRegister, v$darkThemeRegister}, $EXTENSION_CLASS_DESCRIPTOR->invertSnackBarTheme(Ljava/lang/Enum;Ljava/lang/Enum;)Ljava/lang/Enum;
|
||||
move-result-object v$appThemeRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableMethod.setBackground(index: Int, register: Int) =
|
||||
addInstruction(
|
||||
index,
|
||||
"invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->setLithoSnackBarBackground(Landroid/view/View;)V"
|
||||
)
|
||||
|
||||
lithoSnackBarFingerprint.methodOrThrow().apply {
|
||||
val backGroundColorIndex = indexOfBackGroundColor(this)
|
||||
val viewRegister =
|
||||
getInstruction<FiveRegisterInstruction>(backGroundColorIndex).registerC
|
||||
val colorRegister =
|
||||
getInstruction<FiveRegisterInstruction>(backGroundColorIndex).registerD
|
||||
|
||||
replaceInstruction(
|
||||
backGroundColorIndex,
|
||||
"invoke-static {v$viewRegister, v$colorRegister}, $EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"setLithoSnackBarBackgroundColor(Landroid/widget/FrameLayout;I)V"
|
||||
)
|
||||
setBackground(backGroundColorIndex + 2, viewRegister)
|
||||
|
||||
implementation!!.instructions
|
||||
.withIndex()
|
||||
.filter { (_, instruction) ->
|
||||
instruction.opcode == Opcode.CHECK_CAST &&
|
||||
(instruction as? ReferenceInstruction)?.reference?.toString() == "Landroid/widget/FrameLayout;"
|
||||
}
|
||||
.map { (index, _) -> index }
|
||||
.reversed()
|
||||
.forEach { index ->
|
||||
val register =
|
||||
getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
setBackground(index + 1, register)
|
||||
}
|
||||
|
||||
findMethodOrThrow(definingClass).apply {
|
||||
val contextIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
getReference<FieldReference>()?.type == "Landroid/content/Context;"
|
||||
}
|
||||
val contextRegister =
|
||||
getInstruction<TwoRegisterInstruction>(contextIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
contextIndex, """
|
||||
invoke-static {v$contextRegister}, $EXTENSION_CLASS_DESCRIPTOR->invertSnackBarTheme(Landroid/content/Context;)Landroid/content/Context;
|
||||
move-result-object v$contextRegister
|
||||
"""
|
||||
)
|
||||
|
||||
val viewIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
getReference<FieldReference>()?.type == "Landroid/widget/FrameLayout;"
|
||||
}
|
||||
val viewRegister =
|
||||
getInstruction<TwoRegisterInstruction>(viewIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
viewIndex,
|
||||
"invoke-static {v$viewRegister}, $EXTENSION_CLASS_DESCRIPTOR->hideLithoSnackBar(Landroid/widget/FrameLayout;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
addDrawableColorHook("$EXTENSION_CLASS_DESCRIPTOR->getLithoColor(I)I", true)
|
||||
addSpanFilter(FILTER_CLASS_DESCRIPTOR)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
val snackBarComponentsPatch = resourcePatch(
|
||||
SNACK_BAR_COMPONENTS.title,
|
||||
SNACK_BAR_COMPONENTS.summary,
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
snackBarComponentsBytecodePatch,
|
||||
)
|
||||
|
||||
val ytBackgroundColorDark = "@color/yt_black3"
|
||||
val ytBackgroundColorLight = "@color/yt_white3"
|
||||
|
||||
val availableDarkTheme = mapOf(
|
||||
"YouTube Dark" to ytBackgroundColorDark,
|
||||
"Amoled Black" to "@android:color/black",
|
||||
"Catppuccin (Mocha)" to "#FF181825",
|
||||
"Dark Pink" to "#FF290025",
|
||||
"Dark Blue" to "#FF001029",
|
||||
"Dark Green" to "#FF002905",
|
||||
"Dark Yellow" to "#FF282900",
|
||||
"Dark Orange" to "#FF291800",
|
||||
"Dark Red" to "#FF290000",
|
||||
)
|
||||
|
||||
val availableLightTheme = mapOf(
|
||||
"YouTube Light" to ytBackgroundColorLight,
|
||||
"White" to "@android:color/white",
|
||||
"Catppuccin (Latte)" to "#FFE6E9EF",
|
||||
"Light Pink" to "#FFFCCFF3",
|
||||
"Light Blue" to "#FFD1E0FF",
|
||||
"Light Green" to "#FFCCFFCC",
|
||||
"Light Yellow" to "#FFFDFFCC",
|
||||
"Light Orange" to "#FFFFE6CC",
|
||||
"Light Red" to "#FFFFD6D6",
|
||||
)
|
||||
|
||||
val cornerRadiusOption = stringOption(
|
||||
key = "cornerRadius",
|
||||
default = "8.0dip",
|
||||
title = "Corner radius",
|
||||
description = "Specify a corner radius for the snack bar.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
val darkThemeBackgroundColor = stringOption(
|
||||
key = "darkThemeBackgroundColor",
|
||||
default = ytBackgroundColorDark,
|
||||
values = availableDarkTheme,
|
||||
title = "Dark theme background color",
|
||||
description = "Specify a background color for the snack bar. You can specify hex color (#AARRGGBB) or color resource reference.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
val lightThemeBackgroundColor = stringOption(
|
||||
key = "lightThemeBackgroundColor",
|
||||
default = ytBackgroundColorLight,
|
||||
values = availableLightTheme,
|
||||
title = "Light theme background color",
|
||||
description = "Specify a background color for the snack bar. You can specify hex color (#AARRGGBB) or color resource reference.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
val strokeColorOption = stringOption(
|
||||
key = "strokeColor",
|
||||
default = "",
|
||||
values = mapOf(
|
||||
"None" to "",
|
||||
"Blue" to "?attr/ytThemedBlue",
|
||||
"Chip" to "?attr/ytChipBackground"
|
||||
),
|
||||
title = "Stroke color",
|
||||
description = "Specify a stroke color for the snack bar. You can specify hex color.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
execute {
|
||||
|
||||
// Check patch options first.
|
||||
val cornerRadius = cornerRadiusOption
|
||||
.valueOrThrow()
|
||||
val darkThemeColor = darkThemeBackgroundColor
|
||||
.valueOrThrow()
|
||||
val lightThemeColor = lightThemeBackgroundColor
|
||||
.valueOrThrow()
|
||||
val strokeColor = strokeColorOption
|
||||
.valueOrThrow()
|
||||
|
||||
val snackBarColorAttr = "snackBarColor"
|
||||
val snackBarColorAttrReference = "?attr/$snackBarColorAttr"
|
||||
val snackBarColorDark = "revanced_snack_bar_color_dark"
|
||||
val snackBarColorDarkReference = "@color/$snackBarColorDark"
|
||||
val snackBarColorLight = "revanced_snack_bar_color_light"
|
||||
val snackBarColorLightReference = "@color/$snackBarColorLight"
|
||||
|
||||
document("res/values/colors.xml").use { document ->
|
||||
mapOf(
|
||||
snackBarColorDark to darkThemeColor,
|
||||
snackBarColorLight to lightThemeColor,
|
||||
).forEach { (k, v) ->
|
||||
val colorElement = document.createElement("color")
|
||||
|
||||
colorElement.setAttribute("name", k)
|
||||
colorElement.textContent = v
|
||||
|
||||
document.getElementsByTagName("resources").item(0)
|
||||
.appendChild(colorElement)
|
||||
}
|
||||
}
|
||||
|
||||
document("res/values/attrs.xml").use { document ->
|
||||
(document.getElementsByTagName("resources").item(0) as Element).appendChild(
|
||||
document.createElement("attr").apply {
|
||||
setAttribute("format", "reference|color")
|
||||
setAttribute("name", snackBarColorAttr)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
document("res/values/styles.xml").use { document ->
|
||||
mapOf(
|
||||
"Base.Theme.YouTube.Dark" to snackBarColorLightReference,
|
||||
"Base.Theme.YouTube.Light" to snackBarColorDarkReference,
|
||||
).forEach { (styleName, colorName) ->
|
||||
val snackBarColorNode = document.createElement("item")
|
||||
snackBarColorNode.setAttribute("name", snackBarColorAttr)
|
||||
snackBarColorNode.appendChild(document.createTextNode(colorName))
|
||||
|
||||
document.childNodes.findElementByAttributeValueOrThrow(
|
||||
"name",
|
||||
styleName,
|
||||
).appendChild(snackBarColorNode)
|
||||
}
|
||||
}
|
||||
|
||||
document("res/drawable/snackbar_rounded_corners_background.xml").use { document ->
|
||||
document.getNode("corners").apply {
|
||||
arrayOf(
|
||||
"android:bottomLeftRadius",
|
||||
"android:bottomRightRadius",
|
||||
"android:topLeftRadius",
|
||||
"android:topRightRadius",
|
||||
).forEach {
|
||||
attributes.getNamedItem(it).nodeValue = cornerRadius
|
||||
}
|
||||
}
|
||||
document.getNode("solid").apply {
|
||||
attributes.getNamedItem("android:color").nodeValue = snackBarColorAttrReference
|
||||
}
|
||||
if (!strokeColor.isEmpty()) {
|
||||
(document.getElementsByTagName("shape").item(0) as Element).appendChild(
|
||||
document.createElement("stroke").apply {
|
||||
setAttribute("android:width", "1.0dip")
|
||||
setAttribute("android:color", strokeColor)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region add settings
|
||||
|
||||
addPreference(
|
||||
arrayOf(
|
||||
"PREFERENCE_SCREEN: GENERAL",
|
||||
"SETTINGS: SNACK_BAR_COMPONENTS"
|
||||
),
|
||||
SNACK_BAR_COMPONENTS
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
}
|
||||
}
|
@ -2,12 +2,14 @@ package app.revanced.patches.youtube.general.spoofappversion
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.shared.spoof.appversion.baseSpoofAppVersionPatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR
|
||||
import app.revanced.patches.youtube.utils.fix.cairo.cairoFragmentPatch
|
||||
import app.revanced.patches.youtube.utils.indexOfGetDrawableInstruction
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_APP_VERSION
|
||||
@ -23,6 +25,7 @@ import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.utils.toolBarButtonFingerprint
|
||||
import app.revanced.util.appendAppVersion
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
@ -69,6 +72,13 @@ private val spoofAppVersionBytecodePatch = bytecodePatch(
|
||||
""", ExternalLabel("ignore", getInstruction(jumpIndex))
|
||||
)
|
||||
}
|
||||
|
||||
findMethodOrThrow(PATCH_STATUS_CLASS_DESCRIPTOR) {
|
||||
name == "SpoofAppVersionDefaultString"
|
||||
}.replaceInstruction(
|
||||
0,
|
||||
"const-string v0, \"18.38.45\""
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import app.revanced.patches.youtube.utils.castbutton.hookToolBarCastButton
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.TOOLBAR_COMPONENTS
|
||||
import app.revanced.patches.youtube.utils.playservice.is_19_46_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.utils.resourceid.actionBarRingoBackground
|
||||
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
|
||||
@ -265,7 +266,11 @@ val toolBarComponentsPatch = bytecodePatch(
|
||||
// region patch for hide search term thumbnail
|
||||
|
||||
createSearchSuggestionsFingerprint.methodOrThrow().apply {
|
||||
val relativeIndex = indexOfFirstLiteralInstructionOrThrow(40L)
|
||||
val literal = if (is_19_46_or_greater)
|
||||
32L
|
||||
else
|
||||
40L
|
||||
val relativeIndex = indexOfFirstLiteralInstructionOrThrow(literal)
|
||||
val replaceIndex = indexOfFirstInstructionReversedOrThrow(relativeIndex) {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.toString() == "Landroid/widget/ImageView;->setVisibility(I)V"
|
||||
|
@ -27,7 +27,7 @@ val sharedThemePatch = resourcePatch(
|
||||
document(attrsResourceFile).use { document ->
|
||||
(document.getElementsByTagName("resources").item(0) as Element).appendChild(
|
||||
document.createElement("attr").apply {
|
||||
setAttribute("format", "reference")
|
||||
setAttribute("format", "reference|color")
|
||||
setAttribute("name", SPLASH_SCREEN_COLOR_NAME)
|
||||
}
|
||||
)
|
||||
@ -49,7 +49,7 @@ val sharedThemePatch = resourcePatch(
|
||||
setAttribute(
|
||||
"name",
|
||||
when (pathIndex) {
|
||||
0 -> "splashScreenColor"
|
||||
0 -> SPLASH_SCREEN_COLOR_NAME
|
||||
1 -> "android:windowSplashScreenBackground"
|
||||
else -> "null"
|
||||
}
|
||||
@ -59,8 +59,10 @@ val sharedThemePatch = resourcePatch(
|
||||
document.createTextNode(
|
||||
when (pathIndex) {
|
||||
0 -> when (nodeAttributeName) {
|
||||
"Base.Theme.YouTube.Launcher.Dark" -> "@color/yt_black1"
|
||||
"Base.Theme.YouTube.Launcher.Light" -> "@color/yt_white1"
|
||||
"Base.Theme.YouTube.Launcher.Dark",
|
||||
"Base.Theme.YouTube.Launcher.Cairo.Dark" -> "@color/yt_black1"
|
||||
"Base.Theme.YouTube.Launcher.Light",
|
||||
"Base.Theme.YouTube.Launcher.Cairo.Light" -> "@color/yt_white1"
|
||||
else -> "null"
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,7 @@ val themePatch = resourcePatch(
|
||||
values = availableDarkTheme,
|
||||
title = "Dark theme background color",
|
||||
description = "Can be a hex color (#AARRGGBB) or a color resource reference.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
val lightThemeBackgroundColor = stringOption(
|
||||
@ -62,6 +63,7 @@ val themePatch = resourcePatch(
|
||||
values = availableLightTheme,
|
||||
title = "Light theme background color",
|
||||
description = "Can be a hex color (#AARRGGBB) or a color resource reference.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
execute {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.youtube.misc.openlinksdirectly
|
||||
package app.revanced.patches.youtube.misc.openlinks.directly
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.or
|
@ -1,11 +1,11 @@
|
||||
package app.revanced.patches.youtube.misc.openlinksdirectly
|
||||
package app.revanced.patches.youtube.misc.openlinks.directly
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.MISC_PATH
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.ENABLE_OPEN_LINKS_DIRECTLY
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.BYPASS_URL_REDIRECTS
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
@ -17,8 +17,8 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
@Suppress("unused")
|
||||
val openLinksDirectlyPatch = bytecodePatch(
|
||||
ENABLE_OPEN_LINKS_DIRECTLY.title,
|
||||
ENABLE_OPEN_LINKS_DIRECTLY.summary,
|
||||
BYPASS_URL_REDIRECTS.title,
|
||||
BYPASS_URL_REDIRECTS.summary,
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
@ -40,7 +40,7 @@ val openLinksDirectlyPatch = bytecodePatch(
|
||||
|
||||
replaceInstruction(
|
||||
insertIndex,
|
||||
"invoke-static {v$insertRegister}, $MISC_PATH/OpenLinksDirectlyPatch;->enableBypassRedirect(Ljava/lang/String;)Landroid/net/Uri;"
|
||||
"invoke-static {v$insertRegister}, $MISC_PATH/OpenLinksDirectlyPatch;->parseRedirectUri(Ljava/lang/String;)Landroid/net/Uri;"
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -49,9 +49,9 @@ val openLinksDirectlyPatch = bytecodePatch(
|
||||
|
||||
addPreference(
|
||||
arrayOf(
|
||||
"SETTINGS: ENABLE_OPEN_LINKS_DIRECTLY"
|
||||
"SETTINGS: BYPASS_URL_REDIRECTS"
|
||||
),
|
||||
ENABLE_OPEN_LINKS_DIRECTLY
|
||||
BYPASS_URL_REDIRECTS
|
||||
)
|
||||
|
||||
// endregion
|
@ -1,11 +1,11 @@
|
||||
package app.revanced.patches.youtube.misc.externalbrowser
|
||||
package app.revanced.patches.youtube.misc.openlinks.externally
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.shared.transformation.transformInstructionsPatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.MISC_PATH
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.ENABLE_EXTERNAL_BROWSER
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.OPEN_LINKS_EXTERNALLY
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
@ -14,8 +14,8 @@ import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
|
||||
@Suppress("unused")
|
||||
val openLinksExternallyPatch = bytecodePatch(
|
||||
ENABLE_EXTERNAL_BROWSER.title,
|
||||
ENABLE_EXTERNAL_BROWSER.summary,
|
||||
OPEN_LINKS_EXTERNALLY.title,
|
||||
OPEN_LINKS_EXTERNALLY.summary,
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
@ -36,7 +36,7 @@ val openLinksExternallyPatch = bytecodePatch(
|
||||
mutableMethod.addInstructions(
|
||||
intentStringIndex + 1,
|
||||
"""
|
||||
invoke-static {v$register}, $MISC_PATH/ExternalBrowserPatch;->enableExternalBrowser(Ljava/lang/String;)Ljava/lang/String;
|
||||
invoke-static {v$register}, $MISC_PATH/OpenLinksExternallyPatch;->openLinksExternally(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$register
|
||||
""",
|
||||
)
|
||||
@ -51,9 +51,9 @@ val openLinksExternallyPatch = bytecodePatch(
|
||||
|
||||
addPreference(
|
||||
arrayOf(
|
||||
"SETTINGS: ENABLE_EXTERNAL_BROWSER"
|
||||
"SETTINGS: OPEN_LINKS_EXTERNALLY"
|
||||
),
|
||||
ENABLE_EXTERNAL_BROWSER
|
||||
OPEN_LINKS_EXTERNALLY
|
||||
)
|
||||
|
||||
// endregion
|
@ -1,16 +1,37 @@
|
||||
package app.revanced.patches.youtube.player.action
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.booleanOption
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.shared.litho.addLithoFilter
|
||||
import app.revanced.patches.shared.litho.emptyComponentsFingerprint
|
||||
import app.revanced.patches.shared.litho.lithoFilterPatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_ACTION_BUTTONS
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.video.information.videoInformationPatch
|
||||
import app.revanced.util.Utils.trimIndentMultiline
|
||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
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
|
||||
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
|
||||
|
||||
private const val FILTER_CLASS_DESCRIPTOR =
|
||||
"$COMPONENTS_PATH/ActionButtonsFilter;"
|
||||
private const val ACTION_BUTTONS_CLASS_DESCRIPTOR =
|
||||
"$PLAYER_PATH/ActionButtonsPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val actionButtonsPatch = bytecodePatch(
|
||||
@ -22,18 +43,73 @@ val actionButtonsPatch = bytecodePatch(
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
lithoFilterPatch,
|
||||
videoInformationPatch,
|
||||
)
|
||||
|
||||
val hideActionButtonByIndex by booleanOption(
|
||||
key = "hideActionButtonByIndex",
|
||||
default = false,
|
||||
title = "Hide action buttons by index",
|
||||
description = """
|
||||
Add an option to hide action buttons by index.
|
||||
|
||||
This setting is still experimental, so use it only for debugging purposes.
|
||||
""".trimIndentMultiline(),
|
||||
required = true
|
||||
)
|
||||
|
||||
execute {
|
||||
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
|
||||
|
||||
var settingArray = arrayOf(
|
||||
"PREFERENCE_SCREEN: PLAYER",
|
||||
"SETTINGS: HIDE_ACTION_BUTTONS"
|
||||
)
|
||||
|
||||
if (hideActionButtonByIndex == true) {
|
||||
componentListFingerprint.methodOrThrow(emptyComponentsFingerprint).apply {
|
||||
val conversionContextToStringMethod =
|
||||
findMethodOrThrow(parameters[1].type) {
|
||||
name == "toString"
|
||||
}
|
||||
val identifierReference = with (conversionContextToStringMethod) {
|
||||
val identifierStringIndex =
|
||||
indexOfFirstStringInstructionOrThrow(", identifierProperty=")
|
||||
val identifierStringAppendIndex =
|
||||
indexOfFirstInstructionOrThrow(identifierStringIndex, Opcode.INVOKE_VIRTUAL)
|
||||
val identifierStringAppendIndexRegister = getInstruction<FiveRegisterInstruction>(identifierStringAppendIndex).registerD
|
||||
val identifierAppendIndex =
|
||||
indexOfFirstInstructionOrThrow(identifierStringAppendIndex + 1, Opcode.INVOKE_VIRTUAL)
|
||||
val identifierRegister = getInstruction<FiveRegisterInstruction>(identifierAppendIndex).registerD
|
||||
val identifierIndex = indexOfFirstInstructionReversedOrThrow(identifierAppendIndex) {
|
||||
opcode == Opcode.IGET_OBJECT &&
|
||||
getReference<FieldReference>()?.type == "Ljava/lang/String;" &&
|
||||
(this as? TwoRegisterInstruction)?.registerA == identifierRegister
|
||||
}
|
||||
getInstruction<ReferenceInstruction>(identifierIndex).reference
|
||||
}
|
||||
|
||||
val listIndex = implementation!!.instructions.lastIndex
|
||||
val listRegister = getInstruction<OneRegisterInstruction>(listIndex).registerA
|
||||
val identifierRegister = listRegister + 1
|
||||
|
||||
addInstructionsAtControlFlowLabel(
|
||||
listIndex, """
|
||||
move-object/from16 v$identifierRegister, p2
|
||||
iget-object v$identifierRegister, v$identifierRegister, $identifierReference
|
||||
invoke-static {v$listRegister, v$identifierRegister}, $ACTION_BUTTONS_CLASS_DESCRIPTOR->hideActionButtonByIndex(Ljava/util/List;Ljava/lang/String;)Ljava/util/List;
|
||||
move-result-object v$listRegister
|
||||
"""
|
||||
)
|
||||
|
||||
settingArray += "SETTINGS: HIDE_BUTTONS_BY_INDEX"
|
||||
}
|
||||
}
|
||||
|
||||
// region add settings
|
||||
|
||||
addPreference(
|
||||
arrayOf(
|
||||
"PREFERENCE_SCREEN: PLAYER",
|
||||
"SETTINGS: HIDE_ACTION_BUTTONS"
|
||||
),
|
||||
settingArray,
|
||||
HIDE_ACTION_BUTTONS
|
||||
)
|
||||
|
||||
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.youtube.player.action
|
||||
|
||||
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.reference.MethodReference
|
||||
|
||||
internal val componentListFingerprint = legacyFingerprint(
|
||||
name = "componentListFingerprint",
|
||||
returnType = "Ljava/util/List;",
|
||||
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
|
||||
customFingerprint = { method, _ ->
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_STATIC &&
|
||||
getReference<MethodReference>()?.name == "nCopies"
|
||||
} >= 0
|
||||
}
|
||||
)
|
@ -23,6 +23,8 @@ import app.revanced.patches.youtube.utils.extension.Constants.SPANS_PATH
|
||||
import app.revanced.patches.youtube.utils.fix.suggestedvideoendscreen.suggestedVideoEndScreenPatch
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.PLAYER_COMPONENTS
|
||||
import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch
|
||||
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.resourceid.darkBackground
|
||||
import app.revanced.patches.youtube.utils.resourceid.fadeDurationFast
|
||||
import app.revanced.patches.youtube.utils.resourceid.scrimOverlay
|
||||
@ -278,9 +280,15 @@ val playerComponentsPatch = bytecodePatch(
|
||||
speedOverlayPatch,
|
||||
suggestedVideoEndScreenPatch,
|
||||
videoInformationPatch,
|
||||
versionCheckPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
var settingArray = arrayOf(
|
||||
"PREFERENCE_SCREEN: PLAYER",
|
||||
"SETTINGS: PLAYER_COMPONENTS"
|
||||
)
|
||||
|
||||
fun MutableMethod.getAllLiteralComponent(
|
||||
startIndex: Int,
|
||||
endIndex: Int
|
||||
@ -563,30 +571,34 @@ val playerComponentsPatch = bytecodePatch(
|
||||
)
|
||||
}
|
||||
|
||||
youtubeControlsOverlayFingerprint.methodOrThrow().apply {
|
||||
val insertIndex =
|
||||
indexOfFirstLiteralInstructionOrThrow(seekUndoEduOverlayStub)
|
||||
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
if (!is_20_02_or_greater) {
|
||||
youtubeControlsOverlayFingerprint.methodOrThrow().apply {
|
||||
val insertIndex =
|
||||
indexOfFirstLiteralInstructionOrThrow(seekUndoEduOverlayStub)
|
||||
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
val onClickListenerIndex = indexOfFirstInstructionOrThrow(insertIndex) {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "setOnClickListener"
|
||||
}
|
||||
val constComponent = getFirstLiteralComponent(insertIndex, onClickListenerIndex - 1)
|
||||
val onClickListenerIndex = indexOfFirstInstructionOrThrow(insertIndex) {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "setOnClickListener"
|
||||
}
|
||||
val constComponent = getFirstLiteralComponent(insertIndex, onClickListenerIndex - 1)
|
||||
|
||||
if (constComponent.isNotEmpty()) {
|
||||
addInstruction(
|
||||
onClickListenerIndex + 2,
|
||||
constComponent
|
||||
if (constComponent.isNotEmpty()) {
|
||||
addInstruction(
|
||||
onClickListenerIndex + 2,
|
||||
constComponent
|
||||
)
|
||||
}
|
||||
addInstructionsWithLabels(
|
||||
insertIndex, """
|
||||
invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->hideSeekUndoMessage()Z
|
||||
move-result v$insertRegister
|
||||
if-nez v$insertRegister, :default
|
||||
""", ExternalLabel("default", getInstruction(onClickListenerIndex + 1))
|
||||
)
|
||||
|
||||
settingArray += "SETTINGS: HIDE_SEEK_UNDO_MESSAGE"
|
||||
}
|
||||
addInstructionsWithLabels(
|
||||
insertIndex, """
|
||||
invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->hideSeekUndoMessage()Z
|
||||
move-result v$insertRegister
|
||||
if-nez v$insertRegister, :default
|
||||
""", ExternalLabel("default", getInstruction(onClickListenerIndex + 1))
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
@ -652,10 +664,7 @@ val playerComponentsPatch = bytecodePatch(
|
||||
// region add settings
|
||||
|
||||
addPreference(
|
||||
arrayOf(
|
||||
"PREFERENCE_SCREEN: PLAYER",
|
||||
"SETTINGS: PLAYER_COMPONENTS"
|
||||
),
|
||||
settingArray,
|
||||
PLAYER_COMPONENTS
|
||||
)
|
||||
|
||||
|
@ -16,17 +16,17 @@ internal val engagementPanelTitleFingerprint = legacyFingerprint(
|
||||
}
|
||||
)
|
||||
|
||||
internal val engagementPanelTitleParentFingerprint = legacyFingerprint(
|
||||
name = "engagementPanelTitleParentFingerprint",
|
||||
strings = listOf("[EngagementPanelTitleHeader] Cannot remove action buttons from header as the child count is out of sync. Buttons to remove exceed current header child count.")
|
||||
)
|
||||
|
||||
internal fun indexOfContentDescriptionInstruction(method: Method) =
|
||||
method.indexOfFirstInstructionReversed {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "setContentDescription"
|
||||
}
|
||||
|
||||
internal val engagementPanelTitleParentFingerprint = legacyFingerprint(
|
||||
name = "engagementPanelTitleParentFingerprint",
|
||||
strings = listOf("[EngagementPanelTitleHeader] Cannot remove action buttons from header as the child count is out of sync. Buttons to remove exceed current header child count.")
|
||||
)
|
||||
|
||||
/**
|
||||
* This fingerprint is compatible with YouTube v18.35.xx~
|
||||
* Nonetheless, the patch works in YouTube v19.02.xx~
|
||||
|
@ -7,6 +7,7 @@ import app.revanced.patches.youtube.utils.resourceid.quickActionsElementContaine
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
internal val broadcastReceiverFingerprint = legacyFingerprint(
|
||||
name = "broadcastReceiverFingerprint",
|
||||
@ -45,12 +46,15 @@ internal val playerTitleViewFingerprint = legacyFingerprint(
|
||||
literals = listOf(playerVideoTitleView),
|
||||
)
|
||||
|
||||
internal val quickActionsElementFingerprint = legacyFingerprint(
|
||||
name = "quickActionsElementFingerprint",
|
||||
internal val quickActionsElementSyntheticFingerprint = legacyFingerprint(
|
||||
name = "quickActionsElementSyntheticFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("Landroid/view/View;"),
|
||||
literals = listOf(quickActionsElementContainer),
|
||||
customFingerprint = { _, classDef ->
|
||||
AccessFlags.SYNTHETIC.isSet(classDef.accessFlags)
|
||||
}
|
||||
)
|
||||
|
||||
internal val relatedEndScreenResultsFingerprint = legacyFingerprint(
|
||||
|
@ -19,6 +19,7 @@ import app.revanced.patches.youtube.utils.extension.Constants.PATCH_STATUS_CLASS
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_CLASS_DESCRIPTOR
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH
|
||||
import app.revanced.patches.youtube.utils.fullscreen.fullscreenButtonHookPatch
|
||||
import app.revanced.patches.youtube.utils.indexOfFocusableInTouchModeInstruction
|
||||
import app.revanced.patches.youtube.utils.layoutConstructorFingerprint
|
||||
import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.FULLSCREEN_COMPONENTS
|
||||
@ -101,7 +102,6 @@ val fullscreenComponentsPatch = bytecodePatch(
|
||||
"invoke-static {v$targetRegister}, " +
|
||||
"$PLAYER_CLASS_DESCRIPTOR->disableEngagementPanels(Landroidx/coordinatorlayout/widget/CoordinatorLayout;)V"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
playerTitleViewFingerprint.methodOrThrow().apply {
|
||||
@ -190,7 +190,7 @@ val fullscreenComponentsPatch = bytecodePatch(
|
||||
|
||||
// region patch for quick actions
|
||||
|
||||
quickActionsElementFingerprint.methodOrThrow().apply {
|
||||
quickActionsElementSyntheticFingerprint.methodOrThrow().apply {
|
||||
val containerCalls = implementation!!.instructions.withIndex()
|
||||
.filter { instruction ->
|
||||
(instruction.value as? WideLiteralInstruction)?.wideLiteral == quickActionsElementContainer
|
||||
@ -219,11 +219,13 @@ val fullscreenComponentsPatch = bytecodePatch(
|
||||
// region patch for compact control overlay
|
||||
|
||||
youtubeControlsOverlayFingerprint.methodOrThrow().apply {
|
||||
val targetIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "setFocusableInTouchMode"
|
||||
val targetIndex = indexOfFocusableInTouchModeInstruction(this)
|
||||
val walkerIndex = indexOfFirstInstructionOrThrow(targetIndex) {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_STATIC &&
|
||||
reference?.returnType == "Z" &&
|
||||
reference.parameterTypes.size == 1
|
||||
}
|
||||
val walkerIndex = indexOfFirstInstructionOrThrow(targetIndex, Opcode.INVOKE_STATIC)
|
||||
|
||||
val walkerMethod = getWalkerMethod(walkerIndex)
|
||||
walkerMethod.apply {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user