Merge branch 'dev' into revanced-extended

This commit is contained in:
inotia00 2025-01-22 17:23:55 +09:00
commit 9146d9283b
213 changed files with 6533 additions and 2371 deletions

View File

@ -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 | | `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 | | `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 | | `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 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 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 | | `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 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 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 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 | | `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 | | `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 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 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 | | `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 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 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 | | `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 | | `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 | | `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 | | `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 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 | | `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 | | `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 | | `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 | | `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 | | `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 | | `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 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 | | `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 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 | | `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 | | `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 | | `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 | | `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 | | `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 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 | | `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 | | 💊 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 | | `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 ~ 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 ~ 8.02.53 |
| `Certificate spoof` | Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate. | 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 ~ 8.02.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 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 ~ 7.25.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 ~ 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 ~ 8.02.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 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 ~ 7.25.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 ~ 7.25.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 ~ 7.25.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 ~ 7.25.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 ~ 7.25.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 ~ 7.25.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 ~ 7.25.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 options to enable the OPUS audio codec if the player response includes it. | 6.20.51 ~ 7.25.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 ~ 7.25.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 ~ 7.25.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 ~ 7.25.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 ~ 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 ~ 8.02.53 |
| `Hide account components` | Adds options to hide components related to the account menu. | 6.20.51 ~ 7.25.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 ~ 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 ~ 8.02.53 |
| `Hide ads` | Adds options to hide ads. | 6.20.51 ~ 7.25.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 ~ 7.25.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 ~ 7.25.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 ~ 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 ~ 8.02.53 |
| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 6.20.51 ~ 7.25.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 ~ 7.25.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 ~ 7.25.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 ~ 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 ~ 8.02.53 |
| `Restore old style library shelf` | Adds an option to return the Library tab to the old style. | 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 ~ 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 ~ 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 ~ 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 ~ 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 ~ 8.02.53 |
| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | 6.20.51 ~ 7.25.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 ~ 7.25.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 ~ 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 ~ 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 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 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 | | `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 ~ 7.25.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 ~ 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 ~ 8.02.53 |
| `Visual preferences icons for YouTube Music` | Adds icons to specific preferences in the settings. | 6.20.51 ~ 7.25.53 | | `Visual preferences icons for YouTube Music` | Adds icons to specific preferences in the settings. | 6.20.51 ~ 8.02.53 |
</details> </details>
### [📦 `com.reddit.frontpage`](https://play.google.com/store/apps/details?id=com.reddit.frontpage) ### [📦 `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 | | 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:| |:--------:|:--------------:|:-----------------:|
| `Change package name` | Changes the package name for Reddit to the name specified in patch options. | ALL | | `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 | | `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 Recently Visited shelf` | Adds an option to hide the Recently Visited shelf in the sidebar. | ALL |
| `Hide ads` | Adds options to hide ads. | 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 | | `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 | | `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 | | `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 | | `Settings for Reddit` | Applies mandatory patches to implement ReVanced Extended settings into the application. | ALL |
</details> </details>
@ -181,7 +183,8 @@ Example:
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []

View File

@ -12,6 +12,7 @@ import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import app.revanced.extension.music.settings.Settings; import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.shared.utils.ResourceUtils;
/** /**
* @noinspection ALL * @noinspection ALL
@ -19,6 +20,18 @@ import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class GeneralPatch { 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 // region [Change start page] patch
public static String changeStartPage(final String browseId) { public static String changeStartPage(final String browseId) {

View File

@ -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.settings.Settings;
import app.revanced.extension.music.shared.VideoInformation; import app.revanced.extension.music.shared.VideoInformation;
import app.revanced.extension.music.utils.VideoUtils; 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.Logger;
import app.revanced.extension.shared.utils.Utils;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class AlbumMusicVideoPatch { public class AlbumMusicVideoPatch {
@ -98,8 +100,17 @@ public class AlbumMusicVideoPatch {
if (request == null) { if (request == null) {
return; 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(); String songId = request.getStream();
if (songId == null) { if (songId == null) {
Logger.printDebug(() -> "Official song not found, videoId: " + videoId);
return; return;
} }
synchronized (lastVideoIds) { synchronized (lastVideoIds) {

View File

@ -12,8 +12,7 @@ import org.json.JSONObject;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.util.HashMap; import java.util.LinkedHashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -25,38 +24,21 @@ import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.shared.utils.Utils;
public class PipedRequester { public class PipedRequester {
/** private static final long MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 4 * 1000; // 4 seconds
* 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
@GuardedBy("itself") @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") @SuppressLint("ObsoleteSdkInt")
public static void fetchRequestIfNeeded(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) { public static void fetchRequestIfNeeded(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) {
synchronized (cache) { 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)) { if (!cache.containsKey(videoId)) {
PipedRequester pipedRequester = new PipedRequester(videoId, playlistId, playlistIndex); PipedRequester pipedRequester = new PipedRequester(videoId, playlistId, playlistIndex);
cache.put(videoId, pipedRequester); cache.put(videoId, pipedRequester);
@ -85,7 +67,7 @@ public class PipedRequester {
private static JSONObject send(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) { private static JSONObject send(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) {
final long startTime = System.currentTimeMillis(); final long startTime = System.currentTimeMillis();
Logger.printDebug(() -> "Fetching piped instances (videoId: '" + videoId + Logger.printDebug(() -> "Fetching piped instances (videoId: '" + videoId +
"', playlistId: '" + playlistId + "', playlistIndex: '" + playlistIndex + "'"); "', playlistId: '" + playlistId + "', playlistIndex: '" + playlistIndex + "')");
try { try {
HttpURLConnection connection = PipedRoutes.getPlaylistConnectionFromRoute(playlistId); HttpURLConnection connection = PipedRoutes.getPlaylistConnectionFromRoute(playlistId);
@ -121,6 +103,8 @@ public class PipedRequester {
if (songId.isEmpty()) { if (songId.isEmpty()) {
handleConnectionError("Url is empty!"); handleConnectionError("Url is empty!");
} else if (!songId.equals(videoId)) { } else if (!songId.equals(videoId)) {
Logger.printDebug(() -> "Video found (videoId: '" + videoId +
"', songId: '" + songId + "')");
return songId; return songId;
} }
} catch (JSONException e) { } catch (JSONException e) {
@ -145,26 +129,12 @@ public class PipedRequester {
/** /**
* Time this instance and the fetch future was created. * Time this instance and the fetch future was created.
*/ */
private final long timeFetched;
private final String videoId;
private final Future<String> future; private final Future<String> future;
private PipedRequester(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) { 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)); 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. * @return if the fetch call has completed.
*/ */

View File

@ -1,5 +1,6 @@
package app.revanced.extension.music.patches.navigation; 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 static app.revanced.extension.shared.utils.Utils.hideViewUnderCondition;
import android.graphics.Color; 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.patches.utils.PatchStatus;
import app.revanced.extension.music.settings.Settings; import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.shared.utils.ResourceUtils; import app.revanced.extension.shared.utils.ResourceUtils;
import app.revanced.extension.shared.utils.Utils;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class NavigationPatch { public class NavigationPatch {
@ -20,10 +22,18 @@ public class NavigationPatch {
public static Enum<?> lastPivotTab; public static Enum<?> lastPivotTab;
public static int enableBlackNavigationBar() { public static int enableCustomNavigationBarColor() {
return Settings.ENABLE_BLACK_NAVIGATION_BAR.get() try {
? Color.BLACK if (Settings.ENABLE_CUSTOM_NAVIGATION_BAR_COLOR.get()) {
: colorGrey12; 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) { public static void hideNavigationLabel(TextView textview) {

View File

@ -12,10 +12,10 @@ import app.revanced.extension.shared.utils.ResourceUtils;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class DrawableColorPatch { public class DrawableColorPatch {
private static final int[] DARK_VALUES = { private static final int[] DARK_COLORS = {
-14606047, // comments box background 0xFF212121, // comments box background
-16579837, // button container background in album 0xFF030303, // button container background in album
-16777216, // button container background in playlist 0xFF000000, // button container background in playlist
}; };
// background colors // background colors
@ -26,10 +26,10 @@ public class DrawableColorPatch {
private static final int elementsContainerIdentifier = private static final int elementsContainerIdentifier =
ResourceUtils.getIdIdentifier("elements_container"); ResourceUtils.getIdIdentifier("elements_container");
public static int getLithoColor(int originalValue) { public static int getLithoColor(int colorValue) {
return ArrayUtils.contains(DARK_VALUES, originalValue) return ArrayUtils.contains(DARK_COLORS, colorValue)
? blackColor ? blackColor
: originalValue; : colorValue;
} }
public static void setHeaderGradient(ViewGroup viewGroup) { public static void setHeaderGradient(ViewGroup viewGroup) {

View File

@ -116,8 +116,9 @@ public class Settings extends BaseSettings {
PatchStatus.SpoofAppVersionDefaultString(), true); PatchStatus.SpoofAppVersionDefaultString(), true);
// PreferenceScreen: Navigation bar // PreferenceScreen: Navigation Bar
public static final BooleanSetting ENABLE_BLACK_NAVIGATION_BAR = new BooleanSetting("revanced_enable_black_navigation_bar", FALSE); 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_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_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); 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_FILTER_STRINGS.key,
CUSTOM_PLAYBACK_SPEEDS.key, CUSTOM_PLAYBACK_SPEEDS.key,
DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE.key, DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE.key,
ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE.key,
EXTERNAL_DOWNLOADER_PACKAGE_NAME.key, EXTERNAL_DOWNLOADER_PACKAGE_NAME.key,
HIDE_ACCOUNT_MENU_FILTER_STRINGS.key, HIDE_ACCOUNT_MENU_FILTER_STRINGS.key,
SB_API_URL.key, SB_API_URL.key,

View File

@ -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_FILTER_STRINGS;
import static app.revanced.extension.music.settings.Settings.CUSTOM_PLAYBACK_SPEEDS; 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.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.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.HIDE_ACCOUNT_MENU_FILTER_STRINGS;
import static app.revanced.extension.music.settings.Settings.OPEN_DEFAULT_APP_SETTINGS; 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) } else if (settings.equals(BYPASS_IMAGE_REGION_RESTRICTIONS_DOMAIN)
|| settings.equals(CUSTOM_FILTER_STRINGS) || settings.equals(CUSTOM_FILTER_STRINGS)
|| settings.equals(CUSTOM_PLAYBACK_SPEEDS) || settings.equals(CUSTOM_PLAYBACK_SPEEDS)
|| settings.equals(ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE)
|| settings.equals(HIDE_ACCOUNT_MENU_FILTER_STRINGS) || settings.equals(HIDE_ACCOUNT_MENU_FILTER_STRINGS)
|| settings.equals(RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY)) { || settings.equals(RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY)) {
ResettableEditTextPreference.showDialog(mActivity, stringSetting); ResettableEditTextPreference.showDialog(mActivity, stringSetting);

View File

@ -8,7 +8,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import app.revanced.extension.reddit.settings.Settings; import app.revanced.extension.reddit.settings.Settings;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.shared.utils.Utils;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -31,20 +30,12 @@ public class RemoveSubRedditDialogPatch {
clickViewDelayed(cancelButtonView); clickViewDelayed(cancelButtonView);
} }
public static void dismissDialogV2(Object object) { public static boolean spoofHasBeenVisitedStatus(boolean hasBeenVisited) {
if (!Settings.REMOVE_NOTIFICATION_DIALOG.get()) return Settings.REMOVE_NSFW_DIALOG.get() || hasBeenVisited;
return;
Utils.runOnMainThreadDelayed(() -> {
try {
dismissRedditDialogV2(object);
} catch (Exception ex) {
Logger.printException(() -> "dismissDialogV2 failed", ex);
}
}, 0);
} }
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) { private static void clickViewDelayed(View view) {

View File

@ -41,7 +41,7 @@ public class MiscellaneousPreferenceCategory extends ConditionalPreferenceCatego
addPreference(new TogglePreference( addPreference(new TogglePreference(
context, context,
"Sanitize sharing links", "Sanitize sharing links",
"Removes tracking query parameters from URLs when sharing links.", "Sanitizes sharing links by removing tracking query parameters.",
Settings.SANITIZE_URL_QUERY Settings.SANITIZE_URL_QUERY
)); ));
} }

View File

@ -289,7 +289,7 @@ object AppClient {
/** /**
* Android SDK version, equivalent to [Build.VERSION.SDK] (System property: ro.build.version.sdk) * 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. * App version.
*/ */
@ -298,10 +298,6 @@ object AppClient {
* GmsCore versionCode. * GmsCore versionCode.
*/ */
val gmscoreVersionCode: String? = null, val gmscoreVersionCode: String? = null,
/**
* ChipSet.
*/
val chipset: String? = null,
/** /**
* If the client can access the API logged in. * If the client can access the API logged in.
* If false, 'Authorization' must not be included. * 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. * Whether a poToken is required to get playback for more than 1 minute.
*/ */
val requirePoToken: Boolean = false, val requirePoToken: Boolean = false,
/**
* Client name for innertube body.
*/
val clientName: String,
/** /**
* Friendly name displayed in stats for nerds. * Friendly name displayed in stats for nerds.
*/ */
@ -329,9 +329,21 @@ object AppClient {
userAgent = USER_AGENT_ANDROID_VR, userAgent = USER_AGENT_ANDROID_VR,
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_VR, androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_VR,
clientVersion = CLIENT_VERSION_ANDROID_VR, clientVersion = CLIENT_VERSION_ANDROID_VR,
chipset = CHIPSET_ANDROID_VR, clientName = "ANDROID_VR",
friendlyName = "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( ANDROID_UNPLUGGED(
id = 29, id = 29,
deviceMake = DEVICE_MAKE_ANDROID_UNPLUGGED, deviceMake = DEVICE_MAKE_ANDROID_UNPLUGGED,
@ -341,8 +353,8 @@ object AppClient {
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_UNPLUGGED, androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_UNPLUGGED,
clientVersion = CLIENT_VERSION_ANDROID_UNPLUGGED, clientVersion = CLIENT_VERSION_ANDROID_UNPLUGGED,
gmscoreVersionCode = GMS_CORE_VERSION_CODE_ANDROID_UNPLUGGED, gmscoreVersionCode = GMS_CORE_VERSION_CODE_ANDROID_UNPLUGGED,
chipset = CHIPSET_ANDROID_UNPLUGGED,
requireAuth = true, requireAuth = true,
clientName = "ANDROID_UNPLUGGED",
friendlyName = "Android TV" friendlyName = "Android TV"
), ),
ANDROID_CREATOR( ANDROID_CREATOR(
@ -354,8 +366,8 @@ object AppClient {
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_CREATOR, androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_CREATOR,
clientVersion = CLIENT_VERSION_ANDROID_CREATOR, clientVersion = CLIENT_VERSION_ANDROID_CREATOR,
gmscoreVersionCode = GMS_CORE_VERSION_CODE_ANDROID_CREATOR, gmscoreVersionCode = GMS_CORE_VERSION_CODE_ANDROID_CREATOR,
chipset = CHIPSET_ANDROID_CREATOR,
requireAuth = true, requireAuth = true,
clientName = "ANDROID_CREATOR",
friendlyName = "Android Studio" friendlyName = "Android Studio"
), ),
IOS_UNPLUGGED( IOS_UNPLUGGED(
@ -367,6 +379,7 @@ object AppClient {
userAgent = USER_AGENT_IOS_UNPLUGGED, userAgent = USER_AGENT_IOS_UNPLUGGED,
clientVersion = CLIENT_VERSION_IOS_UNPLUGGED, clientVersion = CLIENT_VERSION_IOS_UNPLUGGED,
requireAuth = true, requireAuth = true,
clientName = "IOS_UNPLUGGED",
friendlyName = if (forceAVC()) friendlyName = if (forceAVC())
"iOS TV Force AVC" "iOS TV Force AVC"
else else
@ -382,6 +395,7 @@ object AppClient {
clientVersion = CLIENT_VERSION_IOS, clientVersion = CLIENT_VERSION_IOS,
supportsCookies = false, supportsCookies = false,
requirePoToken = true, requirePoToken = true,
clientName = "IOS",
friendlyName = if (forceAVC()) friendlyName = if (forceAVC())
"iOS Force AVC" "iOS Force AVC"
else else
@ -396,13 +410,11 @@ object AppClient {
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_MUSIC, androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_MUSIC,
clientVersion = CLIENT_VERSION_ANDROID_MUSIC, clientVersion = CLIENT_VERSION_ANDROID_MUSIC,
gmscoreVersionCode = GMS_CORE_VERSION_CODE_ANDROID_MUSIC, gmscoreVersionCode = GMS_CORE_VERSION_CODE_ANDROID_MUSIC,
chipset = CHIPSET_ANDROID_MUSIC,
requireAuth = true, requireAuth = true,
clientName = "ANDROID_MUSIC",
friendlyName = "Android Music" friendlyName = "Android Music"
); );
val clientName: String = name
companion object { companion object {
val CLIENT_ORDER_TO_USE_YOUTUBE: Array<ClientType> = arrayOf( val CLIENT_ORDER_TO_USE_YOUTUBE: Array<ClientType> = arrayOf(
ANDROID_VR, ANDROID_VR,
@ -410,6 +422,7 @@ object AppClient {
IOS_UNPLUGGED, IOS_UNPLUGGED,
ANDROID_CREATOR, ANDROID_CREATOR,
IOS, IOS,
ANDROID_VR_NO_AUTH,
) )
internal val CLIENT_ORDER_TO_USE_YOUTUBE_MUSIC: Array<ClientType> = arrayOf( internal val CLIENT_ORDER_TO_USE_YOUTUBE_MUSIC: Array<ClientType> = arrayOf(

View File

@ -61,7 +61,7 @@ public abstract class Filter {
*/ */
public boolean skip(String conversionContext, SpannableString spannableString, Object span, int start, int end, public boolean skip(String conversionContext, SpannableString spannableString, Object span, int start, int end,
int flags, boolean isWord, SpanType spanType, StringFilterGroup matchedGroup) { 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(); String filterSimpleName = getClass().getSimpleName();
Logger.printDebug(() -> filterSimpleName + " Removed setSpan: " + spanType.type); Logger.printDebug(() -> filterSimpleName + " Removed setSpan: " + spanType.type);
} }

View File

@ -13,8 +13,10 @@ import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; 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.patches.spoof.requests.StreamingDataRequest;
import app.revanced.extension.shared.settings.BaseSettings; 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.Logger;
import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.shared.utils.Utils;
@ -107,16 +109,6 @@ public class SpoofStreamingDataPatch {
return SPOOF_STREAMING_DATA; return SPOOF_STREAMING_DATA;
} }
/**
* Injection point.
*/
public static Object isSpoofingEnabled(Object original) {
if (!SPOOF_STREAMING_DATA) {
return original;
}
return null;
}
/** /**
* Injection point. * Injection point.
* This method is only invoked when playing a livestream on an iOS client. * This method is only invoked when playing a livestream on an iOS client.
@ -341,4 +333,12 @@ public class SpoofStreamingDataPatch {
return videoFormat; 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;
}
}
} }

View File

@ -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.Requester
import app.revanced.extension.shared.requests.Route import app.revanced.extension.shared.requests.Route
import app.revanced.extension.shared.requests.Route.CompiledRoute 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.Logger
import app.revanced.extension.shared.utils.Utils import app.revanced.extension.shared.utils.Utils
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
@ -43,6 +44,14 @@ object PlayerRoutes {
"&alt=proto" "&alt=proto"
).compile() ).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/" 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("clientVersion", clientType.clientVersion)
client.put("osName", clientType.osName) client.put("osName", clientType.osName)
client.put("osVersion", clientType.osVersion) client.put("osVersion", clientType.osVersion)
if (clientType.osName != "iOS") { if (clientType.androidSdkVersion != null) {
client.put("androidSdkVersion", clientType.androidSdkVersion) client.put("androidSdkVersion", clientType.androidSdkVersion)
if (clientType.gmscoreVersionCode != null) { if (clientType.gmscoreVersionCode != null) {
client.put("gmscoreVersionCode", clientType.gmscoreVersionCode) 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("gl", LOCALE_COUNTRY)
client.put("hl", LOCALE_LANGUAGE) client.put("timeZone", TIME_ZONE_ID)
client.put("gl", LOCALE_COUNTRY) client.put("utcOffsetMinutes", "$UTC_OFFSET_MINUTES")
client.put("timeZone", TIME_ZONE_ID)
client.put("utcOffsetMinutes", "$UTC_OFFSET_MINUTES")
}
val context = JSONObject() val context = JSONObject()
context.put("client", client) context.put("client", client)

View File

@ -93,9 +93,14 @@ class StreamingDataRequest private constructor(
"X-GOOG-API-FORMAT-VERSION", "X-GOOG-API-FORMAT-VERSION",
VISITOR_ID_HEADER 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> = 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 private var lastSpoofedClientType: AppClient.ClientType? = null
@ -177,8 +182,6 @@ class StreamingDataRequest private constructor(
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
val setLocale =
!clientType.supportsCookies || playerHeaders[AUTHORIZATION_HEADER] == null
val usePoToken = val usePoToken =
clientType.requirePoToken && !StringUtils.isAnyEmpty(botGuardPoToken, visitorId) clientType.requirePoToken && !StringUtils.isAnyEmpty(botGuardPoToken, visitorId)
@ -209,7 +212,7 @@ class StreamingDataRequest private constructor(
videoId = videoId, videoId = videoId,
botGuardPoToken = botGuardPoToken, botGuardPoToken = botGuardPoToken,
visitorId = visitorId, visitorId = visitorId,
setLocale = setLocale setLocale = DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH,
) )
Logger.printDebug { "Set poToken (botGuardPoToken):\n$botGuardPoToken" } Logger.printDebug { "Set poToken (botGuardPoToken):\n$botGuardPoToken" }
} else { } else {
@ -217,9 +220,10 @@ class StreamingDataRequest private constructor(
createApplicationRequestBody( createApplicationRequestBody(
clientType = clientType, clientType = clientType,
videoId = videoId, videoId = videoId,
setLocale = setLocale setLocale = DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH,
) )
} }
connection.setFixedLengthStreamingMode(requestBody.size) connection.setFixedLengthStreamingMode(requestBody.size)
connection.outputStream.write(requestBody) connection.outputStream.write(requestBody)
@ -275,7 +279,7 @@ class StreamingDataRequest private constructor(
} else { } else {
BufferedInputStream(connection.inputStream).use { inputStream -> BufferedInputStream(connection.inputStream).use { inputStream ->
ByteArrayOutputStream().use { stream -> ByteArrayOutputStream().use { stream ->
val buffer = ByteArray(8192) val buffer = ByteArray(4096)
var bytesRead: Int var bytesRead: Int
while ((inputStream.read(buffer) while ((inputStream.read(buffer)
.also { bytesRead = it }) >= 0 .also { bytesRead = it }) >= 0

View File

@ -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);
}
}

View File

@ -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.ReturnYouTubeUsernamePatch.DisplayFormat;
import app.revanced.extension.shared.patches.client.AppClient.ClientType; import app.revanced.extension.shared.patches.client.AppClient.ClientType;
import app.revanced.extension.shared.patches.spoof.SpoofStreamingDataPatch.AudioStreamLanguageOverrideAvailability;
/** /**
* Settings shared across multiple apps. * 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 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 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. * 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 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 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, 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"); "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); public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE);

View File

@ -43,6 +43,8 @@ import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; 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 app.revanced.extension.shared.settings.BooleanSetting;
import kotlin.text.Regex; import kotlin.text.Regex;
@ -280,14 +282,13 @@ public class Utils {
} }
public static Resources getResources() { public static Resources getResources() {
if (context != null) {
return context.getResources();
}
Activity mActivity = activityRef.get(); Activity mActivity = activityRef.get();
if (mActivity != null) { if (mActivity != null) {
return mActivity.getResources(); return mActivity.getResources();
} }
Context mContext = getContext();
if (mContext != null) {
return mContext.getResources();
}
throw new IllegalStateException("Get resources failed"); throw new IllegalStateException("Get resources failed");
} }
@ -301,7 +302,7 @@ public class Utils {
* @param mContext Context to check locale. * @param mContext Context to check locale.
* @return Context with locale applied. * @return Context with locale applied.
*/ */
public static Context getLocalizedContextAndSetResources(Context mContext) { public static Context getLocalizedContext(Context mContext) {
Activity mActivity = activityRef.get(); Activity mActivity = activityRef.get();
if (mActivity == null) { if (mActivity == null) {
return mContext; return mContext;
@ -310,19 +311,15 @@ public class Utils {
return null; return null;
} }
// Locale of MainActivity. AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
Locale applicationLocale;
// Locale of Application.
Locale applicationLocale = language == AppLanguage.DEFAULT
? mActivity.getResources().getConfiguration().locale
: language.getLocale();
// Locale of Context. // Locale of Context.
Locale contextLocale; Locale contextLocale = mContext.getResources().getConfiguration().locale;
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;
}
// If they are identical, no need to override them. // If they are identical, no need to override them.
if (applicationLocale == contextLocale) { if (applicationLocale == contextLocale) {
@ -350,6 +347,14 @@ public class Utils {
context = appContext; 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. // 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, // Calling the regular printDebug method here can cause a Settings context null pointer exception,
// even though the context is already set before the call. // even though the context is already set before the call.

View File

@ -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_HOME = "FEwhat_to_watch";
private static final String BROWSE_ID_LIBRARY = "FElibrary"; private static final String BROWSE_ID_LIBRARY = "FElibrary";
private static final String BROWSE_ID_MOVIE = "FEstorefront"; 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 = "FEactivity";
private static final String BROWSE_ID_NOTIFICATION_INBOX = "FEnotifications_inbox"; private static final String BROWSE_ID_NOTIFICATION_INBOX = "FEnotifications_inbox";
private static final String BROWSE_ID_PLAYLIST = "VLPL"; private static final String BROWSE_ID_PLAYLIST = "VLPL";
@ -38,6 +39,7 @@ public final class CarouselShelfFilter extends Filter {
BROWSE_ID_COURSES, BROWSE_ID_COURSES,
BROWSE_ID_LIBRARY, BROWSE_ID_LIBRARY,
BROWSE_ID_MOVIE, BROWSE_ID_MOVIE,
BROWSE_ID_NEWS,
BROWSE_ID_NOTIFICATION_INBOX, BROWSE_ID_NOTIFICATION_INBOX,
BROWSE_ID_PREMIUM BROWSE_ID_PREMIUM
); );

View File

@ -19,12 +19,6 @@ public final class FeedComponentsFilter extends Filter {
private static final String INLINE_EXPANSION_PATH = "inline_expansion"; private static final String INLINE_EXPANSION_PATH = "inline_expansion";
private static final String FEED_VIDEO_PATH = "video_lockup_with_attachment"; 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 = private static final ByteArrayFilterGroup mixPlaylists =
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
null, null,
@ -37,14 +31,11 @@ public final class FeedComponentsFilter extends Filter {
"channel_profile" "channel_profile"
); );
private static final StringTrieSearch mixPlaylistsContextExceptions = new StringTrieSearch(); private static final StringTrieSearch mixPlaylistsContextExceptions = new StringTrieSearch();
private static final StringTrieSearch communityPostsFeedGroupSearch = new StringTrieSearch();
private final StringFilterGroup channelProfile; private final StringFilterGroup channelProfile;
private final StringFilterGroup communityPosts; private final StringFilterGroup communityPosts;
private final StringFilterGroup expandableChip; private final StringFilterGroup expandableChip;
private final ByteArrayFilterGroup visitStoreButton; private final ByteArrayFilterGroup visitStoreButton;
private final StringFilterGroup videoLockup;
private static final StringTrieSearch communityPostsFeedGroupSearch = new StringTrieSearch();
private final StringFilterGroupList communityPostsFeedGroup = new StringFilterGroupList(); private final StringFilterGroupList communityPostsFeedGroup = new StringFilterGroupList();
@ -90,18 +81,12 @@ public final class FeedComponentsFilter extends Filter {
"cell_button.eml" "cell_button.eml"
); );
videoLockup = new StringFilterGroup(
null,
FEED_VIDEO_PATH
);
addIdentifierCallbacks( addIdentifierCallbacks(
chipsShelf, chipsShelf,
communityPosts, communityPosts,
expandableShelf, expandableShelf,
feedSearchBar, feedSearchBar,
tasteBuilder, tasteBuilder
videoLockup
); );
// Paths. // Paths.
@ -207,8 +192,7 @@ public final class FeedComponentsFilter extends Filter {
notifyMe, notifyMe,
playables, playables,
subscriptionsChannelBar, subscriptionsChannelBar,
ticketShelf, ticketShelf
videoLockup
); );
final StringFilterGroup communityPostsHomeAndRelatedVideos = final StringFilterGroup communityPostsHomeAndRelatedVideos =
@ -259,16 +243,12 @@ public final class FeedComponentsFilter extends Filter {
if (!communityPostsFeedGroupSearch.matches(allValue) && Settings.HIDE_COMMUNITY_POSTS_CHANNEL.get()) { if (!communityPostsFeedGroupSearch.matches(allValue) && Settings.HIDE_COMMUNITY_POSTS_CHANNEL.get()) {
return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex);
} }
if (!communityPostsFeedGroup.check(allValue).isFiltered()) { if (communityPostsFeedGroup.check(allValue).isFiltered()) {
return false;
}
} else if (matchedGroup == expandableChip) {
if (path.startsWith(FEED_VIDEO_PATH)) {
return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex);
} }
return false; return false;
} else if (matchedGroup == videoLockup) { } else if (matchedGroup == expandableChip) {
if (contentIndex == 0 && path.startsWith("CellType|") && inlineExpansion.check(protobufBufferArray).isFiltered()) { if (path.startsWith(FEED_VIDEO_PATH)) {
return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex);
} }
return false; return false;

View File

@ -5,7 +5,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup; 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 { public final class ReturnYouTubeDislikeFilterPatch extends Filter {
/** /**
* Last unique video id's loaded. Value is ignored and Map is treated as a Set. * Last unique video id's loaded.
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry(). * Key is a String represeting the video id.
* Value is a ByteArrayFilterGroup used for performing KMP pattern searching.
*/ */
@GuardedBy("itself") @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. * 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. * 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; return;
} }
synchronized (lastVideoIds) { synchronized (lastVideoIds) {
if (lastVideoIds.put(videoId, Boolean.TRUE) == null) { if (!lastVideoIds.containsKey(videoId)) {
Logger.printDebug(() -> "New Short video id: " + 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) { } 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 * This could use {@link TrieSearch}, but since the patterns are constantly changing
* the overhead of updating the Trie might negate the search performance gain. * 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++) { for (int i = 0, lastArrayStartIndex = array.length - text.length(); i <= lastArrayStartIndex; i++) {
boolean found = true; boolean found = true;
for (int j = 0, textLength = text.length(); j < textLength; j++) { for (int j = 0, textLength = text.length(); j < textLength; j++) {
@ -154,8 +162,10 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
@Nullable @Nullable
private String findVideoId(byte[] protobufBufferArray) { private String findVideoId(byte[] protobufBufferArray) {
synchronized (lastVideoIds) { synchronized (lastVideoIds) {
for (String videoId : lastVideoIds.keySet()) { for (Map.Entry<String, ByteArrayFilterGroup> entry : lastVideoIds.entrySet()) {
if (byteArrayContainsString(protobufBufferArray, videoId)) { final String videoId = entry.getKey();
final ByteArrayFilterGroup videoIdFilter = entry.getValue();
if (byteArrayContainsString(protobufBufferArray, videoId, videoIdFilter)) {
return videoId; return videoId;
} }
} }

View File

@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup; 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; 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. * Last unique video id's loaded.
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry(). * Key is a String represeting the video id.
* Value is a ByteArrayFilterGroup used for performing KMP pattern searching.
*/ */
@GuardedBy("itself") @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. * 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. * 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; return;
} }
synchronized (lastVideoIds) { 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) { } catch (Exception ex) {
Logger.printException(() -> "newPlayerResponseVideoId failure", 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 * This could use {@link TrieSearch}, but since the patterns are constantly changing
* the overhead of updating the Trie might negate the search performance gain. * 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++) { for (int i = 0, lastArrayStartIndex = array.length - text.length(); i <= lastArrayStartIndex; i++) {
boolean found = true; boolean found = true;
for (int j = 0, textLength = text.length(); j < textLength; j++) { 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) { private void findVideoId(byte[] protobufBufferArray) {
synchronized (lastVideoIds) { synchronized (lastVideoIds) {
for (String videoId : lastVideoIds.keySet()) { for (Map.Entry<String, ByteArrayFilterGroup> entry : lastVideoIds.entrySet()) {
if (byteArrayContainsString(protobufBufferArray, videoId)) { final String videoId = entry.getKey();
final ByteArrayFilterGroup videoIdFilter = entry.getValue();
if (byteArrayContainsString(protobufBufferArray, videoId, videoIdFilter)) {
setShortsVideoId(videoId, false); setShortsVideoId(videoId, false);
return;
} }
} }
} }

View File

@ -41,9 +41,13 @@ public final class ChangeStartPagePatch {
* Channel id, this can be used as a browseId. * Channel id, this can be used as a browseId.
*/ */
COURSES("UCtFRv9O2AHqOZjjynzrv-xg", TRUE), COURSES("UCtFRv9O2AHqOZjjynzrv-xg", TRUE),
FASHION("UCrpQ4p1Ql_hG8rKXIKM1MOQ", TRUE),
GAMING("UCOpNcN46UbXVtpKMrmU4Abg", TRUE), GAMING("UCOpNcN46UbXVtpKMrmU4Abg", TRUE),
LIVE("UC4R8DWoMoI7CAwX8_LjQHig", TRUE), LIVE("UC4R8DWoMoI7CAwX8_LjQHig", TRUE),
MUSIC("UC-9-kyTW8ZkZNDHQJ6FgpwQ", TRUE), MUSIC("UC-9-kyTW8ZkZNDHQJ6FgpwQ", TRUE),
NEWS("UCYfdidRxbB8Qhf0Nx7ioOYw", TRUE),
PODCASTS("UCNVkxRPqsBNejO6B9thG9Xw", TRUE),
SHOPPING("UCkYQyvc_i9hXEo4xic9Hh2g", TRUE),
SPORTS("UCEgdi0XIXXZ-qJOFPf4JSKw", TRUE), SPORTS("UCEgdi0XIXXZ-qJOFPf4JSKw", TRUE),
/** /**

View File

@ -188,10 +188,6 @@ public class GeneralPatch {
return Settings.HIDE_FLOATING_MICROPHONE.get() || original; return Settings.HIDE_FLOATING_MICROPHONE.get() || original;
} }
public static boolean hideSnackBar() {
return Settings.HIDE_SNACK_BAR.get();
}
// endregion // endregion
// region [Hide navigation bar components] patch // region [Hide navigation bar components] patch

View File

@ -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);
}
}
}

View File

@ -22,7 +22,8 @@ public final class SettingsMenuPatch extends BaseSettingsMenuPatch {
GENERAL("general_key", Settings.HIDE_SETTINGS_MENU_GENERAL.get()), GENERAL("general_key", Settings.HIDE_SETTINGS_MENU_GENERAL.get()),
ACCOUNT("account_switcher_key", Settings.HIDE_SETTINGS_MENU_ACCOUNT.get()), ACCOUNT("account_switcher_key", Settings.HIDE_SETTINGS_MENU_ACCOUNT.get()),
DATA_SAVING("data_saving_settings_key", Settings.HIDE_SETTINGS_MENU_DATA_SAVING.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()), 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()), POST_PURCHASE("yt_unlimited_post_purchase_key", Settings.HIDE_SETTINGS_MENU_POST_PURCHASE.get()),
OFFLINE("offline_key", Settings.HIDE_SETTINGS_MENU_OFFLINE.get()), OFFLINE("offline_key", Settings.HIDE_SETTINGS_MENU_OFFLINE.get()),

View File

@ -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;
}
}

View File

@ -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
}
}
}

View File

@ -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 "";
}
}

View File

@ -10,9 +10,9 @@ import app.revanced.extension.youtube.settings.Settings;
public class OpenLinksDirectlyPatch { public class OpenLinksDirectlyPatch {
private static final String YOUTUBE_REDIRECT_PATH = "/redirect"; 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); final Uri parsed = Uri.parse(uri);
if (!Settings.ENABLE_OPEN_LINKS_DIRECTLY.get()) if (!Settings.BYPASS_URL_REDIRECTS.get())
return parsed; return parsed;
if (Objects.equals(parsed.getPath(), YOUTUBE_REDIRECT_PATH)) { if (Objects.equals(parsed.getPath(), YOUTUBE_REDIRECT_PATH)) {

View File

@ -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 "";
}
}

View File

@ -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;
}
}

View File

@ -27,23 +27,28 @@ public class SeekbarColorPatch {
private static final int ORIGINAL_SEEKBAR_COLOR = 0xFFFF0000; 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. * Default YouTube seekbar color brightness.
*/ */
private static final float ORIGINAL_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, * 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}. * Otherwise this is {@link #ORIGINAL_SEEKBAR_COLOR}.
*/ */
private static int seekbarColor = 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]; 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 { static {
float[] hsv = new float[3]; float[] hsv = new float[3];
Color.colorToHSV(ORIGINAL_SEEKBAR_COLOR, hsv); Color.colorToHSV(ORIGINAL_SEEKBAR_COLOR, hsv);
@ -61,16 +71,18 @@ public class SeekbarColorPatch {
if (CUSTOM_SEEKBAR_COLOR_ENABLED) { if (CUSTOM_SEEKBAR_COLOR_ENABLED) {
loadCustomSeekbarColor(); loadCustomSeekbarColor();
} }
Arrays.fill(customSeekbarColorInt, seekbarColor);
} }
private static void loadCustomSeekbarColor() { private static void loadCustomSeekbarColor() {
try { try {
seekbarColor = Color.parseColor(Settings.ENABLE_CUSTOM_SEEKBAR_COLOR_VALUE.get()); seekbarColor = Color.parseColor(Settings.CUSTOM_SEEKBAR_COLOR_VALUE.get());
Color.colorToHSV(seekbarColor, customSeekbarColorHSV); Color.colorToHSV(seekbarColor, customSeekbarColorHSV);
} catch (Exception ex) { } catch (Exception ex) {
Utils.showToastShort(str("revanced_custom_seekbar_color_value_invalid_invalid_toast")); Utils.showToastShort(str("revanced_custom_seekbar_color_value_invalid_invalid_toast"));
Utils.showToastShort(str("revanced_extended_reset_to_default_toast")); Utils.showToastShort(str("revanced_extended_reset_to_default_toast"));
Settings.ENABLE_CUSTOM_SEEKBAR_COLOR_VALUE.resetToDefault(); Settings.CUSTOM_SEEKBAR_COLOR_VALUE.resetToDefault();
loadCustomSeekbarColor(); loadCustomSeekbarColor();
} }
} }
@ -165,6 +177,33 @@ public class SeekbarColorPatch {
return colorValue; 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. * Injection point.
*/ */
@ -174,15 +213,15 @@ public class SeekbarColorPatch {
if (CUSTOM_SEEKBAR_COLOR_ENABLED || hideSeekbar) { if (CUSTOM_SEEKBAR_COLOR_ENABLED || hideSeekbar) {
// Most litho usage of linear gradients is hooked here, // Most litho usage of linear gradients is hooked here,
// so must only change if the values are those for the seekbar. // so must only change if the values are those for the seekbar.
if (Arrays.equals(ORIGINAL_SEEKBAR_GRADIENT_COLORS, colors) if ((Arrays.equals(FEED_ORIGINAL_SEEKBAR_GRADIENT_COLORS, colors)
&& Arrays.equals(ORIGINAL_SEEKBAR_GRADIENT_POSITIONS, positions)) { && Arrays.equals(FEED_ORIGINAL_SEEKBAR_GRADIENT_POSITIONS, positions))) {
Arrays.fill(colors, hideSeekbar Arrays.fill(colors, hideSeekbar
? 0x00000000 ? 0x00000000
: seekbarColor); : seekbarColor);
return; return;
} }
Logger.printDebug(() -> "Ignoring gradient colors: " + Arrays.toString(colors) Logger.printDebug(() -> "Ignoring gradient colors: " + colorArrayToHex(colors)
+ " positions: " + Arrays.toString(positions)); + " positions: " + Arrays.toString(positions));
} }
} }

View File

@ -16,11 +16,14 @@ import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
import java.lang.ref.WeakReference; 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.ResourceUtils;
import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.shared.utils.Utils;
import app.revanced.extension.youtube.settings.Settings; 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.shared.ShortsPlayerState;
import app.revanced.extension.youtube.utils.VideoUtils; import app.revanced.extension.youtube.utils.VideoUtils;
import kotlin.Unit;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class ShortsPatch { public class ShortsPatch {
@ -34,7 +37,7 @@ public class ShortsPatch {
if (HIDE_SHORTS_NAVIGATION_BAR) { if (HIDE_SHORTS_NAVIGATION_BAR) {
ShortsPlayerState.getOnChange().addObserver((ShortsPlayerState state) -> { ShortsPlayerState.getOnChange().addObserver((ShortsPlayerState state) -> {
setNavigationBarLayoutParams(state); setNavigationBarLayoutParams(state);
return null; return Unit.INSTANCE;
}); });
} }
final int bottomMargin = validateValue( final int bottomMargin = validateValue(
@ -60,6 +63,10 @@ public class ShortsPatch {
return Settings.DISABLE_RESUMING_SHORTS_PLAYER.get(); 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) { public static boolean enableShortsTimeStamp(boolean original) {
return ENABLE_TIME_STAMP || original; return ENABLE_TIME_STAMP || original;
} }
@ -211,4 +218,38 @@ public class ShortsPatch {
return !Settings.RESTORE_SHORTS_OLD_PLAYER_LAYOUT.get(); 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;
}
}
} }

View File

@ -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;
}
}

View File

@ -1,34 +1,36 @@
package app.revanced.extension.youtube.patches.utils; package app.revanced.extension.youtube.patches.utils;
import org.apache.commons.lang3.ArrayUtils;
import app.revanced.extension.shared.utils.ResourceUtils; import app.revanced.extension.shared.utils.ResourceUtils;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class DrawableColorPatch { public class DrawableColorPatch {
private static final int[] WHITE_VALUES = { private static final int[] DARK_COLORS = {
-1, // comments chip background 0xFF282828, // drawer content view background
-394759, // music related results panel background 0xFF212121, // comments chip background
-83886081 // video chapters list background 0xFF181818, // music related results panel background
0xFF0F0F0F, // comments chip background (new layout)
0xFA212121, // video chapters list background
}; };
private static final int[] DARK_VALUES = { private static final int[] LIGHT_COLORS = {
-14145496, // drawer content view background -1, // comments chip background
-14606047, // comments chip background 0xFFF9F9F9, // music related results panel background
-15198184, // music related results panel background 0xFAFFFFFF, // video chapters list background
-15790321, // comments chip background (new layout)
-98492127 // video chapters list background
}; };
// background colors // background colors
private static int whiteColor = 0; private static int whiteColor = 0;
private static int blackColor = 0; private static int blackColor = 0;
public static int getLithoColor(int originalValue) { public static int getLithoColor(int colorValue) {
if (anyEquals(originalValue, DARK_VALUES)) { if (ArrayUtils.contains(DARK_COLORS, colorValue)) {
return getBlackColor(); return getBlackColor();
} else if (anyEquals(originalValue, WHITE_VALUES)) { } else if (ArrayUtils.contains(LIGHT_COLORS, colorValue)) {
return getWhiteColor(); return getWhiteColor();
} }
return originalValue; return colorValue;
} }
private static int getBlackColor() { private static int getBlackColor() {
@ -40,11 +42,6 @@ public class DrawableColorPatch {
if (whiteColor == 0) whiteColor = ResourceUtils.getColor("yt_white1"); if (whiteColor == 0) whiteColor = ResourceUtils.getColor("yt_white1");
return whiteColor; return whiteColor;
} }
private static boolean anyEquals(int value, int... of) {
for (int v : of) if (value == v) return true;
return false;
}
} }

View File

@ -27,6 +27,10 @@ public class PatchStatus {
return false; return false;
} }
public static String SpoofAppVersionDefaultString() {
return "18.17.43";
}
public static boolean ToolBarComponents() { public static boolean ToolBarComponents() {
// Replace this with true if the Toolbar components patch succeeds // Replace this with true if the Toolbar components patch succeeds
return false; return false;

View File

@ -24,7 +24,9 @@ public class VideoQualityPatch {
* Injection point. * Injection point.
*/ */
public static void newVideoStarted() { public static void newVideoStarted() {
setVideoQuality(0); VideoInformation.qualityNeedsUpdating = true;
VideoInformation.videoQualities = null;
setVideoQuality(250);
} }
/** /**

View File

@ -129,7 +129,11 @@ class MusicRequest private constructor(
clientType clientType
) )
val requestBody = 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.setFixedLengthStreamingMode(requestBody.size)
connection.outputStream.write(requestBody) connection.outputStream.write(requestBody)
@ -161,7 +165,7 @@ class MusicRequest private constructor(
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
val clientType = WebClient.ClientType.MWEB val clientType = WebClient.ClientType.MWEB
val clientTypeName = clientType.name 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 { try {
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute( val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
@ -226,9 +230,14 @@ class MusicRequest private constructor(
.getJSONObject("watchEndpoint") .getJSONObject("watchEndpoint")
val playerParams: String? = watchEndpointJsonObject?.getString("playerParams") val playerParams: String? = watchEndpointJsonObject?.getString("playerParams")
return playerParams != null && VideoInformation.isMixPlaylistsOpenedByUser(playerParams) return playerParams != null && VideoInformation.isMixPlaylistsOpenedByUser(
playerParams
)
} catch (e: JSONException) { } 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 return false
@ -242,7 +251,10 @@ class MusicRequest private constructor(
.getString("category") .getString("category")
.equals("Music") .equals("Music")
} catch (e: JSONException) { } 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 return false

View File

@ -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_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_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_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_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_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); public static final BooleanSetting HIDE_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", TRUE);
// PreferenceScreen: Feed - Category bar // 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_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); 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 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_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_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 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 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 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 // PreferenceScreen: General - Account menu
public static final BooleanSetting HIDE_ACCOUNT_MENU = new BooleanSetting("revanced_hide_account_menu", FALSE); 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_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)); 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 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_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); 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_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_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_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_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_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); 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_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); 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 // PreferenceScreen: General - Toolbar
public static final BooleanSetting CHANGE_YOUTUBE_HEADER = new BooleanSetting("revanced_change_youtube_header", TRUE, true); 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 = 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_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_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_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); 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_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_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 // PreferenceScreen: Player - Ambient mode
public static final BooleanSetting BYPASS_AMBIENT_MODE_RESTRICTIONS = new BooleanSetting("revanced_bypass_ambient_mode_restrictions", FALSE); 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); 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 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 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 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 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_CHAPTER_LABEL = new BooleanSetting("revanced_hide_seekbar_chapter_label", FALSE, true);
public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", 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 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_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 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 // PreferenceScreen: Shorts - Shorts player components
public static final BooleanSetting HIDE_SHORTS_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE); 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 // PreferenceScreen: Miscellaneous
public static final BooleanSetting ENABLE_EXTERNAL_BROWSER = new BooleanSetting("revanced_enable_external_browser", TRUE, true); public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
public static final BooleanSetting ENABLE_OPEN_LINKS_DIRECTLY = new BooleanSetting("revanced_enable_open_links_directly", TRUE); public static final BooleanSetting OPEN_LINKS_EXTERNALLY = new BooleanSetting("revanced_open_links_externally", TRUE, true);
// Experimental Flags // Experimental Flags
public static final BooleanSetting CHANGE_SHARE_SHEET = new BooleanSetting("revanced_change_share_sheet", FALSE, true); public static final BooleanSetting CHANGE_SHARE_SHEET = new BooleanSetting("revanced_change_share_sheet", FALSE, true);

View File

@ -69,9 +69,9 @@ public final class VideoInformation {
* The available qualities of the current video in human readable form: [1080, 720, 480] * The available qualities of the current video in human readable form: [1080, 720, 480]
*/ */
@Nullable @Nullable
private static List<Integer> videoQualities; public static volatile List<Integer> videoQualities;
private static boolean qualityNeedsUpdating; public static volatile boolean qualityNeedsUpdating;
/** /**
* Injection point. * Injection point.

View File

@ -23,6 +23,7 @@ import app.revanced.extension.youtube.patches.player.PlayerPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment; import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment;
import kotlin.Unit;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class SponsorBlockViewController { public class SponsorBlockViewController {
@ -44,7 +45,7 @@ public class SponsorBlockViewController {
static { static {
PlayerType.getOnChange().addObserver((PlayerType type) -> { PlayerType.getOnChange().addObserver((PlayerType type) -> {
playerTypeChanged(type); playerTypeChanged(type);
return null; return Unit.INSTANCE;
}); });
defaultBottomMargin = getDimension("brand_interaction_default_bottom_margin"); defaultBottomMargin = getDimension("brand_interaction_default_bottom_margin");

View File

@ -59,7 +59,7 @@ class ScreenBrightnessController(
*/ */
private var rawScreenBrightness: Float private var rawScreenBrightness: Float
get() = host.window.attributes.screenBrightness get() = host.window.attributes.screenBrightness
private set(value) { set(value) {
val attr = host.window.attributes val attr = host.window.attributes
attr.screenBrightness = value attr.screenBrightness = value
host.window.attributes = attr host.window.attributes = attr

View File

@ -29,12 +29,17 @@ import app.revanced.extension.youtube.shared.VideoInformation;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class VideoUtils extends IntentUtils { 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 PLAYLIST_URL = "https://www.youtube.com/playlist?list=";
private static final String VIDEO_URL = "https://youtu.be/"; 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_INTENT_FORMAT = "vnd.youtube://%s?start=%d";
private static final String VIDEO_SCHEME_LINK_FORMAT = "https://youtu.be/%s?t=%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 final AtomicBoolean isExternalDownloaderLaunched = new AtomicBoolean(false);
private static String getChannelUrl(String channelId) {
return CHANNEL_URL + channelId;
}
private static String getPlaylistUrl(String playlistId) { private static String getPlaylistUrl(String playlistId) {
return PLAYLIST_URL + 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() { public static void openVideo() {
openVideo(VideoInformation.getVideoId()); openVideo(VideoInformation.getVideoId());
} }

View File

@ -44,7 +44,7 @@ public class VideoQualitySettingsActivity extends Activity {
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(Context base) {
super.attachBaseContext(Utils.getLocalizedContextAndSetResources(base)); super.attachBaseContext(Utils.getLocalizedContext(base));
} }
@Override @Override

View File

@ -4,5 +4,5 @@ org.gradle.parallel = true
android.useAndroidX = true android.useAndroidX = true
kotlin.code.style = official kotlin.code.style = official
kotlin.jvm.target.validation.mode = IGNORE kotlin.jvm.target.validation.mode = IGNORE
version = 5.2.2 version = 5.3.1

View File

@ -57,7 +57,28 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.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": [] "options": []
@ -77,7 +98,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -115,7 +137,51 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.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": [] "options": []
@ -180,7 +246,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -221,7 +288,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -376,7 +444,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [ "options": [
@ -418,7 +487,7 @@
}, },
{ {
"name": "Custom branding name for Reddit", "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, "use": false,
"dependencies": [], "dependencies": [],
"compatiblePackages": { "compatiblePackages": {
@ -441,7 +510,7 @@
}, },
{ {
"name": "Custom branding name for YouTube", "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, "use": true,
"dependencies": [ "dependencies": [
"Settings for YouTube" "Settings for YouTube"
@ -476,7 +545,7 @@
}, },
{ {
"name": "Custom branding name for YouTube Music", "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, "use": true,
"dependencies": [ "dependencies": [
"Settings for YouTube Music" "Settings for YouTube Music"
@ -488,7 +557,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [ "options": [
@ -599,7 +669,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [ "options": [
@ -630,7 +701,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [ "options": [
@ -701,7 +773,8 @@
"com.google.android.apps.youtube.music": [ "com.google.android.apps.youtube.music": [
"7.06.54", "7.06.54",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -721,7 +794,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -751,7 +825,8 @@
"description": "Adds an option to disable redirection to the next track when clicking the Dislike button.", "description": "Adds an option to disable redirection to the next track when clicking the Dislike button.",
"use": true, "use": true,
"dependencies": [ "dependencies": [
"Settings for YouTube Music" "Settings for YouTube Music",
"ResourcePatch"
], ],
"compatiblePackages": { "compatiblePackages": {
"com.google.android.apps.youtube.music": [ "com.google.android.apps.youtube.music": [
@ -760,7 +835,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -800,7 +876,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -862,7 +939,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "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.", "description": "Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched.",
"use": true, "use": true,
"dependencies": [ "dependencies": [
"Settings for YouTube" "Settings for YouTube",
"ResourcePatch"
], ],
"compatiblePackages": { "compatiblePackages": {
"com.google.android.youtube": [ "com.google.android.youtube": [
@ -922,7 +1001,7 @@
}, },
{ {
"name": "Enable OPUS codec", "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, "use": true,
"dependencies": [ "dependencies": [
"BytecodePatch", "BytecodePatch",
@ -935,14 +1014,15 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
}, },
{ {
"name": "Enable OPUS codec", "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, "use": true,
"dependencies": [ "dependencies": [
"BytecodePatch", "BytecodePatch",
@ -975,7 +1055,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -1000,27 +1081,6 @@
}, },
"options": [] "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", "name": "Enable gradient loading screen",
"description": "Adds an option to enable the gradient loading screen.", "description": "Adds an option to enable the gradient loading screen.",
@ -1056,27 +1116,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
] "8.02.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"
] ]
}, },
"options": [] "options": []
@ -1102,7 +1143,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -1169,7 +1211,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [ "options": [
@ -1332,7 +1375,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -1355,7 +1399,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -1366,6 +1411,7 @@
"use": true, "use": true,
"dependencies": [ "dependencies": [
"Settings for YouTube", "Settings for YouTube",
"BytecodePatch",
"BytecodePatch" "BytecodePatch"
], ],
"compatiblePackages": { "compatiblePackages": {
@ -1379,7 +1425,17 @@
"19.44.39" "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", "name": "Hide ads",
@ -1399,7 +1455,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -1476,7 +1533,9 @@
"BytecodePatch", "BytecodePatch",
"ResourcePatch", "ResourcePatch",
"Settings for YouTube", "Settings for YouTube",
"BytecodePatch" "BytecodePatch",
"BytecodePatch",
"ResourcePatch"
], ],
"compatiblePackages": { "compatiblePackages": {
"com.google.android.youtube": [ "com.google.android.youtube": [
@ -1530,7 +1589,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -1587,7 +1647,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -1652,7 +1713,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -1769,26 +1831,6 @@
}, },
"options": [] "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", "name": "MaterialYou",
"description": "Applies the MaterialYou theme for Android 12+ devices.", "description": "Applies the MaterialYou theme for Android 12+ devices.",
@ -1848,7 +1890,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -1903,6 +1946,27 @@
}, },
"options": [] "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", "name": "Overlay buttons",
"description": "Adds options to display useful overlay buttons in the video player.", "description": "Adds options to display useful overlay buttons in the video player.",
@ -1913,8 +1977,7 @@
"BytecodePatch", "BytecodePatch",
"ResourcePatch", "ResourcePatch",
"ResourcePatch", "ResourcePatch",
"Settings for YouTube", "Settings for YouTube"
"ResourcePatch"
], ],
"compatiblePackages": { "compatiblePackages": {
"com.google.android.youtube": [ "com.google.android.youtube": [
@ -1944,20 +2007,20 @@
{ {
"key": "bottomMargin", "key": "bottomMargin",
"title": "Bottom margin", "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, "required": true,
"type": "kotlin.String", "type": "kotlin.String",
"default": "2.5dip", "default": "2.5dip",
"values": { "values": {
"Default": "2.5dip", "Default": "2.5dip",
"None": "0.0dip", "Minimum": "0.1dip",
"Wider": "5.0dip" "Wider": "5.0dip"
} }
}, },
{ {
"key": "widerButtonsSpace", "key": "widerButtonsSpace",
"title": "Wider between-buttons space", "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, "required": true,
"type": "kotlin.Boolean", "type": "kotlin.Boolean",
"default": false, "default": false,
@ -1993,7 +2056,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -2011,7 +2075,8 @@
"ResourcePatch", "ResourcePatch",
"BytecodePatch", "BytecodePatch",
"BytecodePatch", "BytecodePatch",
"BytecodePatch" "BytecodePatch",
"ResourcePatch"
], ],
"compatiblePackages": { "compatiblePackages": {
"com.google.android.youtube": [ "com.google.android.youtube": [
@ -2050,7 +2115,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -2103,7 +2169,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -2143,7 +2210,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -2163,7 +2231,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -2208,7 +2277,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -2236,7 +2306,7 @@
}, },
{ {
"name": "Sanitize sharing links", "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, "use": true,
"dependencies": [ "dependencies": [
"BytecodePatch", "BytecodePatch",
@ -2249,14 +2319,15 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
}, },
{ {
"name": "Sanitize sharing links", "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, "use": true,
"dependencies": [ "dependencies": [
"Settings for Reddit" "Settings for Reddit"
@ -2268,7 +2339,7 @@
}, },
{ {
"name": "Sanitize sharing links", "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, "use": true,
"dependencies": [ "dependencies": [
"BytecodePatch", "BytecodePatch",
@ -2416,7 +2487,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [ "options": [
@ -2445,6 +2517,8 @@
"BytecodePatch", "BytecodePatch",
"BytecodePatch", "BytecodePatch",
"BytecodePatch", "BytecodePatch",
"BytecodePatch",
"BytecodePatch",
"ResourcePatch", "ResourcePatch",
"BytecodePatch", "BytecodePatch",
"ResourcePatch", "ResourcePatch",
@ -2463,6 +2537,88 @@
}, },
"options": [] "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", "name": "SponsorBlock",
"description": "Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections.", "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.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -2594,7 +2751,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -2669,7 +2827,7 @@
"key": "darkThemeBackgroundColor", "key": "darkThemeBackgroundColor",
"title": "Dark theme background color", "title": "Dark theme background color",
"description": "Can be a hex color (#AARRGGBB) or a color resource reference.", "description": "Can be a hex color (#AARRGGBB) or a color resource reference.",
"required": false, "required": true,
"type": "kotlin.String", "type": "kotlin.String",
"default": "@android:color/black", "default": "@android:color/black",
"values": { "values": {
@ -2688,7 +2846,7 @@
"key": "lightThemeBackgroundColor", "key": "lightThemeBackgroundColor",
"title": "Light theme background color", "title": "Light theme background color",
"description": "Can be a hex color (#AARRGGBB) or a color resource reference.", "description": "Can be a hex color (#AARRGGBB) or a color resource reference.",
"required": false, "required": true,
"type": "kotlin.String", "type": "kotlin.String",
"default": "@android:color/white", "default": "@android:color/white",
"values": { "values": {
@ -2790,7 +2948,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [ "options": [
@ -2839,7 +2998,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [] "options": []
@ -2933,7 +3093,8 @@
"6.42.55", "6.42.55",
"6.51.53", "6.51.53",
"7.16.53", "7.16.53",
"7.25.53" "7.25.53",
"8.02.53"
] ]
}, },
"options": [ "options": [

View File

@ -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 final class app/revanced/patches/music/general/redirection/DislikeRedirectionPatchKt {
public static final fun getDislikeRedirectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; 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 { 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 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 final class app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatchKt {
public static final fun getCustomBrandingIconPatch ()Lapp/revanced/patcher/patch/ResourcePatch; 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_20_or_greater ()Z
public static final fun is_7_23_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_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 { 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 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 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 { 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 getSettingsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
public static final fun is_2024_26_or_greater ()Z 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_2024_41_or_greater ()Z
public static final fun is_2025_01_or_greater ()Z
} }
public final class app/revanced/patches/shared/FingerprintsKt { 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 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 final class app/revanced/patches/youtube/general/loadingscreen/GradientLoadingScreenPatchKt {
public static final fun getGradientLoadingScreenPatch ()Lapp/revanced/patcher/patch/BytecodePatch; 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 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 final class app/revanced/patches/youtube/general/splashanimation/SplashAnimationPatchKt {
public static final fun getSplashAnimationPatch ()Lapp/revanced/patcher/patch/BytecodePatch; 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 static final fun getDebuggingPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/youtube/misc/externalbrowser/OpenLinksExternallyPatchKt { public final class app/revanced/patches/youtube/misc/openlinks/directly/OpenLinksDirectlyPatchKt {
public static final fun getOpenLinksExternallyPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getOpenLinksDirectlyPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/youtube/misc/openlinksdirectly/OpenLinksDirectlyPatchKt { public final class app/revanced/patches/youtube/misc/openlinks/externally/OpenLinksExternallyPatchKt {
public static final fun getOpenLinksDirectlyPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getOpenLinksExternallyPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/youtube/misc/quic/QUICProtocolPatchKt { 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 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 final class app/revanced/patches/youtube/utils/extension/SharedExtensionPatchKt {
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; 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_43_or_greater ()Z
public static final fun is_19_44_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_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 { 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 getDrawerResults ()J
public static final fun getEasySeekEduContainer ()J public static final fun getEasySeekEduContainer ()J
public static final fun getEditSettingsAction ()J public static final fun getEditSettingsAction ()J
public static final fun getElementsImage ()J
public static final fun getEmojiPickerIcon ()J public static final fun getEmojiPickerIcon ()J
public static final fun getEndScreenElementLayoutCircle ()J public static final fun getEndScreenElementLayoutCircle ()J
public static final fun getEndScreenElementLayoutIcon ()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 getImageOnlyTab ()J
public static final fun getInlineTimeBarColorizedBarPlayedColorDark ()J public static final fun getInlineTimeBarColorizedBarPlayedColorDark ()J
public static final fun getInlineTimeBarPlayedNotHighlightedColor ()J public static final fun getInlineTimeBarPlayedNotHighlightedColor ()J
public static final fun getInsetElementsWrapper ()J
public static final fun getInsetOverlayViewLayout ()J public static final fun getInsetOverlayViewLayout ()J
public static final fun getInterstitialsContainer ()J public static final fun getInterstitialsContainer ()J
public static final fun getMenuItemView ()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 getYtOutlineXWhite ()J
public static final fun getYtPremiumWordMarkHeader ()J public static final fun getYtPremiumWordMarkHeader ()J
public static final fun getYtWordMarkHeader ()J public static final fun getYtWordMarkHeader ()J
public static final fun getYtYoutubeMagenta ()J
} }
public final class app/revanced/patches/youtube/utils/returnyoutubedislike/ReturnYouTubeDislikePatchKt { 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 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 abstract class app/revanced/patches/youtube/video/playerresponse/Hook {
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun toString ()Ljava/lang/String; public fun toString ()Ljava/lang/String;

View File

@ -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.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.DISABLE_DISLIKE_REDIRECTION import app.revanced.patches.music.utils.patch.PatchList.DISABLE_DISLIKE_REDIRECTION
import app.revanced.patches.music.utils.pendingIntentReceiverFingerprint 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.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference 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.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction 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.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.Reference
var onClickReference = ""
@Suppress("unused") @Suppress("unused")
val dislikeRedirectionPatch = bytecodePatch( val dislikeRedirectionPatch = bytecodePatch(
@ -32,11 +35,12 @@ val dislikeRedirectionPatch = bytecodePatch(
) { ) {
compatibleWith(COMPATIBLE_PACKAGE) compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch) dependsOn(
settingsPatch,
versionCheckPatch,
)
execute { execute {
lateinit var onClickReference: Reference
pendingIntentReceiverFingerprint.methodOrThrow().apply { pendingIntentReceiverFingerprint.methodOrThrow().apply {
val startIndex = indexOfFirstStringInstructionOrThrow("YTM Dislike") val startIndex = indexOfFirstStringInstructionOrThrow("YTM Dislike")
val onClickRelayIndex = val onClickRelayIndex =
@ -49,27 +53,35 @@ val dislikeRedirectionPatch = bytecodePatch(
val onClickMethod = getWalkerMethod(onClickMethodIndex) val onClickMethod = getWalkerMethod(onClickMethodIndex)
onClickMethod.apply { onClickMethod.apply {
val onClickIndex = indexOfFirstInstructionOrThrow { val relativeIndex = indexOfFirstInstructionOrThrow {
val reference = opcode == Opcode.INVOKE_VIRTUAL &&
((this as? ReferenceInstruction)?.reference as? MethodReference) getReference<MethodReference>()
?.parameterTypes
?.contains("Ljava/util/Map;") == true
}
val onClickIndex = indexOfFirstInstructionOrThrow(relativeIndex) {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_INTERFACE && opcode == Opcode.INVOKE_INTERFACE &&
reference?.returnType == "V" && reference?.returnType == "V" &&
reference.parameterTypes.size == 1 reference.parameterTypes.size == 1
} }
onClickReference = onClickReference =
getInstruction<ReferenceInstruction>(onClickIndex).reference getInstruction<ReferenceInstruction>(onClickIndex).reference.toString()
disableDislikeRedirection(onClickIndex) disableDislikeRedirection(onClickIndex)
} }
} }
} }
dislikeButtonOnClickListenerFingerprint.methodOrThrow().apply { if (is_7_29_or_greater) {
val onClickIndex = indexOfFirstInstructionOrThrow { dislikeButtonOnClickListenerAlternativeFingerprint
getReference<MethodReference>()?.toString() == onClickReference.toString() .methodOrThrow()
} .disableDislikeRedirection()
disableDislikeRedirection(onClickIndex) } else {
dislikeButtonOnClickListenerFingerprint
.methodOrThrow()
.disableDislikeRedirection()
} }
addSwitchPreference( 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 targetIndex = indexOfFirstInstructionReversedOrThrow(onClickIndex, Opcode.IF_EQZ)
val insertRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA val insertRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA

View File

@ -4,6 +4,8 @@ import app.revanced.util.containsLiteralInstruction
import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags 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( internal val dislikeButtonOnClickListenerFingerprint = legacyFingerprint(
name = "dislikeButtonOnClickListenerFingerprint", 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"
}
)

View File

@ -2,7 +2,6 @@ package app.revanced.patches.music.general.startpage
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE 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.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.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.settingsPatch import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.fingerprint.matchOrThrow import app.revanced.util.fingerprint.methodOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction 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") @Suppress("unused")
val changeStartPagePatch = bytecodePatch( val changeStartPagePatch = bytecodePatch(
@ -25,20 +29,20 @@ val changeStartPagePatch = bytecodePatch(
execute { execute {
coldStartUpFingerprint.matchOrThrow().let { coldStartUpFingerprint.methodOrThrow().apply {
it.method.apply { val defaultBrowseIdIndex = indexOfFirstStringInstructionOrThrow(DEFAULT_BROWSE_ID)
val targetIndex = it.patternMatch!!.endIndex val browseIdIndex = indexOfFirstInstructionReversedOrThrow(defaultBrowseIdIndex) {
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA opcode == Opcode.IGET_OBJECT &&
getReference<FieldReference>()?.type == "Ljava/lang/String;"
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)
} }
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( addPreferenceWithIntent(

View File

@ -5,16 +5,17 @@ import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
const val DEFAULT_BROWSE_ID = "FEmusic_home"
internal val coldStartUpFingerprint = legacyFingerprint( internal val coldStartUpFingerprint = legacyFingerprint(
name = "coldStartUpFingerprint", name = "coldStartUpFingerprint",
returnType = "Ljava/lang/String;", returnType = "Ljava/lang/String;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(), parameters = emptyList(),
opcodes = listOf( opcodes = listOf(
Opcode.GOTO,
Opcode.CONST_STRING, Opcode.CONST_STRING,
Opcode.RETURN_OBJECT Opcode.RETURN_OBJECT
), ),
strings = listOf("FEmusic_library_sideloaded_tracks", "FEmusic_home") strings = listOf("FEmusic_library_sideloaded_tracks", DEFAULT_BROWSE_ID)
) )

View File

@ -4,8 +4,10 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption import app.revanced.patcher.patch.stringOption
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE 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.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_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.playservice.versionCheckPatch
import app.revanced.patches.music.utils.resourceid.actionBarLogo import app.revanced.patches.music.utils.resourceid.actionBarLogo
import app.revanced.patches.music.utils.resourceid.actionBarLogoRingo2 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.getIconType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT
import app.revanced.util.ResourceGroup import app.revanced.util.ResourceGroup
import app.revanced.util.Utils.printWarn import app.revanced.util.Utils.printWarn
import app.revanced.util.Utils.trimIndentMultiline import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.copyFile import app.revanced.util.copyFile
import app.revanced.util.copyResources import app.revanced.util.copyResources
import app.revanced.util.doRecursively
import app.revanced.util.replaceLiteralInstructionCall import app.revanced.util.replaceLiteralInstructionCall
import app.revanced.util.underBarOrThrow import app.revanced.util.underBarOrThrow
import app.revanced.util.valueOrThrow import app.revanced.util.valueOrThrow
import org.w3c.dom.Element
private const val DEFAULT_HEADER_KEY = "Custom branding icon" private const val DEFAULT_HEADER_KEY = "Custom branding icon"
private const val DEFAULT_HEADER_VALUE = "custom_branding_icon" private const val DEFAULT_HEADER_VALUE = "custom_branding_icon"
@ -125,11 +130,26 @@ private val changeHeaderBytecodePatch = bytecodePatch(
return@execute return@execute
} }
listOf( if (actionBarLogoRingo2 == -1L || ytmLogoRingo2 == -1L) {
actionBarLogoRingo2 to actionBarLogo, printWarn("Target resource not found!")
ytmLogoRingo2 to ytmLogo, return@execute
).forEach { (originalResource, replacementResource) -> }
replaceLiteralInstructionCall(originalResource, replacementResource)
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) 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) updatePatchStatus(CUSTOM_HEADER_FOR_YOUTUBE_MUSIC)
} }

View File

@ -22,7 +22,9 @@ internal val audioVideoSwitchToggleConstructorFingerprint = legacyFingerprint(
internal fun indexOfAudioVideoSwitchSetOnClickListenerInstruction(method: Method) = internal fun indexOfAudioVideoSwitchSetOnClickListenerInstruction(method: Method) =
method.indexOfFirstInstruction { method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL && 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( internal val snackBarParentFingerprint = legacyFingerprint(

View File

@ -40,6 +40,7 @@ val cairoSplashAnimationPatch = bytecodePatch(
"7.06.54", "7.06.54",
"7.16.53", "7.16.53",
"7.25.53", "7.25.53",
"8.02.53",
), ),
) )

View File

@ -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.resourceid.text1
import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus 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.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.fingerprint.matchOrThrow import app.revanced.util.fingerprint.matchOrThrow
@ -62,7 +63,7 @@ val navigationBarComponentsPatch = bytecodePatch(
execute { execute {
/** /**
* Enable black navigation bar * Enable custom navigation bar color
*/ */
tabLayoutFingerprint.methodOrThrow().apply { tabLayoutFingerprint.methodOrThrow().apply {
val constIndex = indexOfFirstLiteralInstructionOrThrow(colorGrey) val constIndex = indexOfFirstLiteralInstructionOrThrow(colorGrey)
@ -74,7 +75,7 @@ val navigationBarComponentsPatch = bytecodePatch(
addInstructions( addInstructions(
insertIndex, """ insertIndex, """
invoke-static {}, $NAVIGATION_CLASS_DESCRIPTOR->enableBlackNavigationBar()I invoke-static {}, $NAVIGATION_CLASS_DESCRIPTOR->enableCustomNavigationBarColor()I
move-result v$insertRegister move-result v$insertRegister
""" """
) )
@ -129,8 +130,13 @@ val navigationBarComponentsPatch = bytecodePatch(
addSwitchPreference( addSwitchPreference(
CategoryType.NAVIGATION, CategoryType.NAVIGATION,
"revanced_enable_black_navigation_bar", "revanced_enable_custom_navigation_bar_color",
"true" "false"
)
addPreferenceWithIntent(
CategoryType.NAVIGATION,
"revanced_custom_navigation_bar_color_value",
"revanced_enable_custom_navigation_bar_color"
) )
addSwitchPreference( addSwitchPreference(
CategoryType.NAVIGATION, CategoryType.NAVIGATION,

View File

@ -23,7 +23,7 @@ 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.MethodReference
const val AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY = 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( internal val audioVideoSwitchToggleFingerprint = legacyFingerprint(
name = "audioVideoSwitchToggleFingerprint", name = "audioVideoSwitchToggleFingerprint",
@ -33,7 +33,9 @@ internal val audioVideoSwitchToggleFingerprint = legacyFingerprint(
customFingerprint = { method, _ -> customFingerprint = { method, _ ->
method.indexOfFirstInstruction { method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL && opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.toString() == AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY getReference<MethodReference>()
?.toString()
?.endsWith(AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY) == true
} >= 0 } >= 0
} }
) )

View File

@ -799,7 +799,7 @@ val playerComponentsPatch = bytecodePatch(
val reference = (instruction as? ReferenceInstruction)?.reference val reference = (instruction as? ReferenceInstruction)?.reference
instruction.opcode == Opcode.INVOKE_VIRTUAL && instruction.opcode == Opcode.INVOKE_VIRTUAL &&
reference is MethodReference && reference is MethodReference &&
reference.toString() == AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY reference.toString().endsWith(AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY)
} }
.map { (index, _) -> index } .map { (index, _) -> index }
.reversed() .reversed()
@ -995,7 +995,7 @@ val playerComponentsPatch = bytecodePatch(
val reference = getReference<MethodReference>() val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_INTERFACE && opcode == Opcode.INVOKE_INTERFACE &&
reference?.returnType == "Z" && reference?.returnType == "Z" &&
reference.parameterTypes.size == 0 reference.parameterTypes.isEmpty()
} + 1 } + 1
val targetRegister = val targetRegister =
getInstruction<OneRegisterInstruction>(targetIndex).registerA getInstruction<OneRegisterInstruction>(targetIndex).registerA

View File

@ -14,7 +14,8 @@ internal object Constants {
"6.42.55", // This is the latest version that supports Android 7.0 "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 "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.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.
) )
) )
} }

View File

@ -31,7 +31,7 @@ internal enum class PatchList(
), ),
CUSTOM_BRANDING_NAME_FOR_YOUTUBE_MUSIC( CUSTOM_BRANDING_NAME_FOR_YOUTUBE_MUSIC(
"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(
"Custom header for YouTube Music", "Custom header for YouTube Music",
@ -63,7 +63,7 @@ internal enum class PatchList(
), ),
ENABLE_OPUS_CODEC( ENABLE_OPUS_CODEC(
"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(
"Enable debug logging", "Enable debug logging",
@ -135,7 +135,7 @@ internal enum class PatchList(
), ),
SANITIZE_SHARING_LINKS( SANITIZE_SHARING_LINKS(
"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(
"Settings for YouTube Music", "Settings for YouTube Music",

View File

@ -27,6 +27,10 @@ var is_7_23_or_greater = false
private set private set
var is_7_25_or_greater = false var is_7_25_or_greater = false
private set private set
var is_7_27_or_greater = false
private set
var is_7_29_or_greater = false
private set
val versionCheckPatch = resourcePatch( val versionCheckPatch = resourcePatch(
description = "versionCheckPatch", description = "versionCheckPatch",
@ -53,5 +57,7 @@ val versionCheckPatch = resourcePatch(
is_7_20_or_greater = 243899000 <= playStoreServicesVersion is_7_20_or_greater = 243899000 <= playStoreServicesVersion
is_7_23_or_greater = 244199000 <= playStoreServicesVersion is_7_23_or_greater = 244199000 <= playStoreServicesVersion
is_7_25_or_greater = 244399000 <= playStoreServicesVersion is_7_25_or_greater = 244399000 <= playStoreServicesVersion
is_7_27_or_greater = 244515000 <= playStoreServicesVersion
is_7_29_or_greater = 244799000 <= playStoreServicesVersion
} }
} }

View File

@ -1,26 +1,33 @@
package app.revanced.patches.music.utils.videotype package app.revanced.patches.music.utils.videotype
import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.or import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val videoTypeFingerprint = legacyFingerprint( internal val videoTypeFingerprint = legacyFingerprint(
name = "videoTypeFingerprint", name = "videoTypeFingerprint",
returnType = "L", returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("L"), parameters = listOf("L"),
opcodes = listOf( customFingerprint = { method, _ ->
Opcode.IGET, indexOfGetEnumInstruction(method) >= 0
Opcode.INVOKE_STATIC, }
Opcode.MOVE_RESULT_OBJECT,
Opcode.IF_NEZ,
Opcode.SGET_OBJECT,
Opcode.GOTO,
Opcode.SGET_OBJECT
)
) )
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( internal val videoTypeParentFingerprint = legacyFingerprint(
name = "videoTypeParentFingerprint", name = "videoTypeParentFingerprint",
returnType = "Z", returnType = "Z",

View File

@ -4,8 +4,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.extension.Constants.UTILS_PATH 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.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 = private const val EXTENSION_CLASS_DESCRIPTOR =
"$UTILS_PATH/VideoTypeHookPatch;" "$UTILS_PATH/VideoTypeHookPatch;"
@ -17,22 +23,27 @@ val videoTypeHookPatch = bytecodePatch(
execute { execute {
videoTypeFingerprint.matchOrThrow(videoTypeParentFingerprint).let { videoTypeFingerprint.methodOrThrow(videoTypeParentFingerprint).apply {
it.method.apply { val getEnumIndex = indexOfGetEnumInstruction(this)
val insertIndex = it.patternMatch!!.startIndex + 3 val enumClass = (getInstruction<ReferenceInstruction>(getEnumIndex).reference as MethodReference).definingClass
val referenceIndex = insertIndex + 1 val referenceIndex = indexOfFirstInstructionOrThrow(getEnumIndex) {
val referenceInstruction = opcode == Opcode.SGET_OBJECT &&
getInstruction<ReferenceInstruction>(referenceIndex).reference getReference<FieldReference>()?.type == enumClass
addInstructionsWithLabels(
insertIndex, """
if-nez p0, :dismiss
sget-object p0, $referenceInstruction
:dismiss
invoke-static {p0}, $EXTENSION_CLASS_DESCRIPTOR->setVideoType(Ljava/lang/Enum;)V
"""
)
} }
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
"""
)
} }
} }
} }

View File

@ -26,12 +26,6 @@ internal val videoIdFingerprint = legacyFingerprint(
returnType = "V", returnType = "V",
parameters = listOf("L", "Ljava/lang/String;"), parameters = listOf("L", "Ljava/lang/String;"),
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, 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") strings = listOf("Null initialPlayabilityStatus")
) )

View File

@ -191,7 +191,12 @@ val videoInformationPatch = bytecodePatch(
*/ */
videoIdFingerprint.matchOrThrow().let { videoIdFingerprint.matchOrThrow().let {
it.method.apply { 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 = PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR =
getInstruction(playerResponseModelIndex) getInstruction(playerResponseModelIndex)

View File

@ -2,9 +2,19 @@ package app.revanced.patches.music.video.playerresponse
import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or import app.revanced.util.or
import app.revanced.util.parametersEqual
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode 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. * For targets 7.03 and later.
*/ */
@ -12,23 +22,21 @@ internal val playerParameterBuilderFingerprint = legacyFingerprint(
name = "playerParameterBuilderFingerprint", name = "playerParameterBuilderFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "L", returnType = "L",
parameters = listOf( strings = listOf("psps"),
"Ljava/lang/String;", // VideoId. customFingerprint = custom@{ method, _ ->
"[B", val parameterTypes = method.parameterTypes
"Ljava/lang/String;", // Player parameters proto buffer. val parameterSize = parameterTypes.size
"Ljava/lang/String;", // PlaylistId. if (parameterSize < 13) {
"I", // PlaylistIndex. return@custom false
"I", }
"L",
"Ljava/util/Set;", val startsWithMethodParameterList = parameterTypes.slice(0..5)
"Ljava/lang/String;",
"Ljava/lang/String;", parametersEqual(
"L", PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST,
"Z", startsWithMethodParameterList
"Z", )
"Z", // Appears to indicate if the video id is being opened or is currently playing. }
),
strings = listOf("psps")
) )
/** /**

View File

@ -3,11 +3,11 @@ package app.revanced.patches.reddit.layout.subredditdialog
import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.indexOfFirstInstructionReversed
import app.revanced.util.or import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
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.MethodReference
internal val frequentUpdatesSheetScreenFingerprint = legacyFingerprint( internal val frequentUpdatesSheetScreenFingerprint = legacyFingerprint(
@ -26,38 +26,49 @@ internal val frequentUpdatesSheetScreenFingerprint = legacyFingerprint(
} }
) )
internal val frequentUpdatesSheetV2ScreenFingerprint = legacyFingerprint( internal val frequentUpdatesHandlerFingerprint = legacyFingerprint(
name = "frequentUpdatesSheetV2ScreenFingerprint", name = "frequentUpdatesHandlerFingerprint",
returnType = "V", returnType = "Ljava/lang/Object;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
strings = listOf("subreddit_name"), strings = listOf("subreddit_name"),
customFingerprint = { method, classDef -> 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( fun listOfIsLoggedInInstruction(method: Method) =
name = "frequentUpdatesSheetV2ScreenInvokeFingerprint", method.implementation?.instructions
returnType = "V", ?.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, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
opcodes = listOf( strings = listOf("reddit://reddit/r/", "nsfwAlertDelegate"),
Opcode.IGET_OBJECT, customFingerprint = { method, _ ->
Opcode.INVOKE_VIRTUAL, method.name == "emit" &&
Opcode.RETURN_VOID, indexOfHasBeenVisitedInstruction(method) >= 0
),
customFingerprint = { method, classDef ->
classDef.type.startsWith("Lcom/reddit/screens/pager/v2/FrequentUpdatesSheetV2Screen${'$'}SheetContent${'$'}") &&
method.name == "invoke" &&
indexOfDismissScreenInstruction(method) >= 0
} }
) )
fun indexOfDismissScreenInstruction(method: Method) = fun indexOfHasBeenVisitedInstruction(method: Method) =
method.indexOfFirstInstructionReversed { method.indexOfFirstInstruction {
val reference = getReference<MethodReference>() val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_VIRTUAL && opcode == Opcode.INVOKE_VIRTUAL &&
reference?.returnType == "V" && reference?.name == "getHasBeenVisited" &&
reference.parameterTypes.isEmpty() reference.returnType == "Z"
} }
internal val redditAlertDialogsFingerprint = legacyFingerprint( internal val redditAlertDialogsFingerprint = legacyFingerprint(

View File

@ -3,15 +3,14 @@ package app.revanced.patches.reddit.layout.subredditdialog
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE 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.extension.Constants.PATCHES_PATH
import app.revanced.patches.reddit.utils.patch.PatchList.REMOVE_SUBREDDIT_DIALOG 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_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.settingsPatch
import app.revanced.patches.reddit.utils.settings.updatePatchStatus import app.revanced.patches.reddit.utils.settings.updatePatchStatus
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow 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.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction 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.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.MethodReference
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR =
@ -36,6 +34,25 @@ val subRedditDialogPatch = bytecodePatch(
execute { 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 { frequentUpdatesSheetScreenFingerprint.methodOrThrow().apply {
val index = indexOfFirstInstructionReversedOrThrow(Opcode.RETURN_OBJECT) val index = indexOfFirstInstructionReversedOrThrow(Opcode.RETURN_OBJECT)
val register = val register =
@ -43,42 +60,27 @@ val subRedditDialogPatch = bytecodePatch(
addInstruction( addInstruction(
index, 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) { if (is_2025_01_or_greater) {
val dismissReference = with (frequentUpdatesSheetV2ScreenInvokeFingerprint.methodOrThrow()) { nsfwAlertEmitFingerprint.methodOrThrow().apply {
val index = indexOfDismissScreenInstruction(this) val hasBeenVisitedIndex = indexOfHasBeenVisitedInstruction(this)
getInstruction<ReferenceInstruction>(index).reference as MethodReference 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. // Not used in latest Reddit client.
redditAlertDialogsFingerprint.second.methodOrNull?.apply { redditAlertDialogsFingerprint.methodOrThrow().apply {
val backgroundTintIndex = indexOfSetBackgroundTintListInstruction(this) val backgroundTintIndex = indexOfSetBackgroundTintListInstruction(this)
val insertIndex = val insertIndex =
indexOfFirstInstructionOrThrow(backgroundTintIndex) { indexOfFirstInstructionOrThrow(backgroundTintIndex) {

View File

@ -11,7 +11,7 @@ internal enum class PatchList(
), ),
CUSTOM_BRANDING_NAME_FOR_REDDIT( CUSTOM_BRANDING_NAME_FOR_REDDIT(
"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(
"Disable screenshot popup", "Disable screenshot popup",
@ -55,7 +55,7 @@ internal enum class PatchList(
), ),
SANITIZE_SHARING_LINKS( SANITIZE_SHARING_LINKS(
"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(
"Settings for Reddit", "Settings for Reddit",

View File

@ -37,6 +37,8 @@ var is_2024_26_or_greater = false
private set private set
var is_2024_41_or_greater = false var is_2024_41_or_greater = false
private set private set
var is_2025_01_or_greater = false
private set
private val settingsBytecodePatch = bytecodePatch( private val settingsBytecodePatch = bytecodePatch(
description = "settingsBytecodePatch" description = "settingsBytecodePatch"
@ -58,7 +60,8 @@ private val settingsBytecodePatch = bytecodePatch(
.replace(".", "").toInt() .replace(".", "").toInt()
is_2024_26_or_greater = 2024260 <= versionNumber 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
} }
/** /**

View File

@ -30,13 +30,15 @@ val drawableColorHookPatch = bytecodePatch(
} }
internal fun addDrawableColorHook( internal fun addDrawableColorHook(
methodDescriptor: String methodDescriptor: String,
highPriority: Boolean = false
) { ) {
insertMethod.addInstructions( insertMethod.addInstructions(
insertIndex + offset, """ if (highPriority) insertIndex else insertIndex + offset,
invoke-static {v$insertRegister}, $methodDescriptor """
move-result v$insertRegister invoke-static {v$insertRegister}, $methodDescriptor
""" move-result v$insertRegister
"""
) )
offset += 2 offset += 2
} }

View File

@ -389,6 +389,9 @@ private object Constants {
// misc // misc
"com.google.android.gms.clearcut.service.START", "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.languageprofile.service.START",
"com.google.android.gms.measurement.START", "com.google.android.gms.measurement.START",
"com.google.android.gms.pseudonymous.service.START", "com.google.android.gms.pseudonymous.service.START",

View File

@ -16,6 +16,7 @@ import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.indexOfFirstStringInstruction
import app.revanced.util.indexOfFirstStringInstructionOrThrow import app.revanced.util.indexOfFirstStringInstructionOrThrow
import app.revanced.util.or import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags 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.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference 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.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.util.MethodUtil import com.android.tools.smali.dexlib2.util.MethodUtil
@ -42,6 +44,8 @@ private var filterCount = 0
internal lateinit var addLithoFilter: (String) -> Unit internal lateinit var addLithoFilter: (String) -> Unit
private set private set
internal var emptyComponentLabel = ""
val lithoFilterPatch = bytecodePatch( val lithoFilterPatch = bytecodePatch(
description = "lithoFilterPatch", description = "lithoFilterPatch",
) { ) {
@ -73,6 +77,8 @@ val lithoFilterPatch = bytecodePatch(
return-object v0 return-object v0
""" """
emptyComponentLabel = label
Pair(this, label) Pair(this, label)
} }
} }
@ -121,17 +127,25 @@ val lithoFilterPatch = bytecodePatch(
val stringBuilderRegister = val stringBuilderRegister =
getInstruction<TwoRegisterInstruction>(stringBuilderIndex).registerA 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>( val identifierRegister = getInstruction<TwoRegisterInstruction>(
indexOfFirstInstructionReversedOrThrow(emptyStringIndex) { indexOfFirstInstructionReversedOrThrow(relativeIndex) {
opcode == Opcode.IPUT_OBJECT opcode == Opcode.IPUT_OBJECT
&& getReference<FieldReference>()?.type == "Ljava/lang/String;" && getReference<FieldReference>()?.type == "Ljava/lang/String;"
} }
).registerA ).registerA
val objectRegister = getInstruction<FiveRegisterInstruction>( val objectRegister = getInstruction<FiveRegisterInstruction>(
indexOfFirstInstructionOrThrow(emptyStringIndex) { indexOfFirstInstructionOrThrow(relativeIndex, Opcode.INVOKE_VIRTUAL)
opcode == Opcode.INVOKE_VIRTUAL
}
).registerC ).registerC
val insertIndex = stringBuilderIndex + 1 val insertIndex = stringBuilderIndex + 1

View File

@ -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.extension.Constants.SPOOF_PATH
import app.revanced.patches.shared.formatStreamModelConstructorFingerprint import app.revanced.patches.shared.formatStreamModelConstructorFingerprint
import app.revanced.util.findInstructionIndicesReversedOrThrow import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.definingClassOrThrow import app.revanced.util.fingerprint.definingClassOrThrow
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
import app.revanced.util.fingerprint.matchOrThrow import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.mutableClassOrThrow
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -370,35 +367,4 @@ fun baseSpoofStreamingDataPatch(
executeBlock() 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
"""
)
}
}
}
} }

View File

@ -198,20 +198,4 @@ internal val hlsCurrentTimeFingerprint = legacyFingerprint(
literals = listOf(HLS_CURRENT_TIME_FEATURE_FLAG), 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")
)

View File

@ -16,6 +16,7 @@ import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction 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.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction 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.FieldReference
@ -40,11 +41,11 @@ val textComponentPatch = bytecodePatch(
spannedIndex = indexOfSpannableStringInstruction(this) spannedIndex = indexOfSpannableStringInstruction(this)
spannedRegister = getInstruction<FiveRegisterInstruction>(spannedIndex).registerC spannedRegister = getInstruction<FiveRegisterInstruction>(spannedIndex).registerC
spannedContextRegister = spannedContextRegister =
getInstruction<TwoRegisterInstruction>(0).registerA getInstruction<OneRegisterInstruction>(spannedIndex + 1).registerA
replaceInstruction( replaceInstruction(
spannedIndex, spannedIndex,
"nop" "move-object/from16 v$spannedContextRegister, p0"
) )
addInstruction( addInstruction(
++spannedIndex, ++spannedIndex,

View File

@ -49,6 +49,7 @@ fun ResourcePatchContext.baseTranslationsPatch(
sourceDirectory: String, sourceDirectory: String,
) { ) {
val resourceDirectory = get("res") val resourceDirectory = get("res")
val isYouTube = sourceDirectory == "youtube"
// Check if the custom translation path is valid. // Check if the custom translation path is valid.
customTranslations?.takeIf { it.isNotEmpty() }?.let { customLang -> 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) // 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 // and also handle locales with "b+" prefix
val filteredAppLanguages = selectedStringResourcesArray.flatMap { language -> var filteredAppLanguages = (selectedStringResourcesArray + arrayOf("en"))
setOf(language, language.replace("-r", "-"), .map { language ->
language.replace("b+", "").replace("+", "-")) language.replace("-r", "-").replace("b+", "").replace("+", "-")
}.toTypedArray() }.toHashSet().toTypedArray()
// Remove unselected app languages from UI // Remove unselected app languages from UI
document("res/xml/locales_config.xml").use { document -> document("res/xml/locales_config.xml").use { document ->
@ -102,7 +103,7 @@ fun ResourcePatchContext.baseTranslationsPatch(
document.doRecursively { node -> document.doRecursively { node ->
if (node is Element && node.tagName == "locale") { if (node is Element && node.tagName == "locale") {
node.getAttributeNode("android:name")?.let { attribute -> node.getAttributeNode("android:name")?.let { attribute ->
if (attribute.textContent != "en" && attribute.textContent !in filteredAppLanguages) { if (attribute.textContent !in filteredAppLanguages) {
nodesToRemove.add(node) nodesToRemove.add(node)
} }
} }
@ -114,6 +115,45 @@ fun ResourcePatchContext.baseTranslationsPatch(
node.parentNode?.removeChild(node) 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)
}
}
}
}
} }
/** /**

View File

@ -7,13 +7,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.litho.addLithoFilter 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.shared.mainactivity.onCreateMethod
import app.revanced.patches.youtube.utils.bottomsheet.bottomSheetHookPatch import app.revanced.patches.youtube.utils.bottomsheet.bottomSheetHookPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE 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.COMPONENTS_PATH
import app.revanced.patches.youtube.utils.extension.Constants.FEED_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.extension.Constants.FEED_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.extension.Constants.FEED_PATH 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.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_FEED_COMPONENTS 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.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.bar
import app.revanced.patches.youtube.utils.resourceid.captionToggleContainer import app.revanced.patches.youtube.utils.resourceid.captionToggleContainer
import app.revanced.patches.youtube.utils.resourceid.channelListSubMenu 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.fingerprint.mutableClassOrThrow
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction 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.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction 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.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.StringReference 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 = private const val CAROUSEL_SHELF_FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/CarouselShelfFilter;" "$COMPONENTS_PATH/CarouselShelfFilter;"
@ -78,6 +79,8 @@ val feedComponentsPatch = bytecodePatch(
sharedResourceIdPatch, sharedResourceIdPatch,
settingsPatch, settingsPatch,
bottomSheetHookPatch, bottomSheetHookPatch,
engagementPanelHookPatch,
versionCheckPatch,
) )
execute { execute {
@ -171,38 +174,6 @@ val feedComponentsPatch = bytecodePatch(
// region patch for hide relative video // 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 { linearLayoutManagerItemCountsFingerprint.matchOrThrow().let {
val methodWalker = val methodWalker =
it.getWalkerMethod(it.patternMatch!!.endIndex) it.getWalkerMethod(it.patternMatch!!.endIndex)
@ -219,23 +190,28 @@ val feedComponentsPatch = bytecodePatch(
} }
} }
hookEngagementPanelState(RELATED_VIDEO_CLASS_DESCRIPTOR)
// endregion // endregion
// region patch for hide subscriptions channel section for tablet // region patch for hide subscriptions channel section for tablet
arrayOf( // Integrated as a litho component since YouTube 20.02.
channelListSubMenuTabletFingerprint, if (!is_20_02_or_greater) {
channelListSubMenuTabletSyntheticFingerprint arrayOf(
).forEach { fingerprint -> channelListSubMenuTabletFingerprint,
fingerprint.methodOrThrow().apply { channelListSubMenuTabletSyntheticFingerprint
addInstructionsWithLabels( ).forEach { fingerprint ->
0, """ fingerprint.methodOrThrow().apply {
invoke-static {}, $FEED_CLASS_DESCRIPTOR->hideSubscriptionsChannelSection()Z addInstructionsWithLabels(
move-result v0 0, """
if-eqz v0, :show invoke-static {}, $FEED_CLASS_DESCRIPTOR->hideSubscriptionsChannelSection()Z
return-void move-result v0
""", ExternalLabel("show", getInstruction(0)) if-eqz v0, :show
) return-void
""", ExternalLabel("show", getInstruction(0))
)
}
} }
} }
@ -287,30 +263,42 @@ val feedComponentsPatch = bytecodePatch(
it.method.apply { it.method.apply {
val freeRegister = implementation!!.registerCount - parameters.size - 2 val freeRegister = implementation!!.registerCount - parameters.size - 2
val insertIndex = indexOfFirstInstructionOrThrow { val insertIndex = indexOfFirstInstructionOrThrow {
val reference = ((this as? ReferenceInstruction)?.reference as? MethodReference) val reference = getReference<MethodReference>()
reference?.parameterTypes?.size == 1 && reference?.parameterTypes?.size == 1 &&
reference.parameterTypes.first() == "[B" && reference.parameterTypes.first() == "[B" &&
reference.returnType.startsWith("L") reference.returnType.startsWith("L")
} }
val objectIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_OBJECT) if (is_19_46_or_greater) {
val objectRegister = getInstruction<TwoRegisterInstruction>(objectIndex).registerA 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( addInstructionsWithLabels(
insertIndex, """ insertIndex, """
invoke-static {v$objectRegister, v$freeRegister}, $FEED_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z invoke-static {v$objectRegister, v$freeRegister}, $FEED_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
move-result v$freeRegister move-result v$freeRegister
if-nez v$freeRegister, :filter if-nez v$freeRegister, :filter
""", ExternalLabel("filter", getInstruction(jumpIndex)) """, ExternalLabel("filter", getInstruction(jumpIndex))
) )
addInstruction(
addInstruction( 0,
0, "move-object/from16 v$freeRegister, p3"
"move-object/from16 v$freeRegister, p3" )
) }
} }
} }

View File

@ -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.horizontalCardList
import app.revanced.patches.youtube.utils.resourceid.relatedChipCloudMargin import app.revanced.patches.youtube.utils.resourceid.relatedChipCloudMargin
import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.or import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val breakingNewsFingerprint = legacyFingerprint( internal val breakingNewsFingerprint = legacyFingerprint(
name = "breakingNewsFingerprint", name = "breakingNewsFingerprint",
@ -103,19 +100,6 @@ internal val elementParserParentFingerprint = legacyFingerprint(
strings = listOf("Element tree missing id in debug mode.") 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( internal val filterBarHeightFingerprint = legacyFingerprint(
name = "filterBarHeightFingerprint", name = "filterBarHeightFingerprint",
returnType = "V", returnType = "V",

View File

@ -69,16 +69,6 @@ internal val appBlockingCheckResultToStringFingerprint = legacyFingerprint(
strings = listOf("AppBlockingCheckResult{intent=") 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( internal val floatingMicrophoneFingerprint = legacyFingerprint(
name = "floatingMicrophoneFingerprint", name = "floatingMicrophoneFingerprint",
returnType = "V", returnType = "V",
@ -87,7 +77,6 @@ internal val floatingMicrophoneFingerprint = legacyFingerprint(
opcodes = listOf( opcodes = listOf(
Opcode.IGET_BOOLEAN, Opcode.IGET_BOOLEAN,
Opcode.IF_EQZ, Opcode.IF_EQZ,
Opcode.RETURN_VOID
), ),
literals = listOf(fab), literals = listOf(fab),
) )

View File

@ -2,12 +2,10 @@ package app.revanced.patches.youtube.general.components
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions 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.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch 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.addLithoFilter
import app.revanced.patches.shared.litho.lithoFilterPatch import app.revanced.patches.shared.litho.lithoFilterPatch
import app.revanced.patches.shared.settingmenu.settingsMenuPatch 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.is_19_25_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.resourceid.accountSwitcherAccessibility 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.resourceid.sharedResourceIdPatch
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.patches.youtube.utils.settings.settingsPatch
@ -154,18 +153,17 @@ val layoutComponentsPatch = bytecodePatch(
// region patch for hide floating microphone // region patch for hide floating microphone
floatingMicrophoneFingerprint.matchOrThrow().let { floatingMicrophoneFingerprint.methodOrThrow().apply {
it.method.apply { val literalIndex = indexOfFirstLiteralInstructionOrThrow(fab)
val insertIndex = it.patternMatch!!.startIndex val booleanIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.IGET_BOOLEAN)
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA val insertRegister = getInstruction<TwoRegisterInstruction>(booleanIndex).registerA
addInstructions( addInstructions(
insertIndex + 1, """ booleanIndex + 1, """
invoke-static {v$register}, $GENERAL_CLASS_DESCRIPTOR->hideFloatingMicrophone(Z)Z invoke-static {v$insertRegister}, $GENERAL_CLASS_DESCRIPTOR->hideFloatingMicrophone(Z)Z
move-result v$register move-result v$insertRegister
""" """
) )
}
} }
// endregion // endregion
@ -215,21 +213,6 @@ val layoutComponentsPatch = bytecodePatch(
// endregion // 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 // region patch for hide tooltip content
tooltipContentFullscreenFingerprint.methodOrThrow().apply { tooltipContentFullscreenFingerprint.methodOrThrow().apply {

View File

@ -6,7 +6,7 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.createPlayerRequestBodyWithModelFingerprint import app.revanced.patches.shared.createPlayerRequestBodyWithModelFingerprint
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE 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.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.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.util.fingerprint.definingClassOrThrow import app.revanced.util.fingerprint.definingClassOrThrow
@ -24,8 +24,8 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
@Suppress("unused") @Suppress("unused")
val layoutSwitchPatch = bytecodePatch( val layoutSwitchPatch = bytecodePatch(
LAYOUT_SWITCH.title, CHANGE_LAYOUT.title,
LAYOUT_SWITCH.summary, CHANGE_LAYOUT.summary,
) { ) {
compatibleWith(COMPATIBLE_PACKAGE) compatibleWith(COMPATIBLE_PACKAGE)
@ -71,9 +71,9 @@ val layoutSwitchPatch = bytecodePatch(
arrayOf( arrayOf(
"PREFERENCE_SCREEN: GENERAL", "PREFERENCE_SCREEN: GENERAL",
"PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS", "PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS",
"SETTINGS: LAYOUT_SWITCH" "SETTINGS: CHANGE_LAYOUT"
), ),
LAYOUT_SWITCH CHANGE_LAYOUT
) )
// endregion // endregion

View File

@ -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()
}

View File

@ -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
}
}

View File

@ -281,7 +281,7 @@ val miniplayerPatch = bytecodePatch(
"$EXTENSION_CLASS_DESCRIPTOR->setRoundedCorners(Z)Z" "$EXTENSION_CLASS_DESCRIPTOR->setRoundedCorners(Z)Z"
) )
settingArray += "SETTINGS: MINIPLAYER_ROUNDED_CONERS" settingArray += "SETTINGS: MINIPLAYER_ROUNDED_CORNERS"
} }
if (is_19_43_or_greater) { if (is_19_43_or_greater) {

View File

@ -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"
}

View File

@ -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
}
}

View File

@ -2,12 +2,14 @@ package app.revanced.patches.youtube.general.spoofappversion
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction 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.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.spoof.appversion.baseSpoofAppVersionPatch import app.revanced.patches.shared.spoof.appversion.baseSpoofAppVersionPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE 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.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.fix.cairo.cairoFragmentPatch
import app.revanced.patches.youtube.utils.indexOfGetDrawableInstruction import app.revanced.patches.youtube.utils.indexOfGetDrawableInstruction
import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_APP_VERSION 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.settings.settingsPatch
import app.revanced.patches.youtube.utils.toolBarButtonFingerprint import app.revanced.patches.youtube.utils.toolBarButtonFingerprint
import app.revanced.util.appendAppVersion import app.revanced.util.appendAppVersion
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
@ -69,6 +72,13 @@ private val spoofAppVersionBytecodePatch = bytecodePatch(
""", ExternalLabel("ignore", getInstruction(jumpIndex)) """, ExternalLabel("ignore", getInstruction(jumpIndex))
) )
} }
findMethodOrThrow(PATCH_STATUS_CLASS_DESCRIPTOR) {
name == "SpoofAppVersionDefaultString"
}.replaceInstruction(
0,
"const-string v0, \"18.38.45\""
)
} }
} }

View File

@ -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.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR 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.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.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.resourceid.actionBarRingoBackground import app.revanced.patches.youtube.utils.resourceid.actionBarRingoBackground
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
@ -265,7 +266,11 @@ val toolBarComponentsPatch = bytecodePatch(
// region patch for hide search term thumbnail // region patch for hide search term thumbnail
createSearchSuggestionsFingerprint.methodOrThrow().apply { 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) { val replaceIndex = indexOfFirstInstructionReversedOrThrow(relativeIndex) {
opcode == Opcode.INVOKE_VIRTUAL && opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.toString() == "Landroid/widget/ImageView;->setVisibility(I)V" getReference<MethodReference>()?.toString() == "Landroid/widget/ImageView;->setVisibility(I)V"

View File

@ -27,7 +27,7 @@ val sharedThemePatch = resourcePatch(
document(attrsResourceFile).use { document -> document(attrsResourceFile).use { document ->
(document.getElementsByTagName("resources").item(0) as Element).appendChild( (document.getElementsByTagName("resources").item(0) as Element).appendChild(
document.createElement("attr").apply { document.createElement("attr").apply {
setAttribute("format", "reference") setAttribute("format", "reference|color")
setAttribute("name", SPLASH_SCREEN_COLOR_NAME) setAttribute("name", SPLASH_SCREEN_COLOR_NAME)
} }
) )
@ -49,7 +49,7 @@ val sharedThemePatch = resourcePatch(
setAttribute( setAttribute(
"name", "name",
when (pathIndex) { when (pathIndex) {
0 -> "splashScreenColor" 0 -> SPLASH_SCREEN_COLOR_NAME
1 -> "android:windowSplashScreenBackground" 1 -> "android:windowSplashScreenBackground"
else -> "null" else -> "null"
} }
@ -59,8 +59,10 @@ val sharedThemePatch = resourcePatch(
document.createTextNode( document.createTextNode(
when (pathIndex) { when (pathIndex) {
0 -> when (nodeAttributeName) { 0 -> when (nodeAttributeName) {
"Base.Theme.YouTube.Launcher.Dark" -> "@color/yt_black1" "Base.Theme.YouTube.Launcher.Dark",
"Base.Theme.YouTube.Launcher.Light" -> "@color/yt_white1" "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" else -> "null"
} }

View File

@ -54,6 +54,7 @@ val themePatch = resourcePatch(
values = availableDarkTheme, values = availableDarkTheme,
title = "Dark theme background color", title = "Dark theme background color",
description = "Can be a hex color (#AARRGGBB) or a color resource reference.", description = "Can be a hex color (#AARRGGBB) or a color resource reference.",
required = true,
) )
val lightThemeBackgroundColor = stringOption( val lightThemeBackgroundColor = stringOption(
@ -62,6 +63,7 @@ val themePatch = resourcePatch(
values = availableLightTheme, values = availableLightTheme,
title = "Light theme background color", title = "Light theme background color",
description = "Can be a hex color (#AARRGGBB) or a color resource reference.", description = "Can be a hex color (#AARRGGBB) or a color resource reference.",
required = true,
) )
execute { execute {

View File

@ -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.fingerprint.legacyFingerprint
import app.revanced.util.or import app.revanced.util.or

View File

@ -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.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE 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.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.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.fingerprint.methodOrThrow
@ -17,8 +17,8 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Suppress("unused") @Suppress("unused")
val openLinksDirectlyPatch = bytecodePatch( val openLinksDirectlyPatch = bytecodePatch(
ENABLE_OPEN_LINKS_DIRECTLY.title, BYPASS_URL_REDIRECTS.title,
ENABLE_OPEN_LINKS_DIRECTLY.summary, BYPASS_URL_REDIRECTS.summary,
) { ) {
compatibleWith(COMPATIBLE_PACKAGE) compatibleWith(COMPATIBLE_PACKAGE)
@ -40,7 +40,7 @@ val openLinksDirectlyPatch = bytecodePatch(
replaceInstruction( replaceInstruction(
insertIndex, 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( addPreference(
arrayOf( arrayOf(
"SETTINGS: ENABLE_OPEN_LINKS_DIRECTLY" "SETTINGS: BYPASS_URL_REDIRECTS"
), ),
ENABLE_OPEN_LINKS_DIRECTLY BYPASS_URL_REDIRECTS
) )
// endregion // endregion

View File

@ -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.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.transformation.transformInstructionsPatch import app.revanced.patches.shared.transformation.transformInstructionsPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE 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.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.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.patches.youtube.utils.settings.settingsPatch
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@ -14,8 +14,8 @@ import com.android.tools.smali.dexlib2.iface.reference.StringReference
@Suppress("unused") @Suppress("unused")
val openLinksExternallyPatch = bytecodePatch( val openLinksExternallyPatch = bytecodePatch(
ENABLE_EXTERNAL_BROWSER.title, OPEN_LINKS_EXTERNALLY.title,
ENABLE_EXTERNAL_BROWSER.summary, OPEN_LINKS_EXTERNALLY.summary,
) { ) {
compatibleWith(COMPATIBLE_PACKAGE) compatibleWith(COMPATIBLE_PACKAGE)
@ -36,7 +36,7 @@ val openLinksExternallyPatch = bytecodePatch(
mutableMethod.addInstructions( mutableMethod.addInstructions(
intentStringIndex + 1, 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 move-result-object v$register
""", """,
) )
@ -51,9 +51,9 @@ val openLinksExternallyPatch = bytecodePatch(
addPreference( addPreference(
arrayOf( arrayOf(
"SETTINGS: ENABLE_EXTERNAL_BROWSER" "SETTINGS: OPEN_LINKS_EXTERNALLY"
), ),
ENABLE_EXTERNAL_BROWSER OPEN_LINKS_EXTERNALLY
) )
// endregion // endregion

View File

@ -1,16 +1,37 @@
package app.revanced.patches.youtube.player.action 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.patcher.patch.bytecodePatch
import app.revanced.patches.shared.litho.addLithoFilter 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.shared.litho.lithoFilterPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE 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.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.patch.PatchList.HIDE_ACTION_BUTTONS
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch 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 = private const val FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/ActionButtonsFilter;" "$COMPONENTS_PATH/ActionButtonsFilter;"
private const val ACTION_BUTTONS_CLASS_DESCRIPTOR =
"$PLAYER_PATH/ActionButtonsPatch;"
@Suppress("unused") @Suppress("unused")
val actionButtonsPatch = bytecodePatch( val actionButtonsPatch = bytecodePatch(
@ -22,18 +43,73 @@ val actionButtonsPatch = bytecodePatch(
dependsOn( dependsOn(
settingsPatch, settingsPatch,
lithoFilterPatch, 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 { execute {
addLithoFilter(FILTER_CLASS_DESCRIPTOR) 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 // region add settings
addPreference( addPreference(
arrayOf( settingArray,
"PREFERENCE_SCREEN: PLAYER",
"SETTINGS: HIDE_ACTION_BUTTONS"
),
HIDE_ACTION_BUTTONS HIDE_ACTION_BUTTONS
) )

View File

@ -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
}
)

View File

@ -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.fix.suggestedvideoendscreen.suggestedVideoEndScreenPatch
import app.revanced.patches.youtube.utils.patch.PatchList.PLAYER_COMPONENTS import app.revanced.patches.youtube.utils.patch.PatchList.PLAYER_COMPONENTS
import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch 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.darkBackground
import app.revanced.patches.youtube.utils.resourceid.fadeDurationFast import app.revanced.patches.youtube.utils.resourceid.fadeDurationFast
import app.revanced.patches.youtube.utils.resourceid.scrimOverlay import app.revanced.patches.youtube.utils.resourceid.scrimOverlay
@ -278,9 +280,15 @@ val playerComponentsPatch = bytecodePatch(
speedOverlayPatch, speedOverlayPatch,
suggestedVideoEndScreenPatch, suggestedVideoEndScreenPatch,
videoInformationPatch, videoInformationPatch,
versionCheckPatch,
) )
execute { execute {
var settingArray = arrayOf(
"PREFERENCE_SCREEN: PLAYER",
"SETTINGS: PLAYER_COMPONENTS"
)
fun MutableMethod.getAllLiteralComponent( fun MutableMethod.getAllLiteralComponent(
startIndex: Int, startIndex: Int,
endIndex: Int endIndex: Int
@ -563,30 +571,34 @@ val playerComponentsPatch = bytecodePatch(
) )
} }
youtubeControlsOverlayFingerprint.methodOrThrow().apply { if (!is_20_02_or_greater) {
val insertIndex = youtubeControlsOverlayFingerprint.methodOrThrow().apply {
indexOfFirstLiteralInstructionOrThrow(seekUndoEduOverlayStub) val insertIndex =
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA indexOfFirstLiteralInstructionOrThrow(seekUndoEduOverlayStub)
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
val onClickListenerIndex = indexOfFirstInstructionOrThrow(insertIndex) { val onClickListenerIndex = indexOfFirstInstructionOrThrow(insertIndex) {
opcode == Opcode.INVOKE_VIRTUAL && opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setOnClickListener" getReference<MethodReference>()?.name == "setOnClickListener"
} }
val constComponent = getFirstLiteralComponent(insertIndex, onClickListenerIndex - 1) val constComponent = getFirstLiteralComponent(insertIndex, onClickListenerIndex - 1)
if (constComponent.isNotEmpty()) { if (constComponent.isNotEmpty()) {
addInstruction( addInstruction(
onClickListenerIndex + 2, onClickListenerIndex + 2,
constComponent 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 // endregion
@ -652,10 +664,7 @@ val playerComponentsPatch = bytecodePatch(
// region add settings // region add settings
addPreference( addPreference(
arrayOf( settingArray,
"PREFERENCE_SCREEN: PLAYER",
"SETTINGS: PLAYER_COMPONENTS"
),
PLAYER_COMPONENTS PLAYER_COMPONENTS
) )

View File

@ -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) = internal fun indexOfContentDescriptionInstruction(method: Method) =
method.indexOfFirstInstructionReversed { method.indexOfFirstInstructionReversed {
opcode == Opcode.INVOKE_VIRTUAL && opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setContentDescription" 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~ * This fingerprint is compatible with YouTube v18.35.xx~
* Nonetheless, the patch works in YouTube v19.02.xx~ * Nonetheless, the patch works in YouTube v19.02.xx~

View File

@ -7,6 +7,7 @@ import app.revanced.patches.youtube.utils.resourceid.quickActionsElementContaine
import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.util.MethodUtil
internal val broadcastReceiverFingerprint = legacyFingerprint( internal val broadcastReceiverFingerprint = legacyFingerprint(
name = "broadcastReceiverFingerprint", name = "broadcastReceiverFingerprint",
@ -45,12 +46,15 @@ internal val playerTitleViewFingerprint = legacyFingerprint(
literals = listOf(playerVideoTitleView), literals = listOf(playerVideoTitleView),
) )
internal val quickActionsElementFingerprint = legacyFingerprint( internal val quickActionsElementSyntheticFingerprint = legacyFingerprint(
name = "quickActionsElementFingerprint", name = "quickActionsElementSyntheticFingerprint",
returnType = "V", returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/view/View;"), parameters = listOf("Landroid/view/View;"),
literals = listOf(quickActionsElementContainer), literals = listOf(quickActionsElementContainer),
customFingerprint = { _, classDef ->
AccessFlags.SYNTHETIC.isSet(classDef.accessFlags)
}
) )
internal val relatedEndScreenResultsFingerprint = legacyFingerprint( internal val relatedEndScreenResultsFingerprint = legacyFingerprint(

View File

@ -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_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH
import app.revanced.patches.youtube.utils.fullscreen.fullscreenButtonHookPatch 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.layoutConstructorFingerprint
import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch
import app.revanced.patches.youtube.utils.patch.PatchList.FULLSCREEN_COMPONENTS import app.revanced.patches.youtube.utils.patch.PatchList.FULLSCREEN_COMPONENTS
@ -101,7 +102,6 @@ val fullscreenComponentsPatch = bytecodePatch(
"invoke-static {v$targetRegister}, " + "invoke-static {v$targetRegister}, " +
"$PLAYER_CLASS_DESCRIPTOR->disableEngagementPanels(Landroidx/coordinatorlayout/widget/CoordinatorLayout;)V" "$PLAYER_CLASS_DESCRIPTOR->disableEngagementPanels(Landroidx/coordinatorlayout/widget/CoordinatorLayout;)V"
) )
} }
playerTitleViewFingerprint.methodOrThrow().apply { playerTitleViewFingerprint.methodOrThrow().apply {
@ -190,7 +190,7 @@ val fullscreenComponentsPatch = bytecodePatch(
// region patch for quick actions // region patch for quick actions
quickActionsElementFingerprint.methodOrThrow().apply { quickActionsElementSyntheticFingerprint.methodOrThrow().apply {
val containerCalls = implementation!!.instructions.withIndex() val containerCalls = implementation!!.instructions.withIndex()
.filter { instruction -> .filter { instruction ->
(instruction.value as? WideLiteralInstruction)?.wideLiteral == quickActionsElementContainer (instruction.value as? WideLiteralInstruction)?.wideLiteral == quickActionsElementContainer
@ -219,11 +219,13 @@ val fullscreenComponentsPatch = bytecodePatch(
// region patch for compact control overlay // region patch for compact control overlay
youtubeControlsOverlayFingerprint.methodOrThrow().apply { youtubeControlsOverlayFingerprint.methodOrThrow().apply {
val targetIndex = indexOfFirstInstructionOrThrow { val targetIndex = indexOfFocusableInTouchModeInstruction(this)
opcode == Opcode.INVOKE_VIRTUAL && val walkerIndex = indexOfFirstInstructionOrThrow(targetIndex) {
getReference<MethodReference>()?.name == "setFocusableInTouchMode" 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) val walkerMethod = getWalkerMethod(walkerIndex)
walkerMethod.apply { walkerMethod.apply {

Some files were not shown because too many files have changed in this diff Show More