mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-04-29 22:24:31 +02:00
Compare commits
65 Commits
v5.6.1-dev
...
revanced-e
Author | SHA1 | Date | |
---|---|---|---|
![]() |
42cc8201f5 | ||
![]() |
15d0fafcf8 | ||
![]() |
db10e410d3 | ||
![]() |
8f23d76f37 | ||
![]() |
7168e49121 | ||
![]() |
2b52380294 | ||
![]() |
0cc693961a | ||
![]() |
bbf863c630 | ||
![]() |
783e366242 | ||
![]() |
4a19a960c5 | ||
![]() |
e55fd4eb74 | ||
![]() |
64af7fd8b6 | ||
![]() |
457dbfec6c | ||
![]() |
22b98336d5 | ||
![]() |
7e4a71b385 | ||
![]() |
bb5964ce98 | ||
![]() |
15db05c636 | ||
![]() |
3f9edca15d | ||
![]() |
1eca8c854c | ||
![]() |
692b4f2c53 | ||
![]() |
5162dccecc | ||
![]() |
56b713b0db | ||
![]() |
f761bc45ec | ||
![]() |
bccd6dc5df | ||
![]() |
09a8eb7114 | ||
![]() |
b439ef3ee7 | ||
![]() |
7f85e802c2 | ||
![]() |
bb1498df76 | ||
![]() |
1c58c6a36e | ||
![]() |
26bf2f4b82 | ||
![]() |
a037b25c13 | ||
![]() |
b72bb71e30 | ||
![]() |
28ff781786 | ||
![]() |
f249e88ce8 | ||
![]() |
036e3dad11 | ||
![]() |
9342e1f7d2 | ||
![]() |
f19dd54026 | ||
![]() |
edccd61e6b | ||
![]() |
35e6c26823 | ||
![]() |
88d59d05b9 | ||
![]() |
aac38dc8af | ||
![]() |
1dd7eda606 | ||
![]() |
05195caa5a | ||
![]() |
82158deaf2 | ||
![]() |
21be41c2a9 | ||
![]() |
ae69aca189 | ||
![]() |
8d84304737 | ||
![]() |
1926becdfc | ||
![]() |
9146d9283b | ||
![]() |
8db38af44f | ||
![]() |
1aaf03df50 | ||
![]() |
ee0a7b5443 | ||
![]() |
a9408b0f51 | ||
![]() |
03c0826fab | ||
![]() |
0384907324 | ||
![]() |
2b0f12cb9e | ||
![]() |
5088daa434 | ||
![]() |
b5fe136898 | ||
![]() |
807494f8a7 | ||
![]() |
d8ae741853 | ||
![]() |
934c33a0f9 | ||
![]() |
b725e54aee | ||
![]() |
c57de97576 | ||
![]() |
2e3274152b | ||
![]() |
6499b7d3f0 |
165
README.md
165
README.md
@ -11,73 +11,73 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `Alternative thumbnails` | Adds options to replace video thumbnails using the DeArrow API or image captures from the video. | 19.05.36 ~ 20.03.43 |
|
||||
| `Ambient mode control` | Adds options to disable Ambient mode and to bypass Ambient mode restrictions. | 19.05.36 ~ 20.03.43 |
|
||||
| `Bypass URL redirects` | Adds an option to bypass URL redirects and open the original URL directly. | 19.05.36 ~ 20.03.43 |
|
||||
| `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. | 19.05.36 ~ 20.03.43 |
|
||||
| `Change form factor` | Adds an option to change the UI appearance to a phone, tablet, or automotive device. | 19.05.36 ~ 20.03.43 |
|
||||
| `Change live ring click action` | Adds an option to open the channel instead of the live stream when clicking on the live ring. | 19.05.36 ~ 20.03.43 |
|
||||
| `Change player flyout menu toggles` | Adds an option to use text toggles instead of switch toggles within the additional settings menu. | 19.05.36 ~ 20.03.43 |
|
||||
| `Change share sheet` | Adds an option to change the in-app share sheet to the system share sheet. | 19.05.36 ~ 20.03.43 |
|
||||
| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 19.05.36 ~ 20.03.43 |
|
||||
| `Custom Shorts action buttons` | Changes, at compile time, the icon of the action buttons of the Shorts player. | 19.05.36 ~ 20.03.43 |
|
||||
| `Custom branding icon for YouTube` | Changes the YouTube app icon to the icon specified in patch options. | 19.05.36 ~ 20.03.43 |
|
||||
| `Custom branding name for YouTube` | Changes the YouTube app name to the name specified in patch options. | 19.05.36 ~ 20.03.43 |
|
||||
| `Custom double tap length` | Adds Double-tap to seek values that are specified in patch options. | 19.05.36 ~ 20.03.43 |
|
||||
| `Custom header for YouTube` | Applies a custom header in the top left corner within the app. | 19.05.36 ~ 20.03.43 |
|
||||
| `Description components` | Adds options to hide and disable description components. | 19.05.36 ~ 20.03.43 |
|
||||
| `Disable QUIC protocol` | Adds an option to disable CronetEngine's QUIC protocol. | 19.05.36 ~ 20.03.43 |
|
||||
| `Disable forced auto audio tracks` | Adds an option to disable audio tracks from being automatically enabled. | 19.05.36 ~ 20.03.43 |
|
||||
| `Disable forced auto captions` | Adds an option to disable captions from being automatically enabled. | 19.05.36 ~ 20.03.43 |
|
||||
| `Disable haptic feedback` | Adds options to disable haptic feedback when swiping in the video player. | 19.05.36 ~ 20.03.43 |
|
||||
| `Disable layout updates` | Adds an option to disable layout updates by server. | 19.05.36 ~ 20.03.43 |
|
||||
| `Disable resuming Miniplayer on startup` | Adds an option to disable the Miniplayer 'Continue watching' from resuming on app startup. | 19.05.36 ~ 20.03.43 |
|
||||
| `Disable resuming Shorts on startup` | Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched. | 19.05.36 ~ 20.03.43 |
|
||||
| `Disable splash animation` | Adds an option to disable the splash animation on app startup. | 19.05.36 ~ 20.03.43 |
|
||||
| `Enable OPUS codec` | Adds an option to enable the OPUS audio codec if the player response includes it. | 19.05.36 ~ 20.03.43 |
|
||||
| `Enable debug logging` | Adds an option to enable debug logging. | 19.05.36 ~ 20.03.43 |
|
||||
| `Enable gradient loading screen` | Adds an option to enable the gradient loading screen. | 19.05.36 ~ 20.03.43 |
|
||||
| `Force hide player buttons background` | Removes, at compile time, the dark background surrounding the video player controls. | 19.05.36 ~ 20.03.43 |
|
||||
| `Fullscreen components` | Adds options to hide or change components related to fullscreen. | 19.05.36 ~ 20.03.43 |
|
||||
| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hide Shorts dimming` | Removes, at compile time, the dimming effect at the top and bottom of Shorts videos. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hide accessibility controls dialog` | Removes, at compile time, accessibility controls dialog 'Turn on accessibility controls for the video player?'. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hide action buttons` | Adds options to hide action buttons under videos. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hide ads` | Adds options to hide ads. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hide comments components` | Adds options to hide components related to comments. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hide feed components` | Adds options to hide components related to feeds. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hide feed flyout menu` | Adds the ability to hide feed flyout menu components using a custom filter. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hide layout components` | Adds options to hide general layout components. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hide player buttons` | Adds options to hide buttons in the video player. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hide player flyout menu` | Adds options to hide player flyout menu components. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hide shortcuts` | Remove, at compile time, the app shortcuts that appears when the app icon is long pressed. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hook YouTube Music actions` | Adds support for opening music in RVX Music using the in-app YouTube Music button. | 19.05.36 ~ 20.03.43 |
|
||||
| `Hook download actions` | Adds support to download videos with an external downloader app using the in-app download button. | 19.05.36 ~ 20.03.43 |
|
||||
| `MaterialYou` | Applies the MaterialYou theme for Android 12+ devices. | 19.05.36 ~ 20.03.43 |
|
||||
| `Miniplayer` | Adds options to change the in-app minimized player, and if patching target 19.16+ adds options to use modern miniplayers. | 19.05.36 ~ 20.03.43 |
|
||||
| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 19.05.36 ~ 20.03.43 |
|
||||
| `Open links externally` | Adds an option to always open links in your browser instead of the in-app browser. | 19.05.36 ~ 20.03.43 |
|
||||
| `Overlay buttons` | Adds options to display useful overlay buttons in the video player. | 19.05.36 ~ 20.03.43 |
|
||||
| `Player components` | Adds options to hide or change components related to the video player. | 19.05.36 ~ 20.03.43 |
|
||||
| `Remove background playback restrictions` | Removes restrictions on background playback, including for music and kids videos. | 19.05.36 ~ 20.03.43 |
|
||||
| `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. | 19.05.36 ~ 20.03.43 |
|
||||
| `Return YouTube Dislike` | Adds an option to show the dislike count of videos using the Return YouTube Dislike API. | 19.05.36 ~ 20.03.43 |
|
||||
| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 19.05.36 ~ 20.03.43 |
|
||||
| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 19.05.36 ~ 20.03.43 |
|
||||
| `Seekbar components` | Adds options to hide or change components related to the seekbar. | 19.05.36 ~ 20.03.43 |
|
||||
| `Settings for YouTube` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 19.05.36 ~ 20.03.43 |
|
||||
| `Shorts components` | Adds options to hide or change components related to YouTube Shorts. | 19.05.36 ~ 20.03.43 |
|
||||
| `Snack bar components` | Adds options to hide or change components related to the snack bar. | 19.05.36 ~ 20.03.43 |
|
||||
| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as sponsored content. | 19.05.36 ~ 20.03.43 |
|
||||
| `Spoof app version` | Adds options to spoof the YouTube client version. This can be used to restore old UI elements and features. | 19.05.36 ~ 20.03.43 |
|
||||
| `Spoof streaming data` | Adds options to spoof the streaming data to allow playback. | 19.05.36 ~ 20.03.43 |
|
||||
| `Swipe controls` | Adds options for controlling volume and brightness with swiping, and whether to enter fullscreen when swiping down below the player. | 19.05.36 ~ 20.03.43 |
|
||||
| `Theme` | Changes the app's themes to the values specified in patch options. | 19.05.36 ~ 20.03.43 |
|
||||
| `Toolbar components` | Adds options to hide or change components located on the toolbar, such as the search bar, header, and toolbar buttons. | 19.05.36 ~ 20.03.43 |
|
||||
| `Translations for YouTube` | Add translations or remove string resources. | 19.05.36 ~ 20.03.43 |
|
||||
| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 19.05.36 ~ 20.03.43 |
|
||||
| `Visual preferences icons for YouTube` | Adds icons to specific preferences in the settings. | 19.05.36 ~ 20.03.43 |
|
||||
| `Watch history` | Adds an option to change the domain of the watch history or check its status. | 19.05.36 ~ 20.03.43 |
|
||||
| `Alternative thumbnails` | Adds options to replace video thumbnails using the DeArrow API or image captures from the video. | 19.05.36 ~ 19.47.53 |
|
||||
| `Ambient mode control` | Adds options to disable Ambient mode and to bypass Ambient mode restrictions. | 19.05.36 ~ 19.47.53 |
|
||||
| `Bypass URL redirects` | Adds an option to bypass URL redirects and open the original URL directly. | 19.05.36 ~ 19.47.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. | 19.05.36 ~ 19.47.53 |
|
||||
| `Change form factor` | Adds an option to change the UI appearance to a phone, tablet, or automotive device. | 19.05.36 ~ 19.47.53 |
|
||||
| `Change live ring click action` | Adds an option to open the channel instead of the live stream when clicking on the live ring. | 19.05.36 ~ 19.47.53 |
|
||||
| `Change player flyout menu toggles` | Adds an option to use text toggles instead of switch toggles within the additional settings menu. | 19.05.36 ~ 19.47.53 |
|
||||
| `Change share sheet` | Adds an option to change the in-app share sheet to the system share sheet. | 19.05.36 ~ 19.47.53 |
|
||||
| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 19.05.36 ~ 19.47.53 |
|
||||
| `Custom Shorts action buttons` | Changes, at compile time, the icon of the action buttons of the Shorts player. | 19.05.36 ~ 19.47.53 |
|
||||
| `Custom branding icon for YouTube` | Changes the YouTube app icon to the icon specified in patch options. | 19.05.36 ~ 19.47.53 |
|
||||
| `Custom branding name for YouTube` | Changes the YouTube app name to the name specified in patch options. | 19.05.36 ~ 19.47.53 |
|
||||
| `Custom double tap length` | Adds Double-tap to seek values that are specified in patch options. | 19.05.36 ~ 19.47.53 |
|
||||
| `Custom header for YouTube` | Applies a custom header in the top left corner within the app. | 19.05.36 ~ 19.47.53 |
|
||||
| `Description components` | Adds options to hide and disable description components. | 19.05.36 ~ 19.47.53 |
|
||||
| `Disable QUIC protocol` | Adds an option to disable CronetEngine's QUIC protocol. | 19.05.36 ~ 19.47.53 |
|
||||
| `Disable forced auto audio tracks` | Adds an option to disable audio tracks from being automatically enabled. | 19.05.36 ~ 19.47.53 |
|
||||
| `Disable forced auto captions` | Adds an option to disable captions from being automatically enabled. | 19.05.36 ~ 19.47.53 |
|
||||
| `Disable haptic feedback` | Adds options to disable haptic feedback when swiping in the video player. | 19.05.36 ~ 19.47.53 |
|
||||
| `Disable layout updates` | Adds an option to disable layout updates by server. | 19.05.36 ~ 19.47.53 |
|
||||
| `Disable resuming Miniplayer on startup` | Adds an option to disable the Miniplayer 'Continue watching' from resuming on app startup. | 19.05.36 ~ 19.47.53 |
|
||||
| `Disable resuming Shorts on startup` | Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched. | 19.05.36 ~ 19.47.53 |
|
||||
| `Disable splash animation` | Adds an option to disable the splash animation on app startup. | 19.05.36 ~ 19.47.53 |
|
||||
| `Enable OPUS codec` | Adds an option to enable the OPUS audio codec if the player response includes it. | 19.05.36 ~ 19.47.53 |
|
||||
| `Enable debug logging` | Adds an option to enable debug logging. | 19.05.36 ~ 19.47.53 |
|
||||
| `Enable gradient loading screen` | Adds an option to enable the gradient loading screen. | 19.05.36 ~ 19.47.53 |
|
||||
| `Force hide player buttons background` | Removes, at compile time, the dark background surrounding the video player controls. | 19.05.36 ~ 19.47.53 |
|
||||
| `Fullscreen components` | Adds options to hide or change components related to fullscreen. | 19.05.36 ~ 19.47.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. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hide Shorts dimming` | Removes, at compile time, the dimming effect at the top and bottom of Shorts videos. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hide accessibility controls dialog` | Removes, at compile time, accessibility controls dialog 'Turn on accessibility controls for the video player?'. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hide action buttons` | Adds options to hide action buttons under videos. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hide ads` | Adds options to hide ads. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hide comments components` | Adds options to hide components related to comments. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hide feed components` | Adds options to hide components related to feeds. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hide feed flyout menu` | Adds the ability to hide feed flyout menu components using a custom filter. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hide layout components` | Adds options to hide general layout components. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hide player buttons` | Adds options to hide buttons in the video player. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hide player flyout menu` | Adds options to hide player flyout menu components. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hide shortcuts` | Remove, at compile time, the app shortcuts that appears when the app icon is long pressed. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hook YouTube Music actions` | Adds support for opening music in RVX Music using the in-app YouTube Music button. | 19.05.36 ~ 19.47.53 |
|
||||
| `Hook download actions` | Adds support to download videos with an external downloader app using the in-app download button. | 19.05.36 ~ 19.47.53 |
|
||||
| `MaterialYou` | Applies the MaterialYou theme for Android 12+ devices. | 19.05.36 ~ 19.47.53 |
|
||||
| `Miniplayer` | Adds options to change the in-app minimized player, and if patching target 19.16+ adds options to use modern miniplayers. | 19.05.36 ~ 19.47.53 |
|
||||
| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 19.05.36 ~ 19.47.53 |
|
||||
| `Open links externally` | Adds an option to always open links in your browser instead of the in-app browser. | 19.05.36 ~ 19.47.53 |
|
||||
| `Overlay buttons` | Adds options to display useful overlay buttons in the video player. | 19.05.36 ~ 19.47.53 |
|
||||
| `Player components` | Adds options to hide or change components related to the video player. | 19.05.36 ~ 19.47.53 |
|
||||
| `Remove background playback restrictions` | Removes restrictions on background playback, including for music and kids videos. | 19.05.36 ~ 19.47.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. | 19.05.36 ~ 19.47.53 |
|
||||
| `Return YouTube Dislike` | Adds an option to show the dislike count of videos using the Return YouTube Dislike API. | 19.05.36 ~ 19.47.53 |
|
||||
| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 19.05.36 ~ 19.47.53 |
|
||||
| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 19.05.36 ~ 19.47.53 |
|
||||
| `Seekbar components` | Adds options to hide or change components related to the seekbar. | 19.05.36 ~ 19.47.53 |
|
||||
| `Settings for YouTube` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 19.05.36 ~ 19.47.53 |
|
||||
| `Shorts components` | Adds options to hide or change components related to YouTube Shorts. | 19.05.36 ~ 19.47.53 |
|
||||
| `Snack bar components` | Adds options to hide or change components related to the snack bar. | 19.05.36 ~ 19.47.53 |
|
||||
| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as sponsored content. | 19.05.36 ~ 19.47.53 |
|
||||
| `Spoof app version` | Adds options to spoof the YouTube client version. This can be used to restore old UI elements and features. | 19.05.36 ~ 19.47.53 |
|
||||
| `Spoof streaming data` | Adds options to spoof the streaming data to allow playback. | 19.05.36 ~ 19.47.53 |
|
||||
| `Swipe controls` | Adds options for controlling volume and brightness with swiping, and whether to enter fullscreen when swiping down below the player. | 19.05.36 ~ 19.47.53 |
|
||||
| `Theme` | Changes the app's themes to the values specified in patch options. | 19.05.36 ~ 19.47.53 |
|
||||
| `Toolbar components` | Adds options to hide or change components located on the toolbar, such as the search bar, header, and toolbar buttons. | 19.05.36 ~ 19.47.53 |
|
||||
| `Translations for YouTube` | Add translations or remove string resources. | 19.05.36 ~ 19.47.53 |
|
||||
| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 19.05.36 ~ 19.47.53 |
|
||||
| `Visual preferences icons for YouTube` | Adds icons to specific preferences in the settings. | 19.05.36 ~ 19.47.53 |
|
||||
| `Watch history` | Adds an option to change the domain of the watch history or check its status. | 19.05.36 ~ 19.47.53 |
|
||||
</details>
|
||||
|
||||
### [📦 `com.google.android.apps.youtube.music`](https://play.google.com/store/apps/details?id=com.google.android.apps.youtube.music)
|
||||
@ -134,19 +134,19 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `Change package name` | Changes the package name for Reddit to the name specified in patch options. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Custom branding name for Reddit` | Changes the Reddit app name to the name specified in patch options. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Disable screenshot popup` | Adds an option to disable the popup that appears when taking a screenshot. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Hide Recently Visited shelf` | Adds an option to hide the Recently Visited shelf in the sidebar. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Hide ads` | Adds options to hide ads. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Hide navigation buttons` | Adds options to hide buttons in the navigation bar. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Hide recommended communities shelf` | Adds an option to hide the recommended communities shelves in subreddits. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Open links directly` | Adds an option to skip over redirection URLs in external links. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Open links externally` | Adds an option to always open links in your browser instead of in the in-app-browser. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Premium icon` | Unlocks premium app icons. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Remove subreddit dialog` | Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Settings for Reddit` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 2024.17.0 ~ 2025.12.0 |
|
||||
| `Change package name` | Changes the package name for Reddit to the name specified in patch options. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Custom branding name for Reddit` | Changes the Reddit app name to the name specified in patch options. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Disable screenshot popup` | Adds an option to disable the popup that appears when taking a screenshot. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Hide Recently Visited shelf` | Adds an option to hide the Recently Visited shelf in the sidebar. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Hide ads` | Adds options to hide ads. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Hide navigation buttons` | Adds options to hide buttons in the navigation bar. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Hide recommended communities shelf` | Adds an option to hide the recommended communities shelves in subreddits. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Open links directly` | Adds an option to skip over redirection URLs in external links. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Open links externally` | Adds an option to always open links in your browser instead of in the in-app-browser. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Premium icon` | Unlocks premium app icons. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Remove subreddit dialog` | Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 2024.17.0 ~ 2025.12.1 |
|
||||
| `Settings for Reddit` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 2024.17.0 ~ 2025.12.1 |
|
||||
</details>
|
||||
|
||||
|
||||
@ -169,8 +169,7 @@ Example:
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -201,7 +200,7 @@ Example:
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
|
@ -2,7 +2,10 @@ package app.revanced.extension.reddit.patches;
|
||||
|
||||
import static app.revanced.extension.shared.utils.StringRef.str;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@ -34,6 +37,35 @@ public class RemoveSubRedditDialogPatch {
|
||||
return Settings.REMOVE_NSFW_DIALOG.get() || hasBeenVisited;
|
||||
}
|
||||
|
||||
public static void dismissNSFWDialog(Object customDialog) {
|
||||
if (Settings.REMOVE_NSFW_DIALOG.get() &&
|
||||
customDialog instanceof Dialog dialog) {
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
WindowManager.LayoutParams params = window.getAttributes();
|
||||
params.height = 0;
|
||||
params.width = 0;
|
||||
|
||||
// Change the size of dialog to 0.
|
||||
window.setAttributes(params);
|
||||
|
||||
// Disable dialog's background dim.
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
|
||||
|
||||
// Hide DecorView.
|
||||
View decorView = window.getDecorView();
|
||||
decorView.setVisibility(View.GONE);
|
||||
|
||||
// Dismiss dialog.
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean removeNSFWDialog() {
|
||||
return Settings.REMOVE_NSFW_DIALOG.get();
|
||||
}
|
||||
|
||||
public static boolean spoofLoggedInStatus(boolean isLoggedIn) {
|
||||
return !Settings.REMOVE_NOTIFICATION_DIALOG.get() && isLoggedIn;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import app.revanced.extension.reddit.settings.Settings;
|
||||
@SuppressWarnings("unused")
|
||||
public class ScreenshotPopupPatch {
|
||||
|
||||
public static boolean disableScreenshotPopup() {
|
||||
return Settings.DISABLE_SCREENSHOT_POPUP.get();
|
||||
public static Boolean disableScreenshotPopup(Boolean original) {
|
||||
return Settings.DISABLE_SCREENSHOT_POPUP.get() ? Boolean.FALSE : original;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_NEW_POST_ADS = new BooleanSetting("revanced_hide_new_post_ads", TRUE, true);
|
||||
|
||||
// Layout
|
||||
public static final BooleanSetting DISABLE_SCREENSHOT_POPUP = new BooleanSetting("revanced_disable_screenshot_popup", TRUE);
|
||||
public static final BooleanSetting DISABLE_SCREENSHOT_POPUP = new BooleanSetting("revanced_disable_screenshot_popup", TRUE, true);
|
||||
public static final BooleanSetting HIDE_CHAT_BUTTON = new BooleanSetting("revanced_hide_chat_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_DISCOVER_BUTTON = new BooleanSetting("revanced_hide_discover_button", FALSE, true);
|
||||
|
@ -279,6 +279,7 @@ object InnerTubeRequestBody {
|
||||
route: CompiledRoute,
|
||||
clientType: YouTubeAppClient.ClientType,
|
||||
requestHeader: Map<String, String>? = null,
|
||||
dataSyncId: String? = null,
|
||||
connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
) = getInnerTubeResponseConnectionFromRoute(
|
||||
@ -288,6 +289,7 @@ object InnerTubeRequestBody {
|
||||
clientVersion = clientType.clientVersion,
|
||||
supportsCookies = clientType.supportsCookies,
|
||||
requestHeader = requestHeader,
|
||||
dataSyncId = dataSyncId,
|
||||
connectTimeout = connectTimeout,
|
||||
readTimeout = readTimeout,
|
||||
)
|
||||
@ -297,6 +299,7 @@ object InnerTubeRequestBody {
|
||||
route: CompiledRoute,
|
||||
clientType: YouTubeWebClient.ClientType,
|
||||
requestHeader: Map<String, String>? = null,
|
||||
dataSyncId: String? = null,
|
||||
connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
) = getInnerTubeResponseConnectionFromRoute(
|
||||
@ -305,6 +308,7 @@ object InnerTubeRequestBody {
|
||||
clientId = clientType.id.toString(),
|
||||
clientVersion = clientType.clientVersion,
|
||||
requestHeader = requestHeader,
|
||||
dataSyncId = dataSyncId,
|
||||
connectTimeout = connectTimeout,
|
||||
readTimeout = readTimeout,
|
||||
)
|
||||
@ -317,6 +321,7 @@ object InnerTubeRequestBody {
|
||||
clientVersion: String,
|
||||
supportsCookies: Boolean = true,
|
||||
requestHeader: Map<String, String>? = null,
|
||||
dataSyncId: String? = null,
|
||||
connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
): HttpURLConnection {
|
||||
@ -348,6 +353,11 @@ object InnerTubeRequestBody {
|
||||
}
|
||||
}
|
||||
|
||||
// Used to identify brand accounts
|
||||
if (dataSyncId != null && dataSyncId.isNotEmpty()) {
|
||||
connection.setRequestProperty("X-Goog-PageId", dataSyncId)
|
||||
}
|
||||
|
||||
return connection
|
||||
}
|
||||
|
||||
|
@ -69,11 +69,21 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
||||
* Skip response encryption in OnesiePlayerRequest.
|
||||
*/
|
||||
public static boolean skipResponseEncryption(boolean original) {
|
||||
if (SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION) {
|
||||
return false;
|
||||
if (!SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION) {
|
||||
return original;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return original;
|
||||
/**
|
||||
* Injection point.
|
||||
* Turns off a feature flag that interferes with video playback.
|
||||
*/
|
||||
public static boolean usePlaybackStartFeatureFlag(boolean original) {
|
||||
if (!SPOOF_STREAMING_DATA) {
|
||||
return original;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -240,7 +240,11 @@ class StreamingDataRequest private constructor(
|
||||
}
|
||||
|
||||
handleConnectionError(str("revanced_spoof_streaming_data_failed_forbidden"), null, true)
|
||||
handleConnectionError(str("revanced_spoof_streaming_data_failed_forbidden_suggestion"), null, true)
|
||||
handleConnectionError(
|
||||
str("revanced_spoof_streaming_data_failed_forbidden_suggestion"),
|
||||
null,
|
||||
true
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ public class Utils {
|
||||
private static WeakReference<Activity> activityRef = new WeakReference<>(null);
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static volatile Context context;
|
||||
private static Locale contextLocale;
|
||||
|
||||
protected Utils() {
|
||||
} // utility class
|
||||
@ -308,34 +309,51 @@ public class Utils {
|
||||
* @return Context with locale applied.
|
||||
*/
|
||||
public static Context getLocalizedContext(Context mContext) {
|
||||
Activity mActivity = activityRef.get();
|
||||
if (mActivity == null) {
|
||||
return mContext;
|
||||
}
|
||||
if (mContext == null) {
|
||||
return null;
|
||||
try {
|
||||
Activity mActivity = activityRef.get();
|
||||
if (mActivity != null && mContext != null) {
|
||||
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
|
||||
|
||||
// Locale of Application.
|
||||
Locale applicationLocale = language == AppLanguage.DEFAULT
|
||||
? mActivity.getResources().getConfiguration().locale
|
||||
: language.getLocale();
|
||||
|
||||
// Locale of Context.
|
||||
Locale contextLocale = mContext.getResources().getConfiguration().locale;
|
||||
|
||||
// If they are different, overrides the Locale of the Context and resource.
|
||||
if (applicationLocale != contextLocale) {
|
||||
Utils.contextLocale = contextLocale;
|
||||
|
||||
// If they are different, overrides the Locale of the Context and resource.
|
||||
Locale.setDefault(applicationLocale);
|
||||
Configuration configuration = new Configuration(mContext.getResources().getConfiguration());
|
||||
configuration.setLocale(applicationLocale);
|
||||
return mContext.createConfigurationContext(configuration);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "getLocalizedContext failed", ex);
|
||||
}
|
||||
|
||||
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
|
||||
return mContext;
|
||||
}
|
||||
|
||||
// Locale of Application.
|
||||
Locale applicationLocale = language == AppLanguage.DEFAULT
|
||||
? mActivity.getResources().getConfiguration().locale
|
||||
: language.getLocale();
|
||||
|
||||
// Locale of Context.
|
||||
Locale contextLocale = mContext.getResources().getConfiguration().locale;
|
||||
|
||||
// If they are identical, no need to override them.
|
||||
if (applicationLocale == contextLocale) {
|
||||
return mContext;
|
||||
public static void resetLocalizedContext() {
|
||||
try {
|
||||
if (contextLocale != null) {
|
||||
Locale.setDefault(contextLocale);
|
||||
Context mContext = getContext();
|
||||
if (mContext != null) {
|
||||
Configuration config = mContext.getResources().getConfiguration();
|
||||
config.setLocale(contextLocale);
|
||||
setContext(mContext.createConfigurationContext(config));
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "resetLocalizedContext failed", ex);
|
||||
}
|
||||
|
||||
// If they are different, overrides the Locale of the Context and resource.
|
||||
Locale.setDefault(applicationLocale);
|
||||
Configuration configuration = new Configuration(mContext.getResources().getConfiguration());
|
||||
configuration.setLocale(applicationLocale);
|
||||
return mContext.createConfigurationContext(configuration);
|
||||
}
|
||||
|
||||
public static void setActivity(Activity mainActivity) {
|
||||
@ -353,14 +371,6 @@ public class Utils {
|
||||
// Must initially set context to check the app language.
|
||||
context = appContext;
|
||||
Logger.initializationInfo(Utils.class, "Set 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);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setClipboard(@NonNull String text) {
|
||||
@ -538,14 +548,6 @@ public class Utils {
|
||||
return Build.VERSION.SDK_INT >= sdk;
|
||||
}
|
||||
|
||||
public static int dpToPx(float dp) {
|
||||
if (context == null) {
|
||||
return (int) dp;
|
||||
} else {
|
||||
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
|
||||
}
|
||||
}
|
||||
|
||||
public static int dpToPx(int dp) {
|
||||
if (context == null) {
|
||||
return dp;
|
||||
|
@ -183,8 +183,8 @@ public class PlayerPatch {
|
||||
// The type of descriptionView can be either ViewGroup or TextView. (A/B tests)
|
||||
// If the type of descriptionView is TextView, longer delay is required.
|
||||
final long delayMillis = descriptionView instanceof TextView
|
||||
? 500
|
||||
: 100;
|
||||
? 750
|
||||
: 200;
|
||||
|
||||
Utils.runOnMainThreadDelayed(() -> Utils.clickView(descriptionView), delayMillis);
|
||||
}
|
||||
|
@ -155,6 +155,34 @@ public final class CustomActionsPatch {
|
||||
Logger.printInfo(() -> customAction.name() + bottomSheetMenuClass + bottomSheetMenuList + bottomSheetMenuObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean onBottomSheetMenuItemClick(View view) {
|
||||
try {
|
||||
if (view instanceof ViewGroup viewGroup) {
|
||||
TextView textView = Utils.getChildView(viewGroup, v -> v instanceof TextView);
|
||||
if (textView != null) {
|
||||
String menuTitle = textView.getText().toString();
|
||||
for (CustomAction customAction : CustomAction.values()) {
|
||||
if (customAction.getLabel().equals(menuTitle)) {
|
||||
View.OnLongClickListener onLongClick = customAction.getOnLongClickListener();
|
||||
if (onLongClick != null) {
|
||||
view.setOnLongClickListener(onLongClick);
|
||||
}
|
||||
customAction.getOnClickAction().run();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onBottomSheetMenuItemClick failed");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@ -179,8 +207,9 @@ public final class CustomActionsPatch {
|
||||
if (recyclerView.getChildAt(childCount - i - 1) instanceof ViewGroup parentViewGroup) {
|
||||
childCount = recyclerView.getChildCount();
|
||||
if (childCount > 3 && parentViewGroup.getChildAt(1) instanceof TextView textView) {
|
||||
String menuTitle = textView.getText().toString();
|
||||
for (CustomAction customAction : CustomAction.values()) {
|
||||
if (customAction.getLabel().equals(textView.getText().toString())) {
|
||||
if (customAction.getLabel().equals(menuTitle)) {
|
||||
View.OnClickListener onClick = customAction.getOnClickListener();
|
||||
View.OnLongClickListener onLongClick = customAction.getOnLongClickListener();
|
||||
recyclerViewRef = new WeakReference<>(recyclerView);
|
||||
|
@ -9,6 +9,11 @@ public class PatchStatus {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
public static boolean OldSeekbarThumbnailsDefaultBoolean() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean OldSplashAnimation() {
|
||||
// Replace this with true if the Restore old splash animation (Custom branding icon) succeeds
|
||||
return false;
|
||||
@ -40,23 +45,22 @@ public class PatchStatus {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String SpoofAppVersionDefaultString() {
|
||||
return "18.17.43";
|
||||
}
|
||||
|
||||
public static boolean ToolBarComponents() {
|
||||
// Replace this with true if the Toolbar components patch succeeds
|
||||
return false;
|
||||
}
|
||||
|
||||
public static long PatchedTime() {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
public static String SpoofAppVersionDefaultString() {
|
||||
return "18.17.43";
|
||||
}
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
public static String RVXMusicPackageName() {
|
||||
return "com.google.android.apps.youtube.music";
|
||||
}
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
public static boolean OldSeekbarThumbnailsDefaultBoolean() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,11 @@
|
||||
package app.revanced.extension.youtube.patches.utils;
|
||||
|
||||
import static app.revanced.extension.shared.utils.StringRef.str;
|
||||
import static app.revanced.extension.shared.utils.Utils.runOnMainThreadDelayed;
|
||||
import static app.revanced.extension.youtube.utils.VideoUtils.dismissPlayer;
|
||||
import static app.revanced.extension.youtube.utils.VideoUtils.launchVideoExternalDownloader;
|
||||
import static app.revanced.extension.youtube.utils.VideoUtils.openPlaylist;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.KeyEvent;
|
||||
import android.widget.LinearLayout;
|
||||
@ -28,62 +34,61 @@ import app.revanced.extension.youtube.patches.utils.requests.EditPlaylistRequest
|
||||
import app.revanced.extension.youtube.patches.utils.requests.GetPlaylistsRequest;
|
||||
import app.revanced.extension.youtube.patches.utils.requests.SavePlaylistRequest;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
import app.revanced.extension.youtube.shared.VideoInformation;
|
||||
import app.revanced.extension.youtube.utils.AuthUtils;
|
||||
import app.revanced.extension.youtube.utils.ExtendedUtils;
|
||||
import app.revanced.extension.youtube.utils.VideoUtils;
|
||||
import kotlin.Pair;
|
||||
|
||||
// TODO: Implement sync queue and clean up code.
|
||||
@SuppressWarnings({"unused", "StaticFieldLeak"})
|
||||
public class PlaylistPatch extends VideoUtils {
|
||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
private static final String[] REQUEST_HEADER_KEYS = {
|
||||
AUTHORIZATION_HEADER,
|
||||
"X-GOOG-API-FORMAT-VERSION",
|
||||
"X-Goog-Visitor-Id"
|
||||
};
|
||||
public class PlaylistPatch extends AuthUtils {
|
||||
private static final boolean QUEUE_MANAGER =
|
||||
Settings.OVERLAY_BUTTON_EXTERNAL_DOWNLOADER_QUEUE_MANAGER.get()
|
||||
|| Settings.OVERRIDE_VIDEO_DOWNLOAD_BUTTON_QUEUE_MANAGER.get();
|
||||
|
||||
private static Context mContext;
|
||||
private static volatile String authorization = "";
|
||||
private static volatile boolean isIncognito = false;
|
||||
private static volatile Map<String, String> requestHeader;
|
||||
private static volatile String playlistId = "";
|
||||
private static volatile String videoId = "";
|
||||
|
||||
private static final String checkFailedAuth =
|
||||
ResourceUtils.getString("revanced_queue_manager_check_failed_auth");
|
||||
private static final String checkFailedPlaylistId =
|
||||
ResourceUtils.getString("revanced_queue_manager_check_failed_playlist_id");
|
||||
private static final String checkFailedQueue =
|
||||
ResourceUtils.getString("revanced_queue_manager_check_failed_queue");
|
||||
private static final String checkFailedVideoId =
|
||||
ResourceUtils.getString("revanced_queue_manager_check_failed_video_id");
|
||||
private static final String checkFailedGeneric =
|
||||
ResourceUtils.getString("revanced_queue_manager_check_failed_generic");
|
||||
private static String checkFailedAuth;
|
||||
private static String checkFailedPlaylistId;
|
||||
private static String checkFailedQueue;
|
||||
private static String checkFailedVideoId;
|
||||
private static String checkFailedGeneric;
|
||||
|
||||
private static final String fetchFailedAdd =
|
||||
ResourceUtils.getString("revanced_queue_manager_fetch_failed_add");
|
||||
private static final String fetchFailedCreate =
|
||||
ResourceUtils.getString("revanced_queue_manager_fetch_failed_create");
|
||||
private static final String fetchFailedDelete =
|
||||
ResourceUtils.getString("revanced_queue_manager_fetch_failed_delete");
|
||||
private static final String fetchFailedRemove =
|
||||
ResourceUtils.getString("revanced_queue_manager_fetch_failed_remove");
|
||||
private static final String fetchFailedSave =
|
||||
ResourceUtils.getString("revanced_queue_manager_fetch_failed_save");
|
||||
private static String fetchFailedAdd;
|
||||
private static String fetchFailedCreate;
|
||||
private static String fetchFailedDelete;
|
||||
private static String fetchFailedRemove;
|
||||
private static String fetchFailedSave;
|
||||
|
||||
private static final String fetchSucceededAdd =
|
||||
ResourceUtils.getString("revanced_queue_manager_fetch_succeeded_add");
|
||||
private static final String fetchSucceededCreate =
|
||||
ResourceUtils.getString("revanced_queue_manager_fetch_succeeded_create");
|
||||
private static final String fetchSucceededDelete =
|
||||
ResourceUtils.getString("revanced_queue_manager_fetch_succeeded_delete");
|
||||
private static final String fetchSucceededRemove =
|
||||
ResourceUtils.getString("revanced_queue_manager_fetch_succeeded_remove");
|
||||
private static final String fetchSucceededSave =
|
||||
ResourceUtils.getString("revanced_queue_manager_fetch_succeeded_save");
|
||||
private static String fetchSucceededAdd;
|
||||
private static String fetchSucceededCreate;
|
||||
private static String fetchSucceededDelete;
|
||||
private static String fetchSucceededRemove;
|
||||
private static String fetchSucceededSave;
|
||||
|
||||
static {
|
||||
Context mContext = Utils.getContext();
|
||||
if (mContext != null && mContext.getResources() != null) {
|
||||
checkFailedAuth = str("revanced_queue_manager_check_failed_auth");
|
||||
checkFailedPlaylistId = str("revanced_queue_manager_check_failed_playlist_id");
|
||||
checkFailedQueue = str("revanced_queue_manager_check_failed_queue");
|
||||
checkFailedVideoId = str("revanced_queue_manager_check_failed_video_id");
|
||||
checkFailedGeneric = str("revanced_queue_manager_check_failed_generic");
|
||||
|
||||
fetchFailedAdd = str("revanced_queue_manager_fetch_failed_add");
|
||||
fetchFailedCreate = str("revanced_queue_manager_fetch_failed_create");
|
||||
fetchFailedDelete = str("revanced_queue_manager_fetch_failed_delete");
|
||||
fetchFailedRemove = str("revanced_queue_manager_fetch_failed_remove");
|
||||
fetchFailedSave = str("revanced_queue_manager_fetch_failed_save");
|
||||
|
||||
fetchSucceededAdd = str("revanced_queue_manager_fetch_succeeded_add");
|
||||
fetchSucceededCreate = str("revanced_queue_manager_fetch_succeeded_create");
|
||||
fetchSucceededDelete = str("revanced_queue_manager_fetch_succeeded_delete");
|
||||
fetchSucceededRemove = str("revanced_queue_manager_fetch_succeeded_remove");
|
||||
fetchSucceededSave = str("revanced_queue_manager_fetch_succeeded_save");
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("itself")
|
||||
private static final BidiMap<String, String> lastVideoIds = new DualHashBidiMap<>();
|
||||
@ -113,20 +118,12 @@ public class PlaylistPatch extends VideoUtils {
|
||||
if (videoId != null) {
|
||||
lastVideoIds.remove(videoId, setVideoId);
|
||||
EditPlaylistRequest.clearVideoId(videoId);
|
||||
Logger.printDebug(() -> "Video removed by YouTube flyout menu: " + videoId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void setIncognitoStatus(boolean incognito) {
|
||||
if (QUEUE_MANAGER) {
|
||||
isIncognito = incognito;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@ -136,30 +133,6 @@ public class PlaylistPatch extends VideoUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void setRequestHeaders(String url, Map<String, String> requestHeaders) {
|
||||
if (QUEUE_MANAGER) {
|
||||
try {
|
||||
// Save requestHeaders whenever an account is switched.
|
||||
String auth = requestHeaders.get(AUTHORIZATION_HEADER);
|
||||
if (auth == null || authorization.equals(auth)) {
|
||||
return;
|
||||
}
|
||||
for (String key : REQUEST_HEADER_KEYS) {
|
||||
if (requestHeaders.get(key) == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
authorization = auth;
|
||||
requestHeader = requestHeaders;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setRequestHeaders failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked by extension.
|
||||
*/
|
||||
@ -171,7 +144,7 @@ public class PlaylistPatch extends VideoUtils {
|
||||
* Invoked by extension.
|
||||
*/
|
||||
public static void prepareDialogBuilder(@NonNull String currentVideoId) {
|
||||
if (authorization.isEmpty() || isIncognito) {
|
||||
if (authorization.isEmpty() || (dataSyncId.isEmpty() && isIncognito)) {
|
||||
handleCheckError(checkFailedAuth);
|
||||
return;
|
||||
}
|
||||
@ -180,9 +153,22 @@ public class PlaylistPatch extends VideoUtils {
|
||||
} else {
|
||||
videoId = currentVideoId;
|
||||
synchronized (lastVideoIds) {
|
||||
QueueManager[] customActionsEntries = playlistId.isEmpty() || lastVideoIds.get(currentVideoId) == null
|
||||
? QueueManager.addToQueueEntries
|
||||
: QueueManager.removeFromQueueEntries;
|
||||
QueueManager[] customActionsEntries;
|
||||
boolean canReload = PlayerType.getCurrent().isMaximizedOrFullscreen() &&
|
||||
lastVideoIds.get(VideoInformation.getVideoId()) != null;
|
||||
if (playlistId.isEmpty() || lastVideoIds.get(currentVideoId) == null) {
|
||||
if (canReload) {
|
||||
customActionsEntries = QueueManager.addToQueueWithReloadEntries;
|
||||
} else {
|
||||
customActionsEntries = QueueManager.addToQueueEntries;
|
||||
}
|
||||
} else {
|
||||
if (canReload) {
|
||||
customActionsEntries = QueueManager.removeFromQueueWithReloadEntries;
|
||||
} else {
|
||||
customActionsEntries = QueueManager.removeFromQueueEntries;
|
||||
}
|
||||
}
|
||||
|
||||
buildBottomSheetDialog(customActionsEntries);
|
||||
}
|
||||
@ -211,13 +197,14 @@ public class PlaylistPatch extends VideoUtils {
|
||||
ExtendedUtils.showBottomSheetDialog(mContext, mScrollView, actionsMap);
|
||||
}
|
||||
|
||||
private static void fetchQueue(boolean remove, boolean openPlaylist, boolean openVideo) {
|
||||
private static void fetchQueue(boolean remove, boolean openPlaylist,
|
||||
boolean openVideo, boolean reload) {
|
||||
try {
|
||||
String currentPlaylistId = playlistId;
|
||||
String currentVideoId = videoId;
|
||||
synchronized (lastVideoIds) {
|
||||
if (currentPlaylistId.isEmpty()) { // Queue is empty, create new playlist.
|
||||
CreatePlaylistRequest.fetchRequestIfNeeded(currentVideoId, requestHeader);
|
||||
CreatePlaylistRequest.fetchRequestIfNeeded(currentVideoId, requestHeader, dataSyncId);
|
||||
runOnMainThreadDelayed(() -> {
|
||||
CreatePlaylistRequest request = CreatePlaylistRequest.getRequestForVideoId(currentVideoId);
|
||||
if (request != null) {
|
||||
@ -231,7 +218,7 @@ public class PlaylistPatch extends VideoUtils {
|
||||
showToast(fetchSucceededCreate);
|
||||
Logger.printDebug(() -> "Queue successfully created, playlistId: " + createdPlaylistId + ", setVideoId: " + setVideoId);
|
||||
if (openPlaylist) {
|
||||
openQueue(currentVideoId, openVideo);
|
||||
openQueue(currentVideoId, openVideo, reload);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -241,7 +228,7 @@ public class PlaylistPatch extends VideoUtils {
|
||||
}, 1000);
|
||||
} else { // Queue is not empty, add or remove video.
|
||||
String setVideoId = lastVideoIds.get(currentVideoId);
|
||||
EditPlaylistRequest.fetchRequestIfNeeded(currentVideoId, currentPlaylistId, setVideoId, requestHeader);
|
||||
EditPlaylistRequest.fetchRequestIfNeeded(currentVideoId, currentPlaylistId, setVideoId, requestHeader, dataSyncId);
|
||||
|
||||
runOnMainThreadDelayed(() -> {
|
||||
EditPlaylistRequest request = EditPlaylistRequest.getRequestForVideoId(currentVideoId);
|
||||
@ -249,22 +236,24 @@ public class PlaylistPatch extends VideoUtils {
|
||||
String fetchedSetVideoId = request.getResult();
|
||||
Logger.printDebug(() -> "fetchedSetVideoId: " + fetchedSetVideoId);
|
||||
if (remove) { // Remove from queue.
|
||||
if (StringUtils.isEmpty(fetchedSetVideoId)) {
|
||||
if ("".equals(fetchedSetVideoId)) {
|
||||
lastVideoIds.remove(currentVideoId, setVideoId);
|
||||
EditPlaylistRequest.clearVideoId(currentVideoId);
|
||||
showToast(fetchSucceededRemove);
|
||||
if (openPlaylist) {
|
||||
openQueue(currentVideoId, openVideo);
|
||||
openQueue(currentVideoId, openVideo, reload);
|
||||
}
|
||||
return;
|
||||
}
|
||||
showToast(fetchFailedRemove);
|
||||
} else { // Add to queue.
|
||||
if (StringUtils.isNotEmpty(fetchedSetVideoId)) {
|
||||
if (fetchedSetVideoId != null && !fetchedSetVideoId.isEmpty()) {
|
||||
lastVideoIds.putIfAbsent(currentVideoId, fetchedSetVideoId);
|
||||
EditPlaylistRequest.clearVideoId(currentVideoId);
|
||||
showToast(fetchSucceededAdd);
|
||||
Logger.printDebug(() -> "Video successfully added, setVideoId: " + fetchedSetVideoId);
|
||||
if (openPlaylist) {
|
||||
openQueue(currentVideoId, openVideo);
|
||||
openQueue(currentVideoId, openVideo, reload);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -286,7 +275,7 @@ public class PlaylistPatch extends VideoUtils {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
GetPlaylistsRequest.fetchRequestIfNeeded(currentPlaylistId, requestHeader);
|
||||
GetPlaylistsRequest.fetchRequestIfNeeded(currentPlaylistId, requestHeader, dataSyncId);
|
||||
runOnMainThreadDelayed(() -> {
|
||||
GetPlaylistsRequest request = GetPlaylistsRequest.getRequestForPlaylistId(currentPlaylistId);
|
||||
if (request != null) {
|
||||
@ -328,7 +317,7 @@ public class PlaylistPatch extends VideoUtils {
|
||||
handleCheckError(checkFailedPlaylistId);
|
||||
return;
|
||||
}
|
||||
SavePlaylistRequest.fetchRequestIfNeeded(playlistId, libraryId, requestHeader);
|
||||
SavePlaylistRequest.fetchRequestIfNeeded(playlistId, libraryId, requestHeader, dataSyncId);
|
||||
|
||||
runOnMainThreadDelayed(() -> {
|
||||
SavePlaylistRequest request = SavePlaylistRequest.getRequestForLibraryId(libraryId);
|
||||
@ -354,7 +343,7 @@ public class PlaylistPatch extends VideoUtils {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
DeletePlaylistRequest.fetchRequestIfNeeded(currentPlaylistId, requestHeader);
|
||||
DeletePlaylistRequest.fetchRequestIfNeeded(currentPlaylistId, requestHeader, dataSyncId);
|
||||
runOnMainThreadDelayed(() -> {
|
||||
DeletePlaylistRequest request = DeletePlaylistRequest.getRequestForPlaylistId(currentPlaylistId);
|
||||
if (request != null) {
|
||||
@ -386,10 +375,10 @@ public class PlaylistPatch extends VideoUtils {
|
||||
}
|
||||
|
||||
private static void openQueue() {
|
||||
openQueue("", false);
|
||||
openQueue("", false, false);
|
||||
}
|
||||
|
||||
private static void openQueue(String currentVideoId, boolean openVideo) {
|
||||
private static void openQueue(String currentVideoId, boolean openVideo, boolean reload) {
|
||||
String currentPlaylistId = playlistId;
|
||||
if (currentPlaylistId.isEmpty()) {
|
||||
handleCheckError(checkFailedQueue);
|
||||
@ -401,7 +390,15 @@ public class PlaylistPatch extends VideoUtils {
|
||||
return;
|
||||
}
|
||||
// Open a video from a playlist
|
||||
openPlaylist(currentPlaylistId, currentVideoId);
|
||||
if (reload) {
|
||||
// Since the Queue is not automatically synced, a 'reload' action has been added as a workaround.
|
||||
// The 'reload' action simply closes the video and reopens it.
|
||||
// It is important to close the video, otherwise the Queue will not be updated.
|
||||
dismissPlayer();
|
||||
openPlaylist(currentPlaylistId, VideoInformation.getVideoId(), true);
|
||||
} else {
|
||||
openPlaylist(currentPlaylistId, currentVideoId);
|
||||
}
|
||||
} else {
|
||||
// Open a playlist
|
||||
openPlaylist(currentPlaylistId);
|
||||
@ -420,27 +417,37 @@ public class PlaylistPatch extends VideoUtils {
|
||||
ADD_TO_QUEUE(
|
||||
"revanced_queue_manager_add_to_queue",
|
||||
"yt_outline_list_add_black_24",
|
||||
() -> fetchQueue(false, false, false)
|
||||
() -> fetchQueue(false, false, false, false)
|
||||
),
|
||||
ADD_TO_QUEUE_AND_OPEN_QUEUE(
|
||||
"revanced_queue_manager_add_to_queue_and_open_queue",
|
||||
"yt_outline_list_add_black_24",
|
||||
() -> fetchQueue(false, true, false)
|
||||
() -> fetchQueue(false, true, false, false)
|
||||
),
|
||||
ADD_TO_QUEUE_AND_PLAY_VIDEO(
|
||||
"revanced_queue_manager_add_to_queue_and_play_video",
|
||||
"yt_outline_list_play_arrow_black_24",
|
||||
() -> fetchQueue(false, true, true)
|
||||
() -> fetchQueue(false, true, true, false)
|
||||
),
|
||||
ADD_TO_QUEUE_AND_RELOAD_VIDEO(
|
||||
"revanced_queue_manager_add_to_queue_and_reload_video",
|
||||
"yt_outline_arrow_circle_black_24",
|
||||
() -> fetchQueue(false, true, true, true)
|
||||
),
|
||||
REMOVE_FROM_QUEUE(
|
||||
"revanced_queue_manager_remove_from_queue",
|
||||
"yt_outline_trash_can_black_24",
|
||||
() -> fetchQueue(true, false, false)
|
||||
() -> fetchQueue(true, false, false, false)
|
||||
),
|
||||
REMOVE_FROM_QUEUE_AND_OPEN_QUEUE(
|
||||
"revanced_queue_manager_remove_from_queue_and_open_queue",
|
||||
"yt_outline_trash_can_black_24",
|
||||
() -> fetchQueue(true, true, false)
|
||||
() -> fetchQueue(true, true, false, false)
|
||||
),
|
||||
REMOVE_FROM_QUEUE_AND_RELOAD_VIDEO(
|
||||
"revanced_queue_manager_remove_from_queue_and_reload_video",
|
||||
"yt_outline_arrow_circle_black_24",
|
||||
() -> fetchQueue(true, true, true, true)
|
||||
),
|
||||
OPEN_QUEUE(
|
||||
"revanced_queue_manager_open_queue",
|
||||
@ -488,6 +495,17 @@ public class PlaylistPatch extends VideoUtils {
|
||||
SAVE_QUEUE,
|
||||
};
|
||||
|
||||
public static final QueueManager[] addToQueueWithReloadEntries = {
|
||||
ADD_TO_QUEUE,
|
||||
ADD_TO_QUEUE_AND_OPEN_QUEUE,
|
||||
ADD_TO_QUEUE_AND_PLAY_VIDEO,
|
||||
ADD_TO_QUEUE_AND_RELOAD_VIDEO,
|
||||
OPEN_QUEUE,
|
||||
//REMOVE_QUEUE,
|
||||
EXTERNAL_DOWNLOADER,
|
||||
SAVE_QUEUE,
|
||||
};
|
||||
|
||||
public static final QueueManager[] removeFromQueueEntries = {
|
||||
REMOVE_FROM_QUEUE,
|
||||
REMOVE_FROM_QUEUE_AND_OPEN_QUEUE,
|
||||
@ -497,6 +515,16 @@ public class PlaylistPatch extends VideoUtils {
|
||||
SAVE_QUEUE,
|
||||
};
|
||||
|
||||
public static final QueueManager[] removeFromQueueWithReloadEntries = {
|
||||
REMOVE_FROM_QUEUE,
|
||||
REMOVE_FROM_QUEUE_AND_OPEN_QUEUE,
|
||||
REMOVE_FROM_QUEUE_AND_RELOAD_VIDEO,
|
||||
OPEN_QUEUE,
|
||||
//REMOVE_QUEUE,
|
||||
EXTERNAL_DOWNLOADER,
|
||||
SAVE_QUEUE,
|
||||
};
|
||||
|
||||
public static final QueueManager[] noVideoIdQueueEntries = {
|
||||
OPEN_QUEUE,
|
||||
//REMOVE_QUEUE,
|
||||
|
@ -24,9 +24,14 @@ import java.util.concurrent.TimeoutException
|
||||
class CreatePlaylistRequest private constructor(
|
||||
private val videoId: String,
|
||||
private val requestHeader: Map<String, String>,
|
||||
private val dataSyncId: String,
|
||||
) {
|
||||
private val future: Future<Pair<String, String>> = Utils.submitOnBackgroundThread {
|
||||
fetch(videoId, requestHeader)
|
||||
fetch(
|
||||
videoId,
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
}
|
||||
|
||||
val playlistId: Pair<String, String>?
|
||||
@ -75,11 +80,19 @@ class CreatePlaylistRequest private constructor(
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun fetchRequestIfNeeded(videoId: String, requestHeader: Map<String, String>) {
|
||||
fun fetchRequestIfNeeded(
|
||||
videoId: String,
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
) {
|
||||
Objects.requireNonNull(videoId)
|
||||
synchronized(cache) {
|
||||
if (!cache.containsKey(videoId)) {
|
||||
cache[videoId] = CreatePlaylistRequest(videoId, requestHeader)
|
||||
cache[videoId] = CreatePlaylistRequest(
|
||||
videoId,
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,7 +110,8 @@ class CreatePlaylistRequest private constructor(
|
||||
|
||||
private fun sendCreatePlaylistRequest(
|
||||
videoId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(videoId)
|
||||
|
||||
@ -112,6 +126,7 @@ class CreatePlaylistRequest private constructor(
|
||||
CREATE_PLAYLIST,
|
||||
clientType,
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
|
||||
val requestBody = createPlaylistRequestBody(videoId = videoId)
|
||||
@ -143,7 +158,8 @@ class CreatePlaylistRequest private constructor(
|
||||
private fun sendSetVideoIdRequest(
|
||||
videoId: String,
|
||||
playlistId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(playlistId)
|
||||
|
||||
@ -157,7 +173,8 @@ class CreatePlaylistRequest private constructor(
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
GET_SET_VIDEO_ID,
|
||||
clientType,
|
||||
requestHeader
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
|
||||
val requestBody = createApplicationRequestBody(
|
||||
@ -232,13 +249,23 @@ class CreatePlaylistRequest private constructor(
|
||||
|
||||
private fun fetch(
|
||||
videoId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
): Pair<String, String>? {
|
||||
val createPlaylistJson = sendCreatePlaylistRequest(videoId, requestHeader)
|
||||
val createPlaylistJson = sendCreatePlaylistRequest(
|
||||
videoId,
|
||||
requestHeader,
|
||||
dataSyncId
|
||||
)
|
||||
if (createPlaylistJson != null) {
|
||||
val playlistId = parseCreatePlaylistResponse(createPlaylistJson)
|
||||
if (playlistId != null) {
|
||||
val setVideoIdJson = sendSetVideoIdRequest(videoId, playlistId, requestHeader)
|
||||
val setVideoIdJson = sendSetVideoIdRequest(
|
||||
videoId,
|
||||
playlistId,
|
||||
requestHeader,
|
||||
dataSyncId
|
||||
)
|
||||
if (setVideoIdJson != null) {
|
||||
val setVideoId = parseSetVideoIdResponse(setVideoIdJson)
|
||||
if (setVideoId != null) {
|
||||
|
@ -22,11 +22,13 @@ import java.util.concurrent.TimeoutException
|
||||
class DeletePlaylistRequest private constructor(
|
||||
private val playlistId: String,
|
||||
private val requestHeader: Map<String, String>,
|
||||
private val dataSyncId: String,
|
||||
) {
|
||||
private val future: Future<Boolean> = Utils.submitOnBackgroundThread {
|
||||
fetch(
|
||||
playlistId,
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
}
|
||||
|
||||
@ -78,14 +80,16 @@ class DeletePlaylistRequest private constructor(
|
||||
@JvmStatic
|
||||
fun fetchRequestIfNeeded(
|
||||
playlistId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
) {
|
||||
Objects.requireNonNull(playlistId)
|
||||
synchronized(cache) {
|
||||
if (!cache.containsKey(playlistId)) {
|
||||
cache[playlistId] = DeletePlaylistRequest(
|
||||
playlistId,
|
||||
requestHeader
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -104,7 +108,8 @@ class DeletePlaylistRequest private constructor(
|
||||
|
||||
private fun sendRequest(
|
||||
playlistId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(playlistId)
|
||||
|
||||
@ -118,7 +123,8 @@ class DeletePlaylistRequest private constructor(
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
DELETE_PLAYLIST,
|
||||
clientType,
|
||||
requestHeader
|
||||
requestHeader,
|
||||
dataSyncId
|
||||
)
|
||||
|
||||
val requestBody = deletePlaylistRequestBody(playlistId)
|
||||
@ -163,9 +169,14 @@ class DeletePlaylistRequest private constructor(
|
||||
|
||||
private fun fetch(
|
||||
playlistId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
): Boolean? {
|
||||
val json = sendRequest(playlistId, requestHeader)
|
||||
val json = sendRequest(
|
||||
playlistId,
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
if (json != null) {
|
||||
return parseResponse(json)
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.EDIT_PLA
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
import app.revanced.extension.shared.utils.Utils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
@ -25,6 +24,7 @@ class EditPlaylistRequest private constructor(
|
||||
private val playlistId: String,
|
||||
private val setVideoId: String?,
|
||||
private val requestHeader: Map<String, String>,
|
||||
private val dataSyncId: String,
|
||||
) {
|
||||
private val future: Future<String> = Utils.submitOnBackgroundThread {
|
||||
fetch(
|
||||
@ -32,6 +32,7 @@ class EditPlaylistRequest private constructor(
|
||||
playlistId,
|
||||
setVideoId,
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
}
|
||||
|
||||
@ -92,7 +93,8 @@ class EditPlaylistRequest private constructor(
|
||||
videoId: String,
|
||||
playlistId: String,
|
||||
setVideoId: String?,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
) {
|
||||
Objects.requireNonNull(videoId)
|
||||
synchronized(cache) {
|
||||
@ -101,7 +103,8 @@ class EditPlaylistRequest private constructor(
|
||||
videoId,
|
||||
playlistId,
|
||||
setVideoId,
|
||||
requestHeader
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -122,7 +125,8 @@ class EditPlaylistRequest private constructor(
|
||||
videoId: String,
|
||||
playlistId: String,
|
||||
setVideoId: String?,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(videoId)
|
||||
|
||||
@ -136,7 +140,8 @@ class EditPlaylistRequest private constructor(
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
EDIT_PLAYLIST,
|
||||
clientType,
|
||||
requestHeader
|
||||
requestHeader,
|
||||
dataSyncId
|
||||
)
|
||||
|
||||
val requestBody = editPlaylistRequestBody(
|
||||
@ -199,11 +204,18 @@ class EditPlaylistRequest private constructor(
|
||||
videoId: String,
|
||||
playlistId: String,
|
||||
setVideoId: String?,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
): String? {
|
||||
val json = sendRequest(videoId, playlistId, setVideoId, requestHeader)
|
||||
val json = sendRequest(
|
||||
videoId,
|
||||
playlistId,
|
||||
setVideoId,
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
if (json != null) {
|
||||
return parseResponse(json, StringUtils.isNotEmpty(setVideoId))
|
||||
return parseResponse(json, setVideoId != null && setVideoId.isNotEmpty())
|
||||
}
|
||||
|
||||
return null
|
||||
|
@ -22,11 +22,13 @@ import java.util.concurrent.TimeoutException
|
||||
class GetPlaylistsRequest private constructor(
|
||||
private val playlistId: String,
|
||||
private val requestHeader: Map<String, String>,
|
||||
private val dataSyncId: String,
|
||||
) {
|
||||
private val future: Future<Array<Pair<String, String>>> = Utils.submitOnBackgroundThread {
|
||||
fetch(
|
||||
playlistId,
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
}
|
||||
|
||||
@ -78,14 +80,16 @@ class GetPlaylistsRequest private constructor(
|
||||
@JvmStatic
|
||||
fun fetchRequestIfNeeded(
|
||||
playlistId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
) {
|
||||
Objects.requireNonNull(playlistId)
|
||||
synchronized(cache) {
|
||||
if (!cache.containsKey(playlistId)) {
|
||||
cache[playlistId] = GetPlaylistsRequest(
|
||||
playlistId,
|
||||
requestHeader
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -104,7 +108,8 @@ class GetPlaylistsRequest private constructor(
|
||||
|
||||
private fun sendRequest(
|
||||
playlistId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(playlistId)
|
||||
|
||||
@ -118,7 +123,8 @@ class GetPlaylistsRequest private constructor(
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
GET_PLAYLISTS,
|
||||
clientType,
|
||||
requestHeader
|
||||
requestHeader,
|
||||
dataSyncId
|
||||
)
|
||||
|
||||
val requestBody = getPlaylistsRequestBody(playlistId)
|
||||
@ -202,9 +208,10 @@ class GetPlaylistsRequest private constructor(
|
||||
|
||||
private fun fetch(
|
||||
playlistId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
): Array<Pair<String, String>>? {
|
||||
val json = sendRequest(playlistId, requestHeader)
|
||||
val json = sendRequest(playlistId, requestHeader, dataSyncId)
|
||||
if (json != null) {
|
||||
return parseResponse(json)
|
||||
}
|
||||
|
@ -23,12 +23,14 @@ class SavePlaylistRequest private constructor(
|
||||
private val playlistId: String,
|
||||
private val libraryId: String,
|
||||
private val requestHeader: Map<String, String>,
|
||||
private val dataSyncId: String,
|
||||
) {
|
||||
private val future: Future<Boolean> = Utils.submitOnBackgroundThread {
|
||||
fetch(
|
||||
playlistId,
|
||||
libraryId,
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
}
|
||||
|
||||
@ -81,14 +83,16 @@ class SavePlaylistRequest private constructor(
|
||||
fun fetchRequestIfNeeded(
|
||||
playlistId: String,
|
||||
libraryId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
) {
|
||||
Objects.requireNonNull(playlistId)
|
||||
synchronized(cache) {
|
||||
cache[libraryId] = SavePlaylistRequest(
|
||||
playlistId,
|
||||
libraryId,
|
||||
requestHeader
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -107,7 +111,8 @@ class SavePlaylistRequest private constructor(
|
||||
private fun sendRequest(
|
||||
playlistId: String,
|
||||
libraryId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(playlistId)
|
||||
Objects.requireNonNull(libraryId)
|
||||
@ -122,7 +127,8 @@ class SavePlaylistRequest private constructor(
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
EDIT_PLAYLIST,
|
||||
clientType,
|
||||
requestHeader
|
||||
requestHeader,
|
||||
dataSyncId
|
||||
)
|
||||
|
||||
val requestBody = savePlaylistRequestBody(libraryId, playlistId)
|
||||
@ -168,9 +174,15 @@ class SavePlaylistRequest private constructor(
|
||||
private fun fetch(
|
||||
playlistId: String,
|
||||
libraryId: String,
|
||||
requestHeader: Map<String, String>
|
||||
requestHeader: Map<String, String>,
|
||||
dataSyncId: String,
|
||||
): Boolean? {
|
||||
val json = sendRequest(playlistId, libraryId, requestHeader)
|
||||
val json = sendRequest(
|
||||
playlistId,
|
||||
libraryId,
|
||||
requestHeader,
|
||||
dataSyncId,
|
||||
)
|
||||
if (json != null) {
|
||||
return parseResponse(json)
|
||||
}
|
||||
|
@ -3,10 +3,14 @@ package app.revanced.extension.youtube.patches.video;
|
||||
import static app.revanced.extension.shared.utils.StringRef.str;
|
||||
import static app.revanced.extension.youtube.shared.RootView.isShortsActive;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.shared.settings.FloatSetting;
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
@ -28,48 +32,61 @@ public class PlaybackSpeedPatch {
|
||||
Settings.DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC.get();
|
||||
private static final long TOAST_DELAY_MILLISECONDS = 750;
|
||||
private static long lastTimeSpeedChanged;
|
||||
|
||||
/**
|
||||
* The last used playback speed.
|
||||
* This value is used when the default playback speed is 'Auto'.
|
||||
*/
|
||||
private static float lastSelectedPlaybackSpeed = 1.0f;
|
||||
private static float lastSelectedShortsPlaybackSpeed = 1.0f;
|
||||
|
||||
private static volatile String channelId = "";
|
||||
private static volatile String videoId = "";
|
||||
private static boolean isLiveStream;
|
||||
/**
|
||||
* The last regular video id.
|
||||
*/
|
||||
private static String videoId = "";
|
||||
|
||||
private static volatile String channelIdShorts = "";
|
||||
private static volatile String videoIdShorts = "";
|
||||
private static boolean isLiveStreamShorts;
|
||||
@GuardedBy("itself")
|
||||
private static final Map<String, Float> ignoredPlaybackSpeedVideoIds = new LinkedHashMap<>() {
|
||||
private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 3;
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry eldest) {
|
||||
return size() > NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* This method is used to reset the playback speed to 1.0 when a general video is started, whether it is a live stream, music, or whitelist.
|
||||
*/
|
||||
public static void newVideoStarted(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName,
|
||||
@NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle,
|
||||
final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) {
|
||||
if (isShortsActive()) {
|
||||
channelIdShorts = newlyLoadedChannelId;
|
||||
videoIdShorts = newlyLoadedVideoId;
|
||||
isLiveStreamShorts = newlyLoadedLiveStreamValue;
|
||||
|
||||
Logger.printDebug(() -> "newVideoStarted: " + newlyLoadedVideoId);
|
||||
} else {
|
||||
channelId = newlyLoadedChannelId;
|
||||
videoId = newlyLoadedVideoId;
|
||||
isLiveStream = newlyLoadedLiveStreamValue;
|
||||
|
||||
Logger.printDebug(() -> "newShortsVideoStarted: " + newlyLoadedVideoId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (videoId.equals(newlyLoadedVideoId)) {
|
||||
return;
|
||||
}
|
||||
videoId = newlyLoadedVideoId;
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void newShortsVideoStarted(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName,
|
||||
@NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle,
|
||||
final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) {
|
||||
channelIdShorts = newlyLoadedChannelId;
|
||||
videoIdShorts = newlyLoadedVideoId;
|
||||
isLiveStreamShorts = newlyLoadedLiveStreamValue;
|
||||
boolean isMusic = isMusic(newlyLoadedVideoId);
|
||||
boolean isWhitelisted = Whitelist.isChannelWhitelistedPlaybackSpeed(newlyLoadedVideoId);
|
||||
|
||||
Logger.printDebug(() -> "newShortsVideoStarted: " + newlyLoadedVideoId);
|
||||
if (newlyLoadedLiveStreamValue || isMusic || isWhitelisted) {
|
||||
synchronized(ignoredPlaybackSpeedVideoIds) {
|
||||
if (!ignoredPlaybackSpeedVideoIds.containsKey(newlyLoadedVideoId)) {
|
||||
lastSelectedPlaybackSpeed = 1.0f;
|
||||
ignoredPlaybackSpeedVideoIds.put(newlyLoadedVideoId, lastSelectedPlaybackSpeed);
|
||||
|
||||
VideoInformation.setPlaybackSpeed(lastSelectedPlaybackSpeed);
|
||||
VideoInformation.overridePlaybackSpeed(lastSelectedPlaybackSpeed);
|
||||
|
||||
Logger.printDebug(() -> "changing playback speed to: 1.0, isLiveStream: " + newlyLoadedLiveStreamValue +
|
||||
", isMusic: " + isMusic + ", isWhitelisted: " + isWhitelisted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,32 +115,29 @@ public class PlaybackSpeedPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* This method is called every second for regular videos and Shorts.
|
||||
*/
|
||||
public static float getPlaybackSpeed(float playbackSpeed) {
|
||||
boolean isShorts = isShortsActive();
|
||||
String currentChannelId = isShorts ? channelIdShorts : channelId;
|
||||
String currentVideoId = isShorts ? videoIdShorts : videoId;
|
||||
boolean currentVideoIsLiveStream = isShorts ? isLiveStreamShorts : isLiveStream;
|
||||
boolean currentVideoIsWhitelisted = Whitelist.isChannelWhitelistedPlaybackSpeed(currentChannelId);
|
||||
boolean currentVideoIsMusic = !isShorts && isMusic();
|
||||
|
||||
if (currentVideoIsLiveStream || currentVideoIsWhitelisted || currentVideoIsMusic) {
|
||||
Logger.printDebug(() -> "changing playback speed to: 1.0");
|
||||
VideoInformation.setPlaybackSpeed(1.0f);
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float defaultPlaybackSpeed = isShorts ? DEFAULT_PLAYBACK_SPEED_SHORTS.get() : DEFAULT_PLAYBACK_SPEED.get();
|
||||
|
||||
if (defaultPlaybackSpeed < 0) {
|
||||
float finalPlaybackSpeed = isShorts ? playbackSpeed : lastSelectedPlaybackSpeed;
|
||||
if (defaultPlaybackSpeed < 0) { // If the default playback speed is 'Auto', it will be overridden to the last used playback speed.
|
||||
float finalPlaybackSpeed = isShorts ? lastSelectedShortsPlaybackSpeed : lastSelectedPlaybackSpeed;
|
||||
VideoInformation.overridePlaybackSpeed(finalPlaybackSpeed);
|
||||
Logger.printDebug(() -> "changing playback speed to: " + finalPlaybackSpeed);
|
||||
return finalPlaybackSpeed;
|
||||
} else {
|
||||
if (isShorts) {
|
||||
VideoInformation.setPlaybackSpeed(defaultPlaybackSpeed);
|
||||
} else { // Otherwise the default playback speed is used.
|
||||
synchronized (ignoredPlaybackSpeedVideoIds) {
|
||||
if (isShorts) {
|
||||
// For Shorts, the VideoInformation.overridePlaybackSpeed() method is not used, so manually save the playback speed in VideoInformation.
|
||||
VideoInformation.setPlaybackSpeed(defaultPlaybackSpeed);
|
||||
} else if (ignoredPlaybackSpeedVideoIds.containsKey(videoId)) {
|
||||
// For general videos, check whether the default video playback speed should not be applied.
|
||||
Logger.printDebug(() -> "changing playback speed to: 1.0");
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "changing playback speed to: " + defaultPlaybackSpeed);
|
||||
return defaultPlaybackSpeed;
|
||||
}
|
||||
@ -138,6 +152,19 @@ public class PlaybackSpeedPatch {
|
||||
public static void userSelectedPlaybackSpeed(float playbackSpeed) {
|
||||
try {
|
||||
boolean isShorts = isShortsActive();
|
||||
|
||||
// Saves the user-selected playback speed in the method.
|
||||
if (isShorts) {
|
||||
lastSelectedShortsPlaybackSpeed = playbackSpeed;
|
||||
} else {
|
||||
lastSelectedPlaybackSpeed = playbackSpeed;
|
||||
// If the user has manually changed the playback speed, the whitelist has already been applied.
|
||||
// If there is a videoId on the map, it will be removed.
|
||||
synchronized (ignoredPlaybackSpeedVideoIds) {
|
||||
ignoredPlaybackSpeedVideoIds.remove(videoId);
|
||||
}
|
||||
}
|
||||
|
||||
if (PatchStatus.RememberPlaybackSpeed()) {
|
||||
BooleanSetting rememberPlaybackSpeedLastSelectedSetting = isShorts
|
||||
? Settings.REMEMBER_PLAYBACK_SPEED_SHORTS_LAST_SELECTED
|
||||
@ -178,15 +205,23 @@ public class PlaybackSpeedPatch {
|
||||
}
|
||||
}, TOAST_DELAY_MILLISECONDS);
|
||||
}
|
||||
} else if (!isShorts){
|
||||
lastSelectedPlaybackSpeed = playbackSpeed;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "userSelectedPlaybackSpeed failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMusic() {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void onDismiss() {
|
||||
synchronized (ignoredPlaybackSpeedVideoIds) {
|
||||
ignoredPlaybackSpeedVideoIds.remove(videoId);
|
||||
videoId = "";
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMusic(String videoId) {
|
||||
if (DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC && !videoId.isEmpty()) {
|
||||
try {
|
||||
MusicRequest request = MusicRequest.getRequestForVideoId(videoId);
|
||||
|
@ -115,7 +115,7 @@ public class ReturnYouTubeDislike {
|
||||
private static final Rect middleSeparatorBounds;
|
||||
|
||||
/**
|
||||
* Left separator horizontal padding for Rolling Number layout.
|
||||
* Horizontal padding between the left and middle separator.
|
||||
*/
|
||||
public static final int leftSeparatorShapePaddingPixels;
|
||||
private static final ShapeDrawable leftSeparatorShape;
|
||||
@ -131,7 +131,7 @@ public class ReturnYouTubeDislike {
|
||||
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
|
||||
middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
|
||||
|
||||
leftSeparatorShapePaddingPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10.0f, dp);
|
||||
leftSeparatorShapePaddingPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8.4f, dp);
|
||||
|
||||
leftSeparatorShape = new ShapeDrawable(new RectShape());
|
||||
leftSeparatorShape.setBounds(leftSeparatorBounds);
|
||||
|
@ -637,31 +637,31 @@ public class Settings extends BaseSettings {
|
||||
public static final FloatSetting SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_SELF_PROMO = new StringSetting("sb_selfpromo", SKIP_AUTOMATICALLY.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_SELF_PROMO_COLOR = new StringSetting("sb_selfpromo_color", "#FFFF00");
|
||||
public static final FloatSetting SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f);
|
||||
public static final FloatSetting SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_INTERACTION = new StringSetting("sb_interaction", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_INTERACTION_COLOR = new StringSetting("sb_interaction_color", "#CC00FF");
|
||||
public static final FloatSetting SB_CATEGORY_INTERACTION_OPACITY = new FloatSetting("sb_interaction_opacity", 0.8f);
|
||||
public static final FloatSetting SB_CATEGORY_INTERACTION_OPACITY = new FloatSetting("sb_interaction_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#FF1684");
|
||||
public static final FloatSetting SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f);
|
||||
public static final FloatSetting SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF");
|
||||
public static final FloatSetting SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f);
|
||||
public static final FloatSetting SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_OUTRO = new StringSetting("sb_outro", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_OUTRO_COLOR = new StringSetting("sb_outro_color", "#0202ED");
|
||||
public static final FloatSetting SB_CATEGORY_OUTRO_OPACITY = new FloatSetting("sb_outro_opacity", 0.8f);
|
||||
public static final FloatSetting SB_CATEGORY_OUTRO_OPACITY = new FloatSetting("sb_outro_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_PREVIEW = new StringSetting("sb_preview", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_PREVIEW_COLOR = new StringSetting("sb_preview_color", "#008FD6");
|
||||
public static final FloatSetting SB_CATEGORY_PREVIEW_OPACITY = new FloatSetting("sb_preview_opacity", 0.8f);
|
||||
public static final FloatSetting SB_CATEGORY_PREVIEW_OPACITY = new FloatSetting("sb_preview_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_FILLER = new StringSetting("sb_filler", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_FILLER_COLOR = new StringSetting("sb_filler_color", "#7300FF");
|
||||
public static final FloatSetting SB_CATEGORY_FILLER_OPACITY = new FloatSetting("sb_filler_opacity", 0.8f);
|
||||
public static final FloatSetting SB_CATEGORY_FILLER_OPACITY = new FloatSetting("sb_filler_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC = new StringSetting("sb_music_offtopic", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC_COLOR = new StringSetting("sb_music_offtopic_color", "#FF9900");
|
||||
public static final FloatSetting SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY = new FloatSetting("sb_music_offtopic_opacity", 0.8f);
|
||||
public static final FloatSetting SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY = new FloatSetting("sb_music_offtopic_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_UNSUBMITTED = new StringSetting("sb_unsubmitted", SKIP_AUTOMATICALLY.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFF");
|
||||
public static final FloatSetting SB_CATEGORY_UNSUBMITTED_OPACITY = new FloatSetting("sb_unsubmitted_opacity", 1.0f);
|
||||
public static final FloatSetting SB_CATEGORY_UNSUBMITTED_OPACITY = new FloatSetting("sb_unsubmitted_opacity", 1.0f);
|
||||
|
||||
// SB Setting not exported
|
||||
public static final LongSetting SB_LAST_VIP_CHECK = new LongSetting("sb_last_vip_check", 0L, false, false);
|
||||
|
@ -377,6 +377,7 @@ public class ReVancedPreferenceFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mSharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
|
||||
Utils.resetLocalizedContext();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,8 @@ import static app.revanced.extension.shared.utils.Utils.isSDKAbove;
|
||||
import android.preference.Preference;
|
||||
import android.preference.SwitchPreference;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.youtube.patches.general.ChangeFormFactorPatch;
|
||||
import app.revanced.extension.youtube.patches.utils.PatchStatus;
|
||||
@ -44,6 +46,7 @@ public class ReVancedSettingsPreference extends ReVancedPreferenceFragment {
|
||||
AmbientModePreferenceLinks();
|
||||
FullScreenPanelPreferenceLinks();
|
||||
NavigationPreferenceLinks();
|
||||
PatchInformationPreferenceLinks();
|
||||
RYDPreferenceLinks();
|
||||
SeekBarPreferenceLinks();
|
||||
ShortsPreferenceLinks();
|
||||
@ -143,6 +146,26 @@ public class ReVancedSettingsPreference extends ReVancedPreferenceFragment {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set patch information preference summary
|
||||
*/
|
||||
private static void PatchInformationPreferenceLinks() {
|
||||
Preference appNamePreference = mPreferenceManager.findPreference("revanced_app_name");
|
||||
if (appNamePreference != null) {
|
||||
appNamePreference.setSummary(ExtendedUtils.getAppLabel());
|
||||
}
|
||||
Preference appVersionPreference = mPreferenceManager.findPreference("revanced_app_version");
|
||||
if (appVersionPreference != null) {
|
||||
appVersionPreference.setSummary(ExtendedUtils.getAppVersionName());
|
||||
}
|
||||
Preference patchedDatePreference = mPreferenceManager.findPreference("revanced_patched_date");
|
||||
if (patchedDatePreference != null) {
|
||||
long patchedTime = PatchStatus.PatchedTime();
|
||||
Date date = new Date(patchedTime);
|
||||
patchedDatePreference.setSummary(date.toLocaleString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/Disable Preference related to RYD settings
|
||||
*/
|
||||
|
@ -37,7 +37,6 @@ import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController
|
||||
|
||||
/**
|
||||
* Not thread safe. All fields/methods must be accessed from the main thread.
|
||||
*
|
||||
*/
|
||||
public class SponsorBlockUtils {
|
||||
private static final int LOCKED_COLOR = Color.parseColor("#FFC83D");
|
||||
|
@ -1,7 +1,36 @@
|
||||
package app.revanced.extension.youtube.sponsorblock.objects;
|
||||
|
||||
import static app.revanced.extension.shared.utils.StringRef.sf;
|
||||
import static app.revanced.extension.youtube.settings.Settings.*;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_FILLER;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_FILLER_COLOR;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_FILLER_OPACITY;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_HIGHLIGHT;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_HIGHLIGHT_COLOR;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_HIGHLIGHT_OPACITY;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTERACTION;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTERACTION_COLOR;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTERACTION_OPACITY;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTRO;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTRO_COLOR;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTRO_OPACITY;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_MUSIC_OFFTOPIC;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_MUSIC_OFFTOPIC_COLOR;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_OUTRO;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_OUTRO_COLOR;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_OUTRO_OPACITY;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_PREVIEW;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_PREVIEW_COLOR;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_PREVIEW_OPACITY;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SELF_PROMO;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SELF_PROMO_COLOR;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SELF_PROMO_OPACITY;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SPONSOR;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SPONSOR_COLOR;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SPONSOR_OPACITY;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_UNSUBMITTED;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_UNSUBMITTED_COLOR;
|
||||
import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_UNSUBMITTED_OPACITY;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
@ -56,7 +85,6 @@ public enum SegmentCategory {
|
||||
SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR, SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY),
|
||||
UNSUBMITTED("unsubmitted", StringRef.empty, sf("revanced_sb_skip_button_unsubmitted"), sf("revanced_sb_skipped_unsubmitted"),
|
||||
SB_CATEGORY_UNSUBMITTED, SB_CATEGORY_UNSUBMITTED_COLOR, SB_CATEGORY_UNSUBMITTED_OPACITY);
|
||||
;
|
||||
|
||||
private static final StringRef skipSponsorTextCompact = sf("revanced_sb_skip_button_compact");
|
||||
private static final StringRef skipSponsorTextCompactHighlight = sf("revanced_sb_skip_button_compact_highlight");
|
||||
|
@ -145,8 +145,10 @@ class SwipeControlsOverlayLayout(
|
||||
addView(feedbackTextView)
|
||||
// get icons scaled, assuming square icons
|
||||
val iconHeight = round(feedbackTextView.lineHeight * .8).toInt()
|
||||
autoBrightnessIcon = getDrawable("revanced_ic_sc_brightness_auto", iconHeight, iconHeight)
|
||||
manualBrightnessIcon = getDrawable("revanced_ic_sc_brightness_manual", iconHeight, iconHeight)
|
||||
autoBrightnessIcon =
|
||||
getDrawable("revanced_ic_sc_brightness_auto", iconHeight, iconHeight)
|
||||
manualBrightnessIcon =
|
||||
getDrawable("revanced_ic_sc_brightness_manual", iconHeight, iconHeight)
|
||||
mutedVolumeIcon = getDrawable("revanced_ic_sc_volume_mute", iconHeight, iconHeight)
|
||||
normalVolumeIcon = getDrawable("revanced_ic_sc_volume_normal", iconHeight, iconHeight)
|
||||
}
|
||||
@ -186,11 +188,18 @@ class SwipeControlsOverlayLayout(
|
||||
/**
|
||||
* Displays the progress bar with the appropriate value, icon, and type (brightness or volume).
|
||||
*/
|
||||
private fun showFeedbackView(value: String, progress: Int, max: Int, icon: Drawable, isBrightness: Boolean) {
|
||||
private fun showFeedbackView(
|
||||
value: String,
|
||||
progress: Int,
|
||||
max: Int,
|
||||
icon: Drawable,
|
||||
isBrightness: Boolean
|
||||
) {
|
||||
feedbackHideHandler.removeCallbacks(feedbackHideCallback)
|
||||
feedbackHideHandler.postDelayed(feedbackHideCallback, config.overlayShowTimeoutMillis)
|
||||
|
||||
val viewToShow = if (config.isCircularProgressBar) circularProgressView else horizontalProgressView
|
||||
val viewToShow =
|
||||
if (config.isCircularProgressBar) circularProgressView else horizontalProgressView
|
||||
viewToShow.apply {
|
||||
setProgress(progress, max, value, isBrightness)
|
||||
this.icon = icon
|
||||
@ -241,7 +250,13 @@ class SwipeControlsOverlayLayout(
|
||||
brightnessValue < 75 -> highBrightnessIcon
|
||||
else -> fullBrightnessIcon
|
||||
}
|
||||
showFeedbackView("$brightnessValue%", brightnessValue, 100, icon, isBrightness = true)
|
||||
showFeedbackView(
|
||||
"$brightnessValue%",
|
||||
brightnessValue,
|
||||
100,
|
||||
icon,
|
||||
isBrightness = true
|
||||
)
|
||||
} else {
|
||||
showFeedbackView("${round(brightness).toInt()}%", manualBrightnessIcon)
|
||||
}
|
||||
@ -274,7 +289,12 @@ abstract class AbstractProgressView(
|
||||
) : View(context, attrs, defStyleAttr) {
|
||||
|
||||
// Combined paint creation function for both fill and stroke styles
|
||||
private fun createPaint(color: Int, style: Paint.Style = Paint.Style.FILL, strokeCap: Paint.Cap = Paint.Cap.BUTT, strokeWidth: Float = 0f) = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
private fun createPaint(
|
||||
color: Int,
|
||||
style: Paint.Style = Paint.Style.FILL,
|
||||
strokeCap: Paint.Cap = Paint.Cap.BUTT,
|
||||
strokeWidth: Float = 0f
|
||||
) = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
this.style = style
|
||||
this.color = color
|
||||
this.strokeCap = strokeCap
|
||||
@ -282,13 +302,18 @@ abstract class AbstractProgressView(
|
||||
}
|
||||
|
||||
// Initialize paints
|
||||
val backgroundPaint = createPaint(overlayBackgroundOpacity, style = Paint.Style.FILL)
|
||||
val progressPaint = createPaint(overlayProgressColor, style = Paint.Style.STROKE, strokeCap = Paint.Cap.ROUND, strokeWidth = 20f)
|
||||
val backgroundPaint = createPaint(overlayBackgroundOpacity, style = Paint.Style.FILL)
|
||||
val progressPaint = createPaint(
|
||||
overlayProgressColor,
|
||||
style = Paint.Style.STROKE,
|
||||
strokeCap = Paint.Cap.ROUND,
|
||||
strokeWidth = 20f
|
||||
)
|
||||
val fillBackgroundPaint = createPaint(overlayFillBackgroundPaint, style = Paint.Style.FILL)
|
||||
val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = overlayTextColor
|
||||
val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = overlayTextColor
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = 40f // Can adjust based on need
|
||||
textSize = 40f // Can adjust based on need
|
||||
}
|
||||
|
||||
protected var progress = 0
|
||||
@ -337,11 +362,11 @@ class CircularProgressView(
|
||||
|
||||
init {
|
||||
textPaint.textSize = 40f // Override default text size for circular view
|
||||
progressPaint.strokeWidth = 20f
|
||||
progressPaint.strokeWidth = 20f
|
||||
fillBackgroundPaint.strokeWidth = 20f
|
||||
progressPaint.strokeCap = Paint.Cap.ROUND
|
||||
progressPaint.strokeCap = Paint.Cap.ROUND
|
||||
fillBackgroundPaint.strokeCap = Paint.Cap.BUTT
|
||||
progressPaint.style = Paint.Style.STROKE
|
||||
progressPaint.style = Paint.Style.STROKE
|
||||
fillBackgroundPaint.style = Paint.Style.STROKE
|
||||
}
|
||||
|
||||
@ -352,7 +377,12 @@ class CircularProgressView(
|
||||
rectF.set(20f, 20f, size - 20f, size - 20f)
|
||||
|
||||
canvas.drawOval(rectF, fillBackgroundPaint) // Draw the outer ring.
|
||||
canvas.drawCircle(width / 2f, height / 2f, size / 3, backgroundPaint) // Draw the inner circle.
|
||||
canvas.drawCircle(
|
||||
width / 2f,
|
||||
height / 2f,
|
||||
size / 3,
|
||||
backgroundPaint
|
||||
) // Draw the inner circle.
|
||||
|
||||
// Select the paint for drawing based on whether it's brightness or volume.
|
||||
val sweepAngle = (progress.toFloat() / maxProgress) * 360
|
||||
@ -399,13 +429,13 @@ class HorizontalProgressView(
|
||||
) {
|
||||
|
||||
private val iconSize = 60f
|
||||
private val padding = 40f
|
||||
private val padding = 40f
|
||||
|
||||
init {
|
||||
textPaint.textSize = 36f // Override default text size for horizontal view
|
||||
textPaint.textSize = 36f // Override default text size for horizontal view
|
||||
progressPaint.strokeWidth = 0f
|
||||
progressPaint.strokeCap = Paint.Cap.BUTT
|
||||
progressPaint.style = Paint.Style.FILL
|
||||
progressPaint.strokeCap = Paint.Cap.BUTT
|
||||
progressPaint.style = Paint.Style.FILL
|
||||
fillBackgroundPaint.style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
@ -428,7 +458,15 @@ class HorizontalProgressView(
|
||||
if (!overlayShowOverlayMinimalStyle) {
|
||||
canvas.drawRoundRect(0f, 0f, width, height, cornerRadius, cornerRadius, backgroundPaint)
|
||||
} else {
|
||||
canvas.drawRoundRect(minimalStartX, 0f, minimalStartX + minimalElementWidth, height, cornerRadius, cornerRadius, backgroundPaint)
|
||||
canvas.drawRoundRect(
|
||||
minimalStartX,
|
||||
0f,
|
||||
minimalStartX + minimalElementWidth,
|
||||
height,
|
||||
cornerRadius,
|
||||
cornerRadius,
|
||||
backgroundPaint
|
||||
)
|
||||
}
|
||||
|
||||
if (!overlayShowOverlayMinimalStyle) {
|
||||
@ -466,7 +504,12 @@ class HorizontalProgressView(
|
||||
padding + minimalStartX
|
||||
}
|
||||
val iconY = height / 2 - iconSize / 2
|
||||
it.setBounds(iconX.toInt(), iconY.toInt(), (iconX + iconSize).toInt(), (iconY + iconSize).toInt())
|
||||
it.setBounds(
|
||||
iconX.toInt(),
|
||||
iconY.toInt(),
|
||||
(iconX + iconSize).toInt(),
|
||||
(iconY + iconSize).toInt()
|
||||
)
|
||||
it.draw(canvas)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
package app.revanced.extension.youtube.utils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AuthUtils {
|
||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
public static final String[] REQUEST_HEADER_KEYS = {
|
||||
AUTHORIZATION_HEADER,
|
||||
"X-GOOG-API-FORMAT-VERSION",
|
||||
"X-Goog-Visitor-Id"
|
||||
};
|
||||
public static volatile String authorization = "";
|
||||
public static volatile String dataSyncId = "";
|
||||
public static volatile boolean isIncognito = false;
|
||||
public static volatile Map<String, String> requestHeader;
|
||||
public static volatile String playlistId = "";
|
||||
public static volatile String videoId = "";
|
||||
|
||||
public static void setRequestHeaders(String url, Map<String, String> requestHeaders) {
|
||||
try {
|
||||
// Save requestHeaders whenever an account is switched.
|
||||
String auth = requestHeaders.get(AUTHORIZATION_HEADER);
|
||||
if (auth == null || authorization.equals(auth)) {
|
||||
return;
|
||||
}
|
||||
for (String key : REQUEST_HEADER_KEYS) {
|
||||
if (requestHeaders.get(key) == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
authorization = auth;
|
||||
requestHeader = requestHeaders;
|
||||
} catch (Exception ex) {
|
||||
Logger.initializationException(AuthUtils.class, "setRequestHeaders failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -31,15 +31,20 @@ import app.revanced.extension.shared.utils.PackageUtils;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
public class ExtendedUtils extends PackageUtils {
|
||||
|
||||
private static boolean isVersionOrGreater(String version) {
|
||||
return getAppVersionName().compareTo(version) >= 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static final boolean IS_19_17_OR_GREATER = getAppVersionName().compareTo("19.17.00") >= 0;
|
||||
public static final boolean IS_19_20_OR_GREATER = getAppVersionName().compareTo("19.20.00") >= 0;
|
||||
public static final boolean IS_19_21_OR_GREATER = getAppVersionName().compareTo("19.21.00") >= 0;
|
||||
public static final boolean IS_19_26_OR_GREATER = getAppVersionName().compareTo("19.26.00") >= 0;
|
||||
public static final boolean IS_19_28_OR_GREATER = getAppVersionName().compareTo("19.28.00") >= 0;
|
||||
public static final boolean IS_19_29_OR_GREATER = getAppVersionName().compareTo("19.29.00") >= 0;
|
||||
public static final boolean IS_19_34_OR_GREATER = getAppVersionName().compareTo("19.34.00") >= 0;
|
||||
public static final boolean IS_20_09_OR_GREATER = getAppVersionName().compareTo("20.09.00") >= 0;
|
||||
public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00");
|
||||
public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00");
|
||||
public static final boolean IS_19_21_OR_GREATER = isVersionOrGreater("19.21.00");
|
||||
public static final boolean IS_19_26_OR_GREATER = isVersionOrGreater("19.26.00");
|
||||
public static final boolean IS_19_28_OR_GREATER = isVersionOrGreater("19.28.00");
|
||||
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
|
||||
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00");
|
||||
public static final boolean IS_20_09_OR_GREATER = isVersionOrGreater("20.09.00");
|
||||
|
||||
public static int validateValue(IntegerSetting settings, int min, int max, String message) {
|
||||
int value = settings.get();
|
||||
|
@ -133,13 +133,25 @@ public class VideoUtils extends IntentUtils {
|
||||
}
|
||||
|
||||
public static void openPlaylist(@NonNull String playlistId, @NonNull String videoId) {
|
||||
openPlaylist(playlistId, videoId, false);
|
||||
}
|
||||
|
||||
public static void openPlaylist(@NonNull String playlistId, @NonNull String videoId, boolean withTimestamp) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (videoId.isEmpty()) {
|
||||
sb.append(getPlaylistUrl(playlistId));
|
||||
} else {
|
||||
sb.append(getVideoScheme(videoId, false));
|
||||
sb.append("&list=");
|
||||
sb.append(VIDEO_URL);
|
||||
sb.append(videoId);
|
||||
sb.append("?list=");
|
||||
sb.append(playlistId);
|
||||
if (withTimestamp) {
|
||||
final long currentVideoTimeInSeconds = VideoInformation.getVideoTimeInSeconds();
|
||||
if (currentVideoTimeInSeconds > 0) {
|
||||
sb.append("&t=");
|
||||
sb.append(currentVideoTimeInSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
launchView(sb.toString(), getContext().getPackageName());
|
||||
}
|
||||
@ -269,6 +281,13 @@ public class VideoUtils extends IntentUtils {
|
||||
return !isExternalDownloaderLaunched.get() && original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rest of the implementation added by patch.
|
||||
*/
|
||||
public static void dismissPlayer() {
|
||||
Logger.printDebug(() -> "Dismiss player");
|
||||
}
|
||||
|
||||
/**
|
||||
* Rest of the implementation added by patch.
|
||||
*/
|
||||
|
@ -1,7 +1,9 @@
|
||||
package com.google.android.apps.youtube.app.settings.videoquality;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
@ -25,8 +27,8 @@ import app.revanced.extension.youtube.utils.ThemeUtils;
|
||||
@SuppressWarnings("deprecation")
|
||||
public class VideoQualitySettingsActivity extends Activity {
|
||||
|
||||
private static final String rvxSettingsLabel = ResourceUtils.getString("revanced_extended_settings_title");
|
||||
private static final String searchLabel = ResourceUtils.getString("revanced_extended_settings_search_title");
|
||||
private static String rvxSettingsLabel;
|
||||
private static String searchLabel;
|
||||
private static WeakReference<SearchView> searchViewRef = new WeakReference<>(null);
|
||||
private static WeakReference<ImageView> closeButtonRef = new WeakReference<>(null);
|
||||
private ReVancedPreferenceFragment fragment;
|
||||
@ -71,6 +73,10 @@ public class VideoQualitySettingsActivity extends Activity {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set label
|
||||
rvxSettingsLabel = getString("revanced_extended_settings_title");
|
||||
searchLabel = getString("revanced_extended_settings_search_title");
|
||||
|
||||
// Set toolbar
|
||||
setToolbar();
|
||||
|
||||
@ -85,6 +91,14 @@ public class VideoQualitySettingsActivity extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
private String getString(String str) {
|
||||
Context baseContext = getBaseContext();
|
||||
Resources resources = baseContext.getResources();
|
||||
int identifier = resources.getIdentifier(str, "string", baseContext.getPackageName());
|
||||
return resources.getString(identifier);
|
||||
}
|
||||
|
||||
private void filterPreferences(String query) {
|
||||
if (fragment == null) return;
|
||||
fragment.filterPreferences(query);
|
||||
|
@ -4,5 +4,5 @@ org.gradle.parallel = true
|
||||
android.useAndroidX = true
|
||||
kotlin.code.style = official
|
||||
kotlin.jvm.target.validation.mode = IGNORE
|
||||
version = 5.6.1-dev.4
|
||||
version = 5.6.2
|
||||
|
||||
|
254
patches.json
254
patches.json
@ -15,8 +15,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -35,8 +34,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -75,8 +73,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -117,8 +114,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -157,8 +153,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -179,8 +174,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -194,7 +188,7 @@
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -226,8 +220,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -270,8 +263,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -311,8 +303,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -352,8 +343,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -389,8 +379,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -496,7 +485,7 @@
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -527,8 +516,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -611,8 +599,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -640,8 +627,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -760,8 +746,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -844,8 +829,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -893,8 +877,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -935,8 +918,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -954,8 +936,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -973,8 +954,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1015,8 +995,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1035,8 +1014,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1046,14 +1024,13 @@
|
||||
"description": "Adds an option to disable the popup that appears when taking a screenshot.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for Reddit",
|
||||
"ResourcePatch"
|
||||
"Settings for Reddit"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1072,8 +1049,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1114,8 +1090,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1154,8 +1129,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1173,8 +1147,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1242,8 +1215,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1268,8 +1240,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1338,6 +1309,15 @@
|
||||
"Clone": "com.rvx.android.apps.youtube.music",
|
||||
"Default": "app.rvx.android.apps.youtube.music"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "patchAllManifest",
|
||||
"title": "Patch all manifest components",
|
||||
"description": "Patch all permissions, intents and content provider authorities supported by GmsCore.",
|
||||
"required": true,
|
||||
"type": "kotlin.Boolean",
|
||||
"default": true,
|
||||
"values": null
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1355,8 +1335,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -1403,6 +1382,15 @@
|
||||
"Clone": "com.rvx.android.apps.youtube.music",
|
||||
"Default": "app.rvx.android.apps.youtube.music"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "patchAllManifest",
|
||||
"title": "Patch all manifest components",
|
||||
"description": "Patch all permissions, intents and content provider authorities supported by GmsCore.",
|
||||
"required": true,
|
||||
"type": "kotlin.Boolean",
|
||||
"default": true,
|
||||
"values": null
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1417,7 +1405,7 @@
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1435,8 +1423,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1454,8 +1441,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1524,8 +1510,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1569,7 +1554,7 @@
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1592,8 +1577,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1614,8 +1598,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1640,8 +1623,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1660,8 +1642,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1709,8 +1690,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1726,7 +1706,7 @@
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1770,8 +1750,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1793,8 +1772,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1829,7 +1807,7 @@
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1848,8 +1826,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -1904,8 +1881,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1926,8 +1902,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1946,8 +1921,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -1967,8 +1941,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2015,8 +1988,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2033,7 +2005,7 @@
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2050,7 +2022,7 @@
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2069,8 +2041,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2094,8 +2065,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -2194,8 +2164,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2209,7 +2178,7 @@
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2250,8 +2219,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2261,13 +2229,14 @@
|
||||
"description": "Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically.",
|
||||
"use": true,
|
||||
"dependencies": [
|
||||
"Settings for Reddit"
|
||||
"Settings for Reddit",
|
||||
"ResourcePatch"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2308,8 +2277,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2374,8 +2342,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2417,8 +2384,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2456,7 +2422,7 @@
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2475,8 +2441,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2500,8 +2465,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2518,7 +2482,7 @@
|
||||
"com.reddit.frontpage": [
|
||||
"2024.17.0",
|
||||
"2025.05.1",
|
||||
"2025.12.0"
|
||||
"2025.12.1"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -2553,8 +2517,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -2564,7 +2527,7 @@
|
||||
"description": "The settings menu name that the RVX settings menu should be above.",
|
||||
"required": true,
|
||||
"type": "kotlin.String",
|
||||
"default": "@string/about_key",
|
||||
"default": "@string/parent_tools_key",
|
||||
"values": {
|
||||
"Parent settings": "@string/parent_tools_key",
|
||||
"General": "@string/general_key",
|
||||
@ -2665,8 +2628,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2685,8 +2647,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -2798,8 +2759,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -2860,8 +2820,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2906,8 +2865,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -2940,8 +2898,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -2960,8 +2917,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -3021,8 +2977,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -3040,8 +2995,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -3160,6 +3114,7 @@
|
||||
"BytecodePatch",
|
||||
"BytecodePatch",
|
||||
"BytecodePatch",
|
||||
"BytecodePatch",
|
||||
"ResourcePatch"
|
||||
],
|
||||
"compatiblePackages": {
|
||||
@ -3168,8 +3123,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
@ -3187,8 +3141,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": [
|
||||
@ -3292,8 +3245,7 @@
|
||||
"19.16.39",
|
||||
"19.43.41",
|
||||
"19.44.39",
|
||||
"19.47.53",
|
||||
"20.03.43"
|
||||
"19.47.53"
|
||||
]
|
||||
},
|
||||
"options": []
|
||||
|
@ -459,7 +459,7 @@ public final class app/revanced/patches/reddit/utils/extension/SharedExtensionPa
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/reddit/utils/resourceid/SharedResourceIdPatchKt {
|
||||
public static final fun getScreenShotShareBanner ()J
|
||||
public static final fun getNsfwDialogTitle ()J
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/reddit/utils/settings/SettingsPatchKt {
|
||||
@ -875,6 +875,7 @@ public final class app/revanced/patches/youtube/player/seekbar/SeekbarComponents
|
||||
|
||||
public final class app/revanced/patches/youtube/shorts/components/FingerprintsKt {
|
||||
public static final fun indexOfAddLiveHeaderElementsContainerInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
|
||||
public static final fun indexOfDismissInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/shorts/components/ShortsComponentPatchKt {
|
||||
@ -894,6 +895,10 @@ public final class app/revanced/patches/youtube/utils/FingerprintsKt {
|
||||
public static final fun indexOfSpannedCharSequenceInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/utils/auth/AuthHookPatchKt {
|
||||
public static final fun getAuthHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/utils/bottomsheet/BottomSheetHookPatchKt {
|
||||
public static final fun getBottomSheetHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@ -906,6 +911,10 @@ public final class app/revanced/patches/youtube/utils/controlsoverlay/ControlsOv
|
||||
public static final fun getControlsOverlayConfigPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/utils/dismiss/DismissPlayerHookPatchKt {
|
||||
public static final fun getDismissPlayerHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/utils/engagement/EngagementPanelHookPatchKt {
|
||||
public static final fun getEngagementPanelHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@ -1038,6 +1047,7 @@ public final class app/revanced/patches/youtube/utils/playservice/VersionCheckPa
|
||||
public static final fun is_19_04_or_greater ()Z
|
||||
public static final fun is_19_05_or_greater ()Z
|
||||
public static final fun is_19_09_or_greater ()Z
|
||||
public static final fun is_19_11_or_greater ()Z
|
||||
public static final fun is_19_15_or_greater ()Z
|
||||
public static final fun is_19_16_or_greater ()Z
|
||||
public static final fun is_19_17_or_greater ()Z
|
||||
@ -1052,10 +1062,12 @@ public final class app/revanced/patches/youtube/utils/playservice/VersionCheckPa
|
||||
public static final fun is_19_34_or_greater ()Z
|
||||
public static final fun is_19_36_or_greater ()Z
|
||||
public static final fun is_19_41_or_greater ()Z
|
||||
public static final fun is_19_42_or_greater ()Z
|
||||
public static final fun is_19_43_or_greater ()Z
|
||||
public static final fun is_19_44_or_greater ()Z
|
||||
public static final fun is_19_46_or_greater ()Z
|
||||
public static final fun is_19_49_or_greater ()Z
|
||||
public static final fun is_19_50_or_greater ()Z
|
||||
public static final fun is_20_02_or_greater ()Z
|
||||
public static final fun is_20_03_or_greater ()Z
|
||||
public static final fun is_20_05_or_greater ()Z
|
||||
|
@ -152,7 +152,10 @@ private enum class MethodCall(
|
||||
RegisterNetworkCallback1(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerNetworkCallback",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
||||
arrayOf(
|
||||
"Landroid/net/NetworkRequest;",
|
||||
"Landroid/net/ConnectivityManager\$NetworkCallback;"
|
||||
),
|
||||
"V",
|
||||
),
|
||||
RegisterNetworkCallback2(
|
||||
@ -174,13 +177,20 @@ private enum class MethodCall(
|
||||
RequestNetwork1(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
||||
arrayOf(
|
||||
"Landroid/net/NetworkRequest;",
|
||||
"Landroid/net/ConnectivityManager\$NetworkCallback;"
|
||||
),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork2(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "I"),
|
||||
arrayOf(
|
||||
"Landroid/net/NetworkRequest;",
|
||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
||||
"I"
|
||||
),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork3(
|
||||
|
@ -19,7 +19,8 @@ val edgeToEdgeDisplayPatch = resourcePatch(
|
||||
// Instead, it checks compileSdkVersion and prints a warning.
|
||||
try {
|
||||
val manifestElement = document.getNode("manifest") as Element
|
||||
val compileSdkVersion = Integer.parseInt(manifestElement.getAttribute("android:compileSdkVersion"))
|
||||
val compileSdkVersion =
|
||||
Integer.parseInt(manifestElement.getAttribute("android:compileSdkVersion"))
|
||||
if (compileSdkVersion < 35) {
|
||||
printWarn("This app may not be forcing edge to edge display (compileSdkVersion: $compileSdkVersion)")
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ val accountComponentsPatch = bytecodePatch(
|
||||
}
|
||||
|
||||
// account switcher
|
||||
val textViewField = with (
|
||||
val textViewField = with(
|
||||
channelHandleFingerprint
|
||||
.methodOrThrow(namesInactiveAccountThumbnailSizeFingerprint)
|
||||
) {
|
||||
@ -117,7 +117,8 @@ val accountComponentsPatch = bytecodePatch(
|
||||
.forEach { index ->
|
||||
val insertIndex = index - 1
|
||||
if (!hook && getInstruction(insertIndex).opcode == Opcode.IF_NEZ) {
|
||||
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
val insertRegister =
|
||||
getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
insertIndex, """
|
||||
|
@ -115,7 +115,8 @@ val adsPatch = bytecodePatch(
|
||||
.methodOrThrow(getPremiumDialogParentFingerprint)
|
||||
.apply {
|
||||
val setContentViewIndex = indexOfSetContentViewInstruction(this)
|
||||
val dialogInstruction = getInstruction<FiveRegisterInstruction>(setContentViewIndex)
|
||||
val dialogInstruction =
|
||||
getInstruction<FiveRegisterInstruction>(setContentViewIndex)
|
||||
val dialogRegister = dialogInstruction.registerC
|
||||
val viewRegister = dialogInstruction.registerD
|
||||
|
||||
|
@ -206,7 +206,8 @@ val changeHeaderPatch = resourcePatch(
|
||||
printWarn(warnings)
|
||||
}
|
||||
|
||||
val isLegacyLogoExists = get("res").resolve("drawable-xxhdpi").resolve("ytm_logo.png").exists()
|
||||
val isLegacyLogoExists =
|
||||
get("res").resolve("drawable-xxhdpi").resolve("ytm_logo.png").exists()
|
||||
if (is_7_27_or_greater && isLegacyLogoExists) {
|
||||
document("res/layout/signin_fragment.xml").use { document ->
|
||||
document.doRecursively node@{ node ->
|
||||
|
@ -1,13 +1,13 @@
|
||||
package app.revanced.patches.music.misc.watchhistory
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.shared.trackingurlhook.hookWatchHistory
|
||||
import app.revanced.patches.shared.trackingurlhook.trackingUrlHookPatch
|
||||
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.music.utils.patch.PatchList.WATCH_HISTORY
|
||||
import app.revanced.patches.music.utils.settings.CategoryType
|
||||
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
|
||||
import app.revanced.patches.music.utils.settings.settingsPatch
|
||||
import app.revanced.patches.shared.trackingurlhook.hookWatchHistory
|
||||
import app.revanced.patches.shared.trackingurlhook.trackingUrlHookPatch
|
||||
|
||||
@Suppress("unused")
|
||||
val watchHistoryPatch = bytecodePatch(
|
||||
|
@ -124,14 +124,17 @@ val navigationBarComponentsPatch = bytecodePatch(
|
||||
opcode == Opcode.IGET_OBJECT &&
|
||||
getReference<FieldReference>()?.type == "Ljava/lang/String;"
|
||||
}
|
||||
val browseIdReference = getInstruction<ReferenceInstruction>(browseIdIndex).reference as FieldReference
|
||||
val browseIdReference =
|
||||
getInstruction<ReferenceInstruction>(browseIdIndex).reference as FieldReference
|
||||
val fieldName = browseIdReference.name
|
||||
val componentIndex = indexOfFirstInstructionOrThrow(stringIndex) {
|
||||
opcode == Opcode.IGET_OBJECT &&
|
||||
getReference<FieldReference>()?.toString() == browseIdReference.toString()
|
||||
}
|
||||
val browseIdRegister = getInstruction<TwoRegisterInstruction>(componentIndex).registerA
|
||||
val componentRegister = getInstruction<TwoRegisterInstruction>(componentIndex).registerB
|
||||
val browseIdRegister =
|
||||
getInstruction<TwoRegisterInstruction>(componentIndex).registerA
|
||||
val componentRegister =
|
||||
getInstruction<TwoRegisterInstruction>(componentIndex).registerB
|
||||
|
||||
val enumIndex = it.patternMatch!!.startIndex + 3
|
||||
val enumRegister = getInstruction<OneRegisterInstruction>(enumIndex).registerA
|
||||
|
@ -54,7 +54,7 @@ internal val engagementPanelHeightFingerprint = legacyFingerprint(
|
||||
parameters = emptyList(),
|
||||
customFingerprint = { method, _ ->
|
||||
AccessFlags.FINAL.isSet(method.accessFlags) &&
|
||||
method.containsLiteralInstruction(1) &&
|
||||
method.containsLiteralInstruction(1) &&
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "booleanValue"
|
||||
|
@ -747,7 +747,8 @@ val playerComponentsPatch = bytecodePatch(
|
||||
getInstruction<FiveRegisterInstruction>(bottomSheetBehaviorIndex).registerD
|
||||
|
||||
val getFieldIndex = bottomSheetBehaviorIndex - 2
|
||||
val getFieldReference = getInstruction<ReferenceInstruction>(getFieldIndex).reference
|
||||
val getFieldReference =
|
||||
getInstruction<ReferenceInstruction>(getFieldIndex).reference
|
||||
val getFieldInstruction = getInstruction<TwoRegisterInstruction>(getFieldIndex)
|
||||
|
||||
addInstructionsWithLabels(
|
||||
|
@ -1,14 +1,8 @@
|
||||
package app.revanced.patches.music.utils.extension
|
||||
|
||||
import app.revanced.patches.music.utils.extension.hooks.applicationInitHook
|
||||
import app.revanced.patches.music.utils.extension.hooks.mainActivityBaseContextHook
|
||||
import app.revanced.patches.shared.extension.hooks.cronetEngineContextHook
|
||||
import app.revanced.patches.shared.extension.hooks.firebaseInitProviderContextHook
|
||||
import app.revanced.patches.shared.extension.sharedExtensionPatch
|
||||
|
||||
val sharedExtensionPatch = sharedExtensionPatch(
|
||||
applicationInitHook,
|
||||
cronetEngineContextHook,
|
||||
firebaseInitProviderContextHook,
|
||||
mainActivityBaseContextHook,
|
||||
)
|
||||
|
@ -1,32 +0,0 @@
|
||||
package app.revanced.patches.music.utils.extension.hooks
|
||||
|
||||
import app.revanced.patches.shared.extension.extensionHook
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
private var attachBaseContextIndex = -1
|
||||
|
||||
internal val mainActivityBaseContextHook = extensionHook(
|
||||
insertIndexResolver = { method ->
|
||||
attachBaseContextIndex = method.indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.name == "attachBaseContext"
|
||||
}
|
||||
|
||||
attachBaseContextIndex + 1
|
||||
},
|
||||
contextRegisterResolver = { method ->
|
||||
val overrideInstruction =
|
||||
method.implementation!!.instructions.elementAt(attachBaseContextIndex)
|
||||
as FiveRegisterInstruction
|
||||
"v${overrideInstruction.registerD}"
|
||||
},
|
||||
) {
|
||||
returns("V")
|
||||
parameters("Landroid/content/Context;")
|
||||
custom { method, classDef ->
|
||||
classDef.type == "Lcom/google/android/apps/youtube/music/activities/MusicActivity;" &&
|
||||
method.name == "attachBaseContext"
|
||||
}
|
||||
}
|
@ -22,7 +22,6 @@ import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
||||
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
|
||||
import app.revanced.patches.music.utils.settings.addSwitchPreference
|
||||
import app.revanced.patches.music.utils.settings.settingsPatch
|
||||
import app.revanced.patches.shared.spoof.blockrequest.blockRequestPatch
|
||||
import app.revanced.patches.shared.createPlayerRequestBodyWithModelFingerprint
|
||||
import app.revanced.patches.shared.customspeed.customPlaybackSpeedPatch
|
||||
import app.revanced.patches.shared.extension.Constants.PATCHES_PATH
|
||||
@ -31,6 +30,7 @@ import app.revanced.patches.shared.indexOfBrandInstruction
|
||||
import app.revanced.patches.shared.indexOfManufacturerInstruction
|
||||
import app.revanced.patches.shared.indexOfModelInstruction
|
||||
import app.revanced.patches.shared.indexOfReleaseInstruction
|
||||
import app.revanced.patches.shared.spoof.blockrequest.blockRequestPatch
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
|
@ -142,7 +142,8 @@ internal val sharedResourceIdPatch = resourcePatch(
|
||||
endButtonsContainer = getResourceId(ID, "end_buttons_container")
|
||||
floatingLayout = getResourceId(ID, "floating_layout")
|
||||
historyMenuItem = getResourceId(ID, "history_menu_item")
|
||||
inlineTimeBarAdBreakMarkerColor = getResourceId(COLOR, "inline_time_bar_ad_break_marker_color")
|
||||
inlineTimeBarAdBreakMarkerColor =
|
||||
getResourceId(COLOR, "inline_time_bar_ad_break_marker_color")
|
||||
inlineTimeBarProgressColor = getResourceId(COLOR, "inline_time_bar_progress_color")
|
||||
interstitialsContainer = getResourceId(ID, "interstitials_container")
|
||||
isTablet = getResourceId(BOOL, "is_tablet")
|
||||
@ -156,7 +157,8 @@ internal val sharedResourceIdPatch = resourcePatch(
|
||||
modernDialogBackground = getResourceId(DRAWABLE, "modern_dialog_background")
|
||||
musicNotifierShelf = getResourceId(LAYOUT, "music_notifier_shelf")
|
||||
musicTasteBuilderShelf = getResourceId(LAYOUT, "music_tastebuilder_shelf")
|
||||
namesInactiveAccountThumbnailSize = getResourceId(DIMEN, "names_inactive_account_thumbnail_size")
|
||||
namesInactiveAccountThumbnailSize =
|
||||
getResourceId(DIMEN, "names_inactive_account_thumbnail_size")
|
||||
offlineSettingsMenuItem = getResourceId(ID, "offline_settings_menu_item")
|
||||
playerOverlayChip = getResourceId(ID, "player_overlay_chip")
|
||||
playerViewPager = getResourceId(ID, "player_view_pager")
|
||||
|
@ -25,7 +25,8 @@ val videoTypeHookPatch = bytecodePatch(
|
||||
|
||||
videoTypeFingerprint.methodOrThrow(videoTypeParentFingerprint).apply {
|
||||
val getEnumIndex = indexOfGetEnumInstruction(this)
|
||||
val enumClass = (getInstruction<ReferenceInstruction>(getEnumIndex).reference as MethodReference).definingClass
|
||||
val enumClass =
|
||||
(getInstruction<ReferenceInstruction>(getEnumIndex).reference as MethodReference).definingClass
|
||||
val referenceIndex = indexOfFirstInstructionOrThrow(getEnumIndex) {
|
||||
opcode == Opcode.SGET_OBJECT &&
|
||||
getReference<FieldReference>()?.type == enumClass
|
||||
|
@ -71,7 +71,8 @@ val playerResponseMethodHookPatch = bytecodePatch(
|
||||
val beforeVideoIdHooks =
|
||||
hooks.filterIsInstance<Hook.PlayerParameterBeforeVideoId>().asReversed()
|
||||
val videoIdHooks = hooks.filterIsInstance<Hook.VideoId>().asReversed()
|
||||
val videoIdAndPlaylistIdHooks = hooks.filterIsInstance<Hook.VideoIdAndPlaylistId>().asReversed()
|
||||
val videoIdAndPlaylistIdHooks =
|
||||
hooks.filterIsInstance<Hook.VideoIdAndPlaylistId>().asReversed()
|
||||
val afterVideoIdHooks = hooks.filterIsInstance<Hook.PlayerParameter>().asReversed()
|
||||
|
||||
// Add the hooks in this specific order as they insert instructions at the beginning of the method.
|
||||
|
@ -56,7 +56,8 @@ val navigationButtonsPatch = bytecodePatch(
|
||||
if (bottomNavScreenFingerprint.resolvable()) {
|
||||
val bottomNavScreenMutableClass = with(bottomNavScreenFingerprint.methodOrThrow()) {
|
||||
val startIndex = indexOfGetDimensionPixelSizeInstruction(this)
|
||||
val targetIndex = indexOfFirstInstructionOrThrow(startIndex, Opcode.NEW_INSTANCE)
|
||||
val targetIndex =
|
||||
indexOfFirstInstructionOrThrow(startIndex, Opcode.NEW_INSTANCE)
|
||||
val targetReference =
|
||||
getInstruction<ReferenceInstruction>(targetIndex).reference.toString()
|
||||
|
||||
@ -65,7 +66,9 @@ val navigationButtonsPatch = bytecodePatch(
|
||||
?: throw ClassNotFoundException("Failed to find class $targetReference")
|
||||
}
|
||||
|
||||
bottomNavScreenOnGlobalLayoutFingerprint.second.matchOrNull(bottomNavScreenMutableClass)
|
||||
bottomNavScreenOnGlobalLayoutFingerprint.second.matchOrNull(
|
||||
bottomNavScreenMutableClass
|
||||
)
|
||||
?.let {
|
||||
it.method.apply {
|
||||
val startIndex = it.patternMatch!!.startIndex
|
||||
@ -82,7 +85,8 @@ val navigationButtonsPatch = bytecodePatch(
|
||||
// Legacy method.
|
||||
bottomNavScreenHandlerFingerprint.methodOrThrow().apply {
|
||||
val targetIndex = indexOfGetItemsInstruction(this) + 1
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
val targetRegister =
|
||||
getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
targetIndex + 1, """
|
||||
|
@ -1,36 +0,0 @@
|
||||
package app.revanced.patches.reddit.layout.screenshotpopup
|
||||
|
||||
import app.revanced.patches.reddit.utils.resourceid.screenShotShareBanner
|
||||
import app.revanced.util.containsLiteralInstruction
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
/**
|
||||
* Reddit 2025.06.0 ~
|
||||
*/
|
||||
internal val screenshotTakenBannerFingerprint = legacyFingerprint(
|
||||
name = "screenshotTakenBannerFingerprint",
|
||||
returnType = "L",
|
||||
opcodes = listOf(
|
||||
Opcode.CONST_4,
|
||||
Opcode.IF_NE,
|
||||
),
|
||||
customFingerprint = { method, classDef ->
|
||||
method.containsLiteralInstruction(screenShotShareBanner) &&
|
||||
classDef.type.startsWith("Lcom/reddit/sharing/screenshot/composables/") &&
|
||||
method.name == "invoke"
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* ~ Reddit 2025.05.1
|
||||
*/
|
||||
internal val screenshotTakenBannerLegacyFingerprint = legacyFingerprint(
|
||||
name = "screenshotTakenBannerLegacyFingerprint",
|
||||
returnType = "V",
|
||||
parameters = listOf("Landroidx/compose/runtime/", "I"),
|
||||
customFingerprint = { method, classDef ->
|
||||
classDef.type.endsWith("\$ScreenshotTakenBannerKt\$lambda-1\$1;") &&
|
||||
method.name == "invoke"
|
||||
}
|
||||
)
|
@ -1,26 +1,22 @@
|
||||
package app.revanced.patches.reddit.layout.screenshotpopup
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.reddit.utils.extension.Constants.PATCHES_PATH
|
||||
import app.revanced.patches.reddit.utils.patch.PatchList.DISABLE_SCREENSHOT_POPUP
|
||||
import app.revanced.patches.reddit.utils.resourceid.screenShotShareBanner
|
||||
import app.revanced.patches.reddit.utils.resourceid.sharedResourceIdPatch
|
||||
import app.revanced.patches.reddit.utils.settings.is_2025_06_or_greater
|
||||
import app.revanced.patches.reddit.utils.settings.settingsPatch
|
||||
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import app.revanced.util.findMutableMethodOf
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
private const val EXTENSION_METHOD_DESCRIPTOR =
|
||||
"$PATCHES_PATH/ScreenshotPopupPatch;->disableScreenshotPopup()Z"
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
@Suppress("unused")
|
||||
val screenshotPopupPatch = bytecodePatch(
|
||||
@ -29,39 +25,67 @@ val screenshotPopupPatch = bytecodePatch(
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
sharedResourceIdPatch,
|
||||
)
|
||||
dependsOn(settingsPatch)
|
||||
|
||||
execute {
|
||||
|
||||
if (is_2025_06_or_greater) {
|
||||
screenshotTakenBannerFingerprint.methodOrThrow().apply {
|
||||
val literalIndex = indexOfFirstLiteralInstructionOrThrow(screenShotShareBanner)
|
||||
val insertIndex = indexOfFirstInstructionReversedOrThrow(literalIndex, Opcode.CONST_4)
|
||||
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
val jumpIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.SGET_OBJECT)
|
||||
fun indexOfShowBannerInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
val reference = getReference<FieldReference>()
|
||||
opcode == Opcode.IGET_OBJECT &&
|
||||
reference?.name?.contains("shouldShowBanner") == true &&
|
||||
reference.definingClass.startsWith("Lcom/reddit/sharing/screenshot/") == true
|
||||
}
|
||||
|
||||
addInstructionsWithLabels(
|
||||
insertIndex, """
|
||||
invoke-static {}, $EXTENSION_METHOD_DESCRIPTOR
|
||||
move-result v$insertRegister
|
||||
if-nez v$insertRegister, :hidden
|
||||
""", ExternalLabel("hidden", getInstruction(jumpIndex))
|
||||
)
|
||||
fun indexOfSetValueInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
getReference<MethodReference>()?.name == "setValue"
|
||||
}
|
||||
} else {
|
||||
screenshotTakenBannerLegacyFingerprint.methodOrThrow().apply {
|
||||
addInstructionsWithLabels(
|
||||
0, """
|
||||
invoke-static {}, $EXTENSION_METHOD_DESCRIPTOR
|
||||
move-result v0
|
||||
if-eqz v0, :dismiss
|
||||
return-void
|
||||
""", ExternalLabel("dismiss", getInstruction(0))
|
||||
)
|
||||
|
||||
fun indexOfBooleanInstruction(method: Method, startIndex: Int = 0) =
|
||||
method.indexOfFirstInstruction(startIndex) {
|
||||
val reference = getReference<FieldReference>()
|
||||
opcode == Opcode.SGET_OBJECT &&
|
||||
reference?.definingClass == "Ljava/lang/Boolean;" &&
|
||||
reference.type == "Ljava/lang/Boolean;"
|
||||
}
|
||||
|
||||
val isScreenShotMethod: Method.() -> Boolean = {
|
||||
definingClass.startsWith("Lcom/reddit/sharing/screenshot/") &&
|
||||
name == "invokeSuspend" &&
|
||||
indexOfShowBannerInstruction(this) >= 0 &&
|
||||
indexOfBooleanInstruction(this) >= 0 &&
|
||||
indexOfSetValueInstruction(this) >= 0
|
||||
}
|
||||
|
||||
var hookCount = 0
|
||||
|
||||
classes.forEach { classDef ->
|
||||
classDef.methods.forEach { method ->
|
||||
if (method.isScreenShotMethod()) {
|
||||
proxy(classDef)
|
||||
.mutableClass
|
||||
.findMutableMethodOf(method)
|
||||
.apply {
|
||||
val showBannerIndex = indexOfShowBannerInstruction(this)
|
||||
val booleanIndex = indexOfBooleanInstruction(this, showBannerIndex)
|
||||
val booleanRegister =
|
||||
getInstruction<OneRegisterInstruction>(booleanIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
booleanIndex + 1, """
|
||||
invoke-static {v$booleanRegister}, $PATCHES_PATH/ScreenshotPopupPatch;->disableScreenshotPopup(Ljava/lang/Boolean;)Ljava/lang/Boolean;
|
||||
move-result-object v$booleanRegister
|
||||
"""
|
||||
)
|
||||
hookCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hookCount == 0) {
|
||||
throw PatchException("Failed to find hook method")
|
||||
}
|
||||
|
||||
updatePatchStatus(
|
||||
|
@ -1,5 +1,6 @@
|
||||
package app.revanced.patches.reddit.layout.subredditdialog
|
||||
|
||||
import app.revanced.patches.reddit.utils.resourceid.nsfwDialogTitle
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
@ -71,6 +72,14 @@ fun indexOfHasBeenVisitedInstruction(method: Method) =
|
||||
reference.returnType == "Z"
|
||||
}
|
||||
|
||||
internal val nsfwAlertBuilderFingerprint = legacyFingerprint(
|
||||
name = "nsfwAlertBuilderFingerprint",
|
||||
literals = listOf(nsfwDialogTitle),
|
||||
customFingerprint = { method, _ ->
|
||||
method.definingClass.startsWith("Lcom/reddit/screen/nsfw")
|
||||
}
|
||||
)
|
||||
|
||||
internal val redditAlertDialogsFingerprint = legacyFingerprint(
|
||||
name = "redditAlertDialogsFingerprint",
|
||||
returnType = "V",
|
||||
|
@ -3,10 +3,12 @@ package app.revanced.patches.reddit.layout.subredditdialog
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.reddit.utils.extension.Constants.PATCHES_PATH
|
||||
import app.revanced.patches.reddit.utils.patch.PatchList.REMOVE_SUBREDDIT_DIALOG
|
||||
import app.revanced.patches.reddit.utils.resourceid.sharedResourceIdPatch
|
||||
import app.revanced.patches.reddit.utils.settings.is_2024_41_or_greater
|
||||
import app.revanced.patches.reddit.utils.settings.is_2025_01_or_greater
|
||||
import app.revanced.patches.reddit.utils.settings.is_2025_05_or_greater
|
||||
@ -14,7 +16,9 @@ import app.revanced.patches.reddit.utils.settings.is_2025_06_or_greater
|
||||
import app.revanced.patches.reddit.utils.settings.settingsPatch
|
||||
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.fingerprint.mutableClassOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
@ -32,7 +36,10 @@ val subRedditDialogPatch = bytecodePatch(
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(settingsPatch)
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
sharedResourceIdPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
|
||||
@ -42,7 +49,8 @@ val subRedditDialogPatch = bytecodePatch(
|
||||
.apply {
|
||||
listOfIsLoggedInInstruction(this)
|
||||
.forEach { index ->
|
||||
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
|
||||
val register =
|
||||
getInstruction<OneRegisterInstruction>(index + 1).registerA
|
||||
|
||||
addInstructions(
|
||||
index + 2, """
|
||||
@ -81,6 +89,32 @@ val subRedditDialogPatch = bytecodePatch(
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
var hookCount = 0
|
||||
|
||||
nsfwAlertBuilderFingerprint.mutableClassOrThrow().let {
|
||||
it.methods.forEach { method ->
|
||||
method.apply {
|
||||
val showIndex = indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "show"
|
||||
}
|
||||
if (showIndex >= 0) {
|
||||
val dialogRegister = getInstruction<OneRegisterInstruction>(showIndex + 1).registerA
|
||||
|
||||
addInstruction(
|
||||
showIndex + 2,
|
||||
"invoke-static {v$dialogRegister}, $EXTENSION_CLASS_DESCRIPTOR->dismissNSFWDialog(Ljava/lang/Object;)V"
|
||||
)
|
||||
hookCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hookCount == 0) {
|
||||
throw PatchException("Failed to find hook method")
|
||||
}
|
||||
}
|
||||
|
||||
// Not used in latest Reddit client.
|
||||
|
@ -11,7 +11,7 @@ internal object Constants {
|
||||
setOf(
|
||||
"2024.17.0", // This is the last version that can be patched without anti-split.
|
||||
"2025.05.1", // This was the latest version supported by the previous RVX patch.
|
||||
"2025.12.0", // This is the latest version supported by the RVX patch.
|
||||
"2025.12.1", // This is the latest version supported by the RVX patch.
|
||||
)
|
||||
)
|
||||
}
|
@ -5,7 +5,7 @@ import app.revanced.patches.shared.mapping.ResourceType.STRING
|
||||
import app.revanced.patches.shared.mapping.getResourceId
|
||||
import app.revanced.patches.shared.mapping.resourceMappingPatch
|
||||
|
||||
var screenShotShareBanner = -1L
|
||||
var nsfwDialogTitle = -1L
|
||||
private set
|
||||
|
||||
internal val sharedResourceIdPatch = resourcePatch(
|
||||
@ -14,6 +14,6 @@ internal val sharedResourceIdPatch = resourcePatch(
|
||||
dependsOn(resourceMappingPatch)
|
||||
|
||||
execute {
|
||||
screenShotShareBanner = getResourceId(STRING, "screenshot_share_banner_title")
|
||||
nsfwDialogTitle = getResourceId(STRING, "nsfw_dialog_title")
|
||||
}
|
||||
}
|
@ -62,7 +62,7 @@ fun baseAdsPatch(
|
||||
)
|
||||
}
|
||||
|
||||
val getAdvertisingIdMethod = with (advertisingIdFingerprint.methodOrThrow()) {
|
||||
val getAdvertisingIdMethod = with(advertisingIdFingerprint.methodOrThrow()) {
|
||||
val getAdvertisingIdIndex = indexOfGetAdvertisingIdInstruction(this)
|
||||
getWalkerMethod(getAdvertisingIdIndex)
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
package app.revanced.patches.shared.extension.hooks
|
||||
|
||||
import app.revanced.patches.shared.extension.extensionHook
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction3rc
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
private var initIndex = -1
|
||||
private var isRange = true
|
||||
|
||||
internal val cronetEngineContextHook = extensionHook(
|
||||
insertIndexResolver = { method ->
|
||||
initIndex = method.indexOfFirstInstruction(Opcode.INVOKE_DIRECT_RANGE)
|
||||
|
||||
if (initIndex < 0) {
|
||||
initIndex = method.indexOfFirstInstructionOrThrow(Opcode.INVOKE_DIRECT)
|
||||
isRange = false
|
||||
}
|
||||
|
||||
initIndex
|
||||
},
|
||||
contextRegisterResolver = { method ->
|
||||
val initInstruction =
|
||||
method.implementation!!.instructions.elementAt(initIndex)
|
||||
if (isRange) {
|
||||
val overrideInstruction = initInstruction as Instruction3rc
|
||||
"v${overrideInstruction.startRegister + 1}"
|
||||
} else {
|
||||
val overrideInstruction = initInstruction as FiveRegisterInstruction
|
||||
"v${overrideInstruction.registerD}"
|
||||
}
|
||||
},
|
||||
) {
|
||||
returns("Lorg/chromium/net/CronetEngine;")
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
strings("Could not create CronetEngine")
|
||||
custom { method, classDef ->
|
||||
method.indexOfFirstInstruction {
|
||||
(opcode == Opcode.INVOKE_DIRECT || opcode == Opcode.INVOKE_DIRECT_RANGE) &&
|
||||
getReference<MethodReference>()?.parameterTypes?.firstOrNull() == "Landroid/content/Context;"
|
||||
} >= 0
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package app.revanced.patches.shared.extension.hooks
|
||||
|
||||
import app.revanced.patches.shared.extension.extensionHook
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
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.reference.MethodReference
|
||||
|
||||
private var getResourcesIndex = -1
|
||||
|
||||
internal val firebaseInitProviderContextHook = extensionHook(
|
||||
insertIndexResolver = { method ->
|
||||
getResourcesIndex = indexOfGerResourcesInstruction(method)
|
||||
|
||||
getResourcesIndex + 2
|
||||
},
|
||||
contextRegisterResolver = { method ->
|
||||
val overrideInstruction =
|
||||
method.implementation!!.instructions.elementAt(getResourcesIndex)
|
||||
as FiveRegisterInstruction
|
||||
|
||||
"v${overrideInstruction.registerC}"
|
||||
},
|
||||
) {
|
||||
strings("firebase_database_url")
|
||||
custom { method, _ ->
|
||||
indexOfGerResourcesInstruction(method) >= 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun indexOfGerResourcesInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.toString() =="Landroid/content/Context;->getResources()Landroid/content/res/Resources;"
|
||||
}
|
@ -6,8 +6,6 @@ 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
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
@ -108,3 +106,8 @@ internal val primesLifecycleEventFingerprint = legacyFingerprint(
|
||||
} >= 0
|
||||
}
|
||||
)
|
||||
|
||||
internal val primeMethodFingerprint = legacyFingerprint(
|
||||
name = "primesLifecycleEventFingerprint",
|
||||
strings = listOf("com.google.android.GoogleCamera", "com.android.vending")
|
||||
)
|
@ -4,6 +4,7 @@ import app.revanced.patcher.Fingerprint
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
@ -18,9 +19,13 @@ import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.patches.shared.extension.Constants.PATCHES_PATH
|
||||
import app.revanced.patches.shared.gms.Constants.ACTIONS
|
||||
import app.revanced.patches.shared.gms.Constants.ACTIONS_LEGACY
|
||||
import app.revanced.patches.shared.gms.Constants.AUTHORITIES
|
||||
import app.revanced.patches.shared.gms.Constants.AUTHORITIES_LEGACY
|
||||
import app.revanced.patches.shared.gms.Constants.PERMISSIONS
|
||||
import app.revanced.patches.shared.gms.Constants.PERMISSIONS_LEGACY
|
||||
import app.revanced.util.Utils.trimIndentMultiline
|
||||
import app.revanced.util.fingerprint.methodOrNull
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.fingerprint.mutableClassOrThrow
|
||||
import app.revanced.util.getReference
|
||||
@ -83,9 +88,9 @@ fun gmsCoreSupportPatch(
|
||||
key = "gmsCoreVendorGroupId",
|
||||
default = "app.revanced",
|
||||
values =
|
||||
mapOf(
|
||||
"ReVanced" to "app.revanced",
|
||||
),
|
||||
mapOf(
|
||||
"ReVanced" to "app.revanced",
|
||||
),
|
||||
title = "GmsCore vendor group ID",
|
||||
description = "The vendor's group ID for GmsCore.",
|
||||
required = true,
|
||||
@ -127,6 +132,14 @@ fun gmsCoreSupportPatch(
|
||||
required = true
|
||||
) { it!!.matches(Regex(PACKAGE_NAME_REGEX_PATTERN)) && it != ORIGINAL_PACKAGE_NAME_YOUTUBE_MUSIC }
|
||||
|
||||
val patchAllManifest by booleanOption(
|
||||
key = "patchAllManifest",
|
||||
default = true,
|
||||
title = "Patch all manifest components",
|
||||
description = "Patch all permissions, intents and content provider authorities supported by GmsCore.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
dependsOn(
|
||||
gmsCoreSupportResourcePatchFactory(
|
||||
gmsCoreVendorGroupIdOption,
|
||||
@ -139,6 +152,20 @@ fun gmsCoreSupportPatch(
|
||||
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
|
||||
|
||||
execute {
|
||||
val patchAllManifestEnabled = patchAllManifest == true
|
||||
val permissions = if (patchAllManifestEnabled)
|
||||
PERMISSIONS
|
||||
else
|
||||
PERMISSIONS_LEGACY
|
||||
val actions = if (patchAllManifestEnabled)
|
||||
ACTIONS
|
||||
else
|
||||
ACTIONS_LEGACY
|
||||
val authorities = if (patchAllManifestEnabled)
|
||||
AUTHORITIES
|
||||
else
|
||||
AUTHORITIES_LEGACY
|
||||
|
||||
fun transformStringReferences(transform: (str: String) -> String?) = classes.forEach {
|
||||
val mutableClass by lazy {
|
||||
proxy(it).mutableClass
|
||||
@ -182,9 +209,9 @@ fun gmsCoreSupportPatch(
|
||||
when (referencedString) {
|
||||
"com.google",
|
||||
"com.google.android.gms",
|
||||
in PERMISSIONS,
|
||||
in ACTIONS,
|
||||
in AUTHORITIES,
|
||||
in permissions,
|
||||
in actions,
|
||||
in authorities,
|
||||
-> referencedString.replace("com.google", gmsCoreVendorGroupId!!)
|
||||
|
||||
// TODO: Add this permission when bumping GmsCore
|
||||
@ -196,7 +223,7 @@ fun gmsCoreSupportPatch(
|
||||
// only when content:// uri
|
||||
if (str.startsWith("content://")) {
|
||||
// check if matches any authority
|
||||
for (authority in AUTHORITIES) {
|
||||
for (authority in authorities) {
|
||||
val uriPrefix = "content://$authority"
|
||||
if (str.startsWith(uriPrefix)) {
|
||||
return str.replace(
|
||||
@ -223,40 +250,56 @@ fun gmsCoreSupportPatch(
|
||||
}
|
||||
}
|
||||
|
||||
fun transformPrimeMethod() {
|
||||
setOf(
|
||||
primesBackgroundInitializationFingerprint,
|
||||
primesLifecycleEventFingerprint
|
||||
).forEach { fingerprint ->
|
||||
fingerprint.methodOrThrow().apply {
|
||||
val exceptionIndex = indexOfFirstInstructionReversedOrThrow {
|
||||
opcode == Opcode.NEW_INSTANCE &&
|
||||
(this as? ReferenceInstruction)?.reference?.toString() == "Ljava/lang/IllegalStateException;"
|
||||
fun transformPrimeMethod(packageName: String) {
|
||||
if (patchAllManifestEnabled) {
|
||||
primeMethodFingerprint.methodOrNull()?.apply {
|
||||
var register = 2
|
||||
|
||||
val index = instructions.indexOfFirst {
|
||||
if (it.getReference<StringReference>()?.string != fromPackageName) return@indexOfFirst false
|
||||
|
||||
register = (it as OneRegisterInstruction).registerA
|
||||
return@indexOfFirst true
|
||||
}
|
||||
val index =
|
||||
indexOfFirstInstructionReversedOrThrow(exceptionIndex, Opcode.IF_EQZ)
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
addInstruction(
|
||||
index,
|
||||
"const/4 v$register, 0x1"
|
||||
)
|
||||
|
||||
replaceInstruction(index, "const-string v$register, \"$packageName\"")
|
||||
}
|
||||
}
|
||||
primesApiFingerprint.mutableClassOrThrow().methods.filter { method ->
|
||||
method.name != "<clinit>" &&
|
||||
method.returnType == "V"
|
||||
}.forEach { method ->
|
||||
method.apply {
|
||||
val index = if (MethodUtil.isConstructor(method))
|
||||
indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.INVOKE_DIRECT &&
|
||||
getReference<MethodReference>()?.name == "<init>"
|
||||
} + 1
|
||||
else 0
|
||||
addInstruction(
|
||||
index,
|
||||
"return-void"
|
||||
)
|
||||
} else {
|
||||
setOf(
|
||||
primesBackgroundInitializationFingerprint,
|
||||
primesLifecycleEventFingerprint
|
||||
).forEach { fingerprint ->
|
||||
fingerprint.methodOrThrow().apply {
|
||||
val exceptionIndex = indexOfFirstInstructionReversedOrThrow {
|
||||
opcode == Opcode.NEW_INSTANCE &&
|
||||
(this as? ReferenceInstruction)?.reference?.toString() == "Ljava/lang/IllegalStateException;"
|
||||
}
|
||||
val index =
|
||||
indexOfFirstInstructionReversedOrThrow(exceptionIndex, Opcode.IF_EQZ)
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
addInstruction(
|
||||
index,
|
||||
"const/4 v$register, 0x1"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
primesApiFingerprint.mutableClassOrThrow().methods.filter { method ->
|
||||
method.name != "<clinit>" &&
|
||||
method.returnType == "V"
|
||||
}.forEach { method ->
|
||||
method.apply {
|
||||
val index = if (MethodUtil.isConstructor(method))
|
||||
indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.INVOKE_DIRECT &&
|
||||
getReference<MethodReference>()?.name == "<init>"
|
||||
} + 1
|
||||
else 0
|
||||
addInstruction(
|
||||
index,
|
||||
"return-void"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -280,37 +323,40 @@ fun gmsCoreSupportPatch(
|
||||
return@transform null
|
||||
}
|
||||
|
||||
// Return these methods early to prevent the app from crashing.
|
||||
setOf(
|
||||
val earlyReturnFingerprints = mutableListOf(
|
||||
castContextFetchFingerprint,
|
||||
castDynamiteModuleFingerprint,
|
||||
castDynamiteModuleV2Fingerprint,
|
||||
googlePlayUtilityFingerprint,
|
||||
serviceCheckFingerprint,
|
||||
sslGuardFingerprint,
|
||||
).forEach { it.methodOrThrow().returnEarly() }
|
||||
serviceCheckFingerprint
|
||||
)
|
||||
|
||||
// Prevent spam logs.
|
||||
eCatcherFingerprint.methodOrThrow().apply {
|
||||
val index = indexOfFirstInstructionOrThrow(Opcode.NEW_ARRAY)
|
||||
addInstruction(index, "return-void")
|
||||
if (patchAllManifestEnabled) {
|
||||
earlyReturnFingerprints += listOf(sslGuardFingerprint)
|
||||
|
||||
// Prevent spam logs.
|
||||
eCatcherFingerprint.methodOrThrow().apply {
|
||||
val index = indexOfFirstInstructionOrThrow(Opcode.NEW_ARRAY)
|
||||
addInstruction(index, "return-void")
|
||||
}
|
||||
}
|
||||
|
||||
// Return these methods early to prevent the app from crashing.
|
||||
earlyReturnFingerprints.forEach { it.methodOrThrow().returnEarly() }
|
||||
|
||||
// Specific method that needs to be patched.
|
||||
transformPrimeMethod()
|
||||
transformPrimeMethod(packageName)
|
||||
|
||||
// Verify GmsCore is installed and whitelisted for power optimizations and background usage.
|
||||
mainActivityOnCreateFingerprint.method.apply {
|
||||
// Temporary fix for patches with an extension patch that hook the onCreate method as well.
|
||||
val setContextIndex = indexOfFirstInstruction {
|
||||
val reference =
|
||||
getReference<MethodReference>() ?: return@indexOfFirstInstruction false
|
||||
if (checkGmsCore == true) {
|
||||
mainActivityOnCreateFingerprint.method.apply {
|
||||
// Temporary fix for patches with an extension patch that hook the onCreate method as well.
|
||||
val setContextIndex = indexOfFirstInstruction {
|
||||
val reference =
|
||||
getReference<MethodReference>() ?: return@indexOfFirstInstruction false
|
||||
|
||||
reference.toString() == "Lapp/revanced/extension/shared/Utils;->setContext(Landroid/content/Context;)V"
|
||||
}
|
||||
reference.toString() == "Lapp/revanced/extension/shared/Utils;->setContext(Landroid/content/Context;)V"
|
||||
}
|
||||
|
||||
// Add after setContext call, because this patch needs the context.
|
||||
if (checkGmsCore == true) {
|
||||
// Add after setContext call, because this patch needs the context.
|
||||
addInstructions(
|
||||
if (setContextIndex < 0) 0 else setContextIndex + 1,
|
||||
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
@ -335,7 +381,30 @@ fun gmsCoreSupportPatch(
|
||||
* that are present in GmsCore which need to be transformed.
|
||||
*/
|
||||
private object Constants {
|
||||
/**
|
||||
* All permissions.
|
||||
*/
|
||||
val PERMISSIONS = setOf(
|
||||
"com.google.android.c2dm.permission.RECEIVE",
|
||||
"com.google.android.c2dm.permission.SEND",
|
||||
"com.google.android.gms.auth.api.phone.permission.SEND",
|
||||
"com.google.android.gms.permission.AD_ID",
|
||||
"com.google.android.gms.permission.AD_ID_NOTIFICATION",
|
||||
"com.google.android.gms.permission.CAR_FUEL",
|
||||
"com.google.android.gms.permission.CAR_INFORMATION",
|
||||
"com.google.android.gms.permission.CAR_MILEAGE",
|
||||
"com.google.android.gms.permission.CAR_SPEED",
|
||||
"com.google.android.gms.permission.CAR_VENDOR_EXTENSION",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.cp",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.local",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.mail",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.writely",
|
||||
"com.google.android.gtalkservice.permission.GTALK_SERVICE",
|
||||
"com.google.android.providers.gsf.permission.READ_GSERVICES",
|
||||
)
|
||||
|
||||
val PERMISSIONS_LEGACY = setOf(
|
||||
// C2DM / GCM
|
||||
"com.google.android.c2dm.permission.RECEIVE",
|
||||
"com.google.android.c2dm.permission.SEND",
|
||||
@ -349,7 +418,234 @@ private object Constants {
|
||||
// "com.google.android.gms.permission.ACTIVITY_RECOGNITION",
|
||||
)
|
||||
|
||||
/**
|
||||
* All intent actions.
|
||||
*/
|
||||
val ACTIONS = setOf(
|
||||
"com.google.android.c2dm.intent.RECEIVE",
|
||||
"com.google.android.c2dm.intent.REGISTER",
|
||||
"com.google.android.c2dm.intent.REGISTRATION",
|
||||
"com.google.android.c2dm.intent.UNREGISTER",
|
||||
"com.google.android.contextmanager.service.ContextManagerService.START",
|
||||
"com.google.android.gcm.intent.SEND",
|
||||
"com.google.android.gms.accounts.ACCOUNT_SERVICE",
|
||||
"com.google.android.gms.accountsettings.ACCOUNT_PREFERENCES_SETTINGS",
|
||||
"com.google.android.gms.accountsettings.action.BROWSE_SETTINGS",
|
||||
"com.google.android.gms.accountsettings.action.VIEW_SETTINGS",
|
||||
"com.google.android.gms.accountsettings.MY_ACCOUNT",
|
||||
"com.google.android.gms.accountsettings.PRIVACY_SETTINGS",
|
||||
"com.google.android.gms.accountsettings.SECURITY_SETTINGS",
|
||||
"com.google.android.gms.ads.gservice.START",
|
||||
"com.google.android.gms.ads.identifier.service.EVENT_ATTESTATION",
|
||||
"com.google.android.gms.ads.service.CACHE",
|
||||
"com.google.android.gms.ads.service.CONSENT_LOOKUP",
|
||||
"com.google.android.gms.ads.service.HTTP",
|
||||
"com.google.android.gms.analytics.service.START",
|
||||
"com.google.android.gms.app.settings.GoogleSettingsLink",
|
||||
"com.google.android.gms.appstate.service.START",
|
||||
"com.google.android.gms.appusage.service.START",
|
||||
"com.google.android.gms.asterism.service.START",
|
||||
"com.google.android.gms.audiomodem.service.AudioModemService.START",
|
||||
"com.google.android.gms.audit.service.START",
|
||||
"com.google.android.gms.auth.account.authapi.START",
|
||||
"com.google.android.gms.auth.account.authenticator.auto.service.START",
|
||||
"com.google.android.gms.auth.account.authenticator.chromeos.START",
|
||||
"com.google.android.gms.auth.account.authenticator.tv.service.START",
|
||||
"com.google.android.gms.auth.account.data.service.START",
|
||||
"com.google.android.gms.auth.api.credentials.PICKER",
|
||||
"com.google.android.gms.auth.api.credentials.service.START",
|
||||
"com.google.android.gms.auth.api.identity.service.authorization.START",
|
||||
"com.google.android.gms.auth.api.identity.service.credentialsaving.START",
|
||||
"com.google.android.gms.auth.api.identity.service.signin.START",
|
||||
"com.google.android.gms.auth.api.phone.service.InternalService.START",
|
||||
"com.google.android.gms.auth.api.signin.service.START",
|
||||
"com.google.android.gms.auth.be.appcert.AppCertService",
|
||||
"com.google.android.gms.auth.blockstore.service.START",
|
||||
"com.google.android.gms.auth.config.service.START",
|
||||
"com.google.android.gms.auth.cryptauth.cryptauthservice.START",
|
||||
"com.google.android.gms.auth.GOOGLE_SIGN_IN",
|
||||
"com.google.android.gms.auth.login.LOGIN",
|
||||
"com.google.android.gms.auth.proximity.devicesyncservice.START",
|
||||
"com.google.android.gms.auth.proximity.securechannelservice.START",
|
||||
"com.google.android.gms.auth.proximity.START",
|
||||
"com.google.android.gms.auth.service.START",
|
||||
"com.google.android.gms.backup.ACTION_BACKUP_SETTINGS",
|
||||
"com.google.android.gms.backup.G1_BACKUP",
|
||||
"com.google.android.gms.backup.G1_RESTORE",
|
||||
"com.google.android.gms.backup.GMS_MODULE_RESTORE",
|
||||
"com.google.android.gms.beacon.internal.IBleService.START",
|
||||
"com.google.android.gms.car.service.START",
|
||||
"com.google.android.gms.carrierauth.service.START",
|
||||
"com.google.android.gms.cast.firstparty.START",
|
||||
"com.google.android.gms.cast.remote_display.service.START",
|
||||
"com.google.android.gms.cast.service.BIND_CAST_DEVICE_CONTROLLER_SERVICE",
|
||||
"com.google.android.gms.cast_mirroring.service.START",
|
||||
"com.google.android.gms.checkin.BIND_TO_SERVICE",
|
||||
"com.google.android.gms.chromesync.service.START",
|
||||
"com.google.android.gms.clearcut.service.START",
|
||||
"com.google.android.gms.common.account.CHOOSE_ACCOUNT",
|
||||
"com.google.android.gms.common.download.START",
|
||||
"com.google.android.gms.common.service.START",
|
||||
"com.google.android.gms.common.telemetry.service.START",
|
||||
"com.google.android.gms.config.START",
|
||||
"com.google.android.gms.constellation.service.START",
|
||||
"com.google.android.gms.credential.manager.service.firstparty.START",
|
||||
"com.google.android.gms.deviceconnection.service.START",
|
||||
"com.google.android.gms.drive.ApiService.RESET_AFTER_BOOT",
|
||||
"com.google.android.gms.drive.ApiService.START",
|
||||
"com.google.android.gms.drive.ApiService.STOP",
|
||||
"com.google.android.gms.droidguard.service.INIT",
|
||||
"com.google.android.gms.droidguard.service.PING",
|
||||
"com.google.android.gms.droidguard.service.START",
|
||||
"com.google.android.gms.enterprise.loader.service.START",
|
||||
"com.google.android.gms.facs.cache.service.START",
|
||||
"com.google.android.gms.facs.internal.service.START",
|
||||
"com.google.android.gms.feedback.internal.IFeedbackService",
|
||||
"com.google.android.gms.fido.credentialstore.internal_service.START",
|
||||
"com.google.android.gms.fido.fido2.privileged.START",
|
||||
"com.google.android.gms.fido.fido2.regular.START",
|
||||
"com.google.android.gms.fido.fido2.zeroparty.START",
|
||||
"com.google.android.gms.fido.sourcedevice.service.START",
|
||||
"com.google.android.gms.fido.targetdevice.internal_service.START",
|
||||
"com.google.android.gms.fido.u2f.privileged.START",
|
||||
"com.google.android.gms.fido.u2f.thirdparty.START",
|
||||
"com.google.android.gms.fido.u2f.zeroparty.START",
|
||||
"com.google.android.gms.fitness.BleApi",
|
||||
"com.google.android.gms.fitness.ConfigApi",
|
||||
"com.google.android.gms.fitness.GoalsApi",
|
||||
"com.google.android.gms.fitness.GoogleFitnessService.START",
|
||||
"com.google.android.gms.fitness.HistoryApi",
|
||||
"com.google.android.gms.fitness.InternalApi",
|
||||
"com.google.android.gms.fitness.RecordingApi",
|
||||
"com.google.android.gms.fitness.SensorsApi",
|
||||
"com.google.android.gms.fitness.SessionsApi",
|
||||
"com.google.android.gms.fonts.service.START",
|
||||
"com.google.android.gms.freighter.service.START",
|
||||
"com.google.android.gms.games.internal.connect.service.START",
|
||||
"com.google.android.gms.games.PLAY_GAMES_UPGRADE",
|
||||
"com.google.android.gms.games.service.START",
|
||||
"com.google.android.gms.gass.START",
|
||||
"com.google.android.gms.gmscompliance.service.START",
|
||||
"com.google.android.gms.googlehelp.HELP",
|
||||
"com.google.android.gms.googlehelp.service.GoogleHelpService.START",
|
||||
"com.google.android.gms.growth.service.START",
|
||||
"com.google.android.gms.herrevad.services.LightweightNetworkQualityAndroidService.START",
|
||||
"com.google.android.gms.icing.INDEX_SERVICE",
|
||||
"com.google.android.gms.icing.LIGHTWEIGHT_INDEX_SERVICE",
|
||||
"com.google.android.gms.identity.service.BIND",
|
||||
"com.google.android.gms.inappreach.service.START",
|
||||
"com.google.android.gms.instantapps.START",
|
||||
"com.google.android.gms.kids.service.START",
|
||||
"com.google.android.gms.languageprofile.service.START",
|
||||
"com.google.android.gms.learning.internal.dynamitesupport.START",
|
||||
"com.google.android.gms.learning.intservice.START",
|
||||
"com.google.android.gms.learning.predictor.START",
|
||||
"com.google.android.gms.learning.trainer.START",
|
||||
"com.google.android.gms.learning.training.background.START",
|
||||
"com.google.android.gms.location.places.GeoDataApi",
|
||||
"com.google.android.gms.location.places.PlaceDetectionApi",
|
||||
"com.google.android.gms.location.places.PlacesApi",
|
||||
"com.google.android.gms.location.reporting.service.START",
|
||||
"com.google.android.gms.location.settings.LOCATION_HISTORY",
|
||||
"com.google.android.gms.location.settings.LOCATION_REPORTING_SETTINGS",
|
||||
"com.google.android.gms.locationsharing.api.START",
|
||||
"com.google.android.gms.locationsharingreporter.service.START",
|
||||
"com.google.android.gms.lockbox.service.START",
|
||||
"com.google.android.gms.matchstick.lighter.service.START",
|
||||
"com.google.android.gms.mdm.services.DeviceManagerApiService.START",
|
||||
"com.google.android.gms.mdm.services.START",
|
||||
"com.google.android.gms.mdns.service.START",
|
||||
"com.google.android.gms.measurement.START",
|
||||
"com.google.android.gms.nearby.bootstrap.service.NearbyBootstrapService.START",
|
||||
"com.google.android.gms.nearby.connection.service.START",
|
||||
"com.google.android.gms.nearby.fastpair.START",
|
||||
"com.google.android.gms.nearby.messages.service.NearbyMessagesService.START",
|
||||
"com.google.android.gms.nearby.sharing.service.NearbySharingService.START",
|
||||
"com.google.android.gms.nearby.sharing.START_SERVICE",
|
||||
"com.google.android.gms.notifications.service.START",
|
||||
"com.google.android.gms.ocr.service.internal.START",
|
||||
"com.google.android.gms.ocr.service.START",
|
||||
"com.google.android.gms.oss.licenses.service.START",
|
||||
"com.google.android.gms.payse.service.BIND",
|
||||
"com.google.android.gms.people.contactssync.service.START",
|
||||
"com.google.android.gms.people.service.START",
|
||||
"com.google.android.gms.phenotype.service.START",
|
||||
"com.google.android.gms.photos.autobackup.service.START",
|
||||
"com.google.android.gms.playlog.service.START",
|
||||
"com.google.android.gms.plus.service.default.INTENT",
|
||||
"com.google.android.gms.plus.service.image.INTENT",
|
||||
"com.google.android.gms.plus.service.internal.START",
|
||||
"com.google.android.gms.plus.service.START",
|
||||
"com.google.android.gms.potokens.service.START",
|
||||
"com.google.android.gms.pseudonymous.service.START",
|
||||
"com.google.android.gms.rcs.START",
|
||||
"com.google.android.gms.reminders.service.START",
|
||||
"com.google.android.gms.romanesco.MODULE_BACKUP_AGENT",
|
||||
"com.google.android.gms.romanesco.service.START",
|
||||
"com.google.android.gms.safetynet.service.START",
|
||||
"com.google.android.gms.scheduler.ACTION_PROXY_SCHEDULE",
|
||||
"com.google.android.gms.search.service.SEARCH_AUTH_START",
|
||||
"com.google.android.gms.semanticlocation.service.START_ODLH",
|
||||
"com.google.android.gms.sesame.service.BIND",
|
||||
"com.google.android.gms.settings.EXPOSURE_NOTIFICATION_SETTINGS",
|
||||
"com.google.android.gms.setup.auth.SecondDeviceAuth.START",
|
||||
"com.google.android.gms.signin.service.START",
|
||||
"com.google.android.gms.smartdevice.d2d.SourceDeviceService.START",
|
||||
"com.google.android.gms.smartdevice.d2d.TargetDeviceService.START",
|
||||
"com.google.android.gms.smartdevice.directtransfer.SourceDirectTransferService.START",
|
||||
"com.google.android.gms.smartdevice.directtransfer.TargetDirectTransferService.START",
|
||||
"com.google.android.gms.smartdevice.postsetup.PostSetupService.START",
|
||||
"com.google.android.gms.smartdevice.setup.accounts.AccountsService.START",
|
||||
"com.google.android.gms.smartdevice.wifi.START_WIFI_HELPER_SERVICE",
|
||||
"com.google.android.gms.social.location.activity.service.START",
|
||||
"com.google.android.gms.speech.service.START",
|
||||
"com.google.android.gms.statementservice.EXECUTE",
|
||||
"com.google.android.gms.stats.ACTION_UPLOAD_DROPBOX_ENTRIES",
|
||||
"com.google.android.gms.tapandpay.service.BIND",
|
||||
"com.google.android.gms.telephonyspam.service.START",
|
||||
"com.google.android.gms.testsupport.service.START",
|
||||
"com.google.android.gms.thunderbird.service.START",
|
||||
"com.google.android.gms.trustagent.BridgeApi.START",
|
||||
"com.google.android.gms.trustagent.StateApi.START",
|
||||
"com.google.android.gms.trustagent.trustlet.trustletmanagerservice.BIND",
|
||||
"com.google.android.gms.trustlet.bluetooth.service.BIND",
|
||||
"com.google.android.gms.trustlet.connectionlessble.service.BIND",
|
||||
"com.google.android.gms.trustlet.face.service.BIND",
|
||||
"com.google.android.gms.trustlet.nfc.service.BIND",
|
||||
"com.google.android.gms.trustlet.onbody.service.BIND",
|
||||
"com.google.android.gms.trustlet.place.service.BIND",
|
||||
"com.google.android.gms.trustlet.voiceunlock.service.BIND",
|
||||
"com.google.android.gms.udc.service.START",
|
||||
"com.google.android.gms.update.START_API_SERVICE",
|
||||
"com.google.android.gms.update.START_SERVICE",
|
||||
"com.google.android.gms.update.START_SINGLE_USER_API_SERVICE",
|
||||
"com.google.android.gms.update.START_TV_API_SERVICE",
|
||||
"com.google.android.gms.usagereporting.service.START",
|
||||
"com.google.android.gms.userlocation.service.START",
|
||||
"com.google.android.gms.vehicle.cabin.service.START",
|
||||
"com.google.android.gms.vehicle.climate.service.START",
|
||||
"com.google.android.gms.vehicle.info.service.START",
|
||||
"com.google.android.gms.wallet.service.BIND",
|
||||
"com.google.android.gms.walletp2p.service.firstparty.BIND",
|
||||
"com.google.android.gms.walletp2p.service.zeroparty.BIND",
|
||||
"com.google.android.gms.wearable.BIND",
|
||||
"com.google.android.gms.wearable.BIND_LISTENER",
|
||||
"com.google.android.gms.wearable.DATA_CHANGED",
|
||||
"com.google.android.gms.wearable.MESSAGE_RECEIVED",
|
||||
"com.google.android.gms.wearable.NODE_CHANGED",
|
||||
"com.google.android.gsf.action.GET_GLS",
|
||||
"com.google.android.location.settings.LOCATION_REPORTING_SETTINGS",
|
||||
"com.google.android.mdd.service.START",
|
||||
"com.google.android.mdh.service.listener.START",
|
||||
"com.google.android.mdh.service.START",
|
||||
"com.google.android.mobstore.service.START",
|
||||
"com.google.firebase.auth.api.gms.service.START",
|
||||
"com.google.firebase.dynamiclinks.service.START",
|
||||
"com.google.iid.TOKEN_REQUEST",
|
||||
"com.google.android.gms.location.places.ui.PICK_PLACE",
|
||||
)
|
||||
|
||||
val ACTIONS_LEGACY = setOf(
|
||||
// C2DM / GCM
|
||||
"com.google.android.c2dm.intent.REGISTER",
|
||||
"com.google.android.c2dm.intent.REGISTRATION",
|
||||
@ -407,7 +703,19 @@ private object Constants {
|
||||
"com.google.android.gms.droidguard.service.START",
|
||||
)
|
||||
|
||||
/**
|
||||
* All content provider authorities.
|
||||
*/
|
||||
val AUTHORITIES = setOf(
|
||||
"com.google.android.gms.auth.accounts",
|
||||
"com.google.android.gms.chimera",
|
||||
"com.google.android.gms.fonts",
|
||||
"com.google.android.gms.phenotype",
|
||||
"com.google.android.gsf.gservices",
|
||||
"com.google.settings",
|
||||
)
|
||||
|
||||
val AUTHORITIES_LEGACY = setOf(
|
||||
// gsf
|
||||
"com.google.android.gsf.gservices",
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package app.revanced.patches.shared.mapping
|
||||
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import org.w3c.dom.Element
|
||||
|
||||
|
@ -147,7 +147,8 @@ fun ResourcePatchContext.baseTranslationsPatch(
|
||||
val length = text.length
|
||||
if (!text.endsWith("DEFAULT") &&
|
||||
length >= 2 &&
|
||||
text.subSequence(length - 2, length) !in filteredAppLanguages) {
|
||||
text.subSequence(length - 2, length) !in filteredAppLanguages
|
||||
) {
|
||||
nodesToRemove.add(item)
|
||||
}
|
||||
}
|
||||
|
@ -262,8 +262,10 @@ val feedComponentsPatch = bytecodePatch(
|
||||
val insertIndex = indexOfBufferParserInstruction(this)
|
||||
|
||||
if (is_19_46_or_greater) {
|
||||
val objectIndex = indexOfFirstInstructionReversedOrThrow(insertIndex, Opcode.IGET_OBJECT)
|
||||
val objectRegister = getInstruction<TwoRegisterInstruction>(objectIndex).registerA
|
||||
val objectIndex =
|
||||
indexOfFirstInstructionReversedOrThrow(insertIndex, Opcode.IGET_OBJECT)
|
||||
val objectRegister =
|
||||
getInstruction<TwoRegisterInstruction>(objectIndex).registerA
|
||||
|
||||
addInstructionsWithLabels(
|
||||
insertIndex, """
|
||||
@ -275,7 +277,8 @@ val feedComponentsPatch = bytecodePatch(
|
||||
)
|
||||
} else {
|
||||
val objectIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_OBJECT)
|
||||
val objectRegister = getInstruction<TwoRegisterInstruction>(objectIndex).registerA
|
||||
val objectRegister =
|
||||
getInstruction<TwoRegisterInstruction>(objectIndex).registerA
|
||||
val jumpIndex = it.patternMatch!!.startIndex
|
||||
|
||||
addInstructionsWithLabels(
|
||||
|
@ -90,7 +90,8 @@ val openChannelOfLiveAvatarPatch = bytecodePatch(
|
||||
)
|
||||
|
||||
val playbackStartIndex = indexOfPlaybackStartDescriptorInstruction(this) + 1
|
||||
val playbackStartRegister = getInstruction<OneRegisterInstruction>(playbackStartIndex).registerA
|
||||
val playbackStartRegister =
|
||||
getInstruction<OneRegisterInstruction>(playbackStartIndex).registerA
|
||||
|
||||
val mapIndex = indexOfFirstInstructionOrThrow(playbackStartIndex) {
|
||||
val reference = getReference<MethodReference>()
|
||||
@ -169,15 +170,24 @@ val openChannelOfLiveAvatarPatch = bytecodePatch(
|
||||
val playbackStartIndex = indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.returnType == PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR
|
||||
}
|
||||
val mapIndex = indexOfFirstInstructionReversedOrThrow(playbackStartIndex, Opcode.IPUT)
|
||||
val mapIndex =
|
||||
indexOfFirstInstructionReversedOrThrow(playbackStartIndex, Opcode.IPUT)
|
||||
val mapRegister = getInstruction<TwoRegisterInstruction>(mapIndex).registerA
|
||||
val playbackStartRegister = getInstruction<OneRegisterInstruction>(playbackStartIndex + 1).registerA
|
||||
val videoIdRegister = getInstruction<FiveRegisterInstruction>(playbackStartIndex).registerC
|
||||
val playbackStartRegister =
|
||||
getInstruction<OneRegisterInstruction>(playbackStartIndex + 1).registerA
|
||||
val videoIdRegister =
|
||||
getInstruction<FiveRegisterInstruction>(playbackStartIndex).registerC
|
||||
|
||||
addInstructionsWithLabels(
|
||||
playbackStartIndex + 2, """
|
||||
move-object/from16 v$mapRegister, p2
|
||||
${fetchChannelIdInstructions(playbackStartRegister, mapRegister, videoIdRegister)}
|
||||
${
|
||||
fetchChannelIdInstructions(
|
||||
playbackStartRegister,
|
||||
mapRegister,
|
||||
videoIdRegister
|
||||
)
|
||||
}
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
@ -80,7 +80,8 @@ private val snackBarComponentsBytecodePatch = bytecodePatch(
|
||||
bottomUiContainerThemeFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val darkThemeIndex = it.patternMatch!!.startIndex + 2
|
||||
val darkThemeReference = getInstruction<ReferenceInstruction>(darkThemeIndex).reference.toString()
|
||||
val darkThemeReference =
|
||||
getInstruction<ReferenceInstruction>(darkThemeIndex).reference.toString()
|
||||
|
||||
implementation!!.instructions
|
||||
.withIndex()
|
||||
@ -91,7 +92,8 @@ private val snackBarComponentsBytecodePatch = bytecodePatch(
|
||||
.map { (index, _) -> index }
|
||||
.reversed()
|
||||
.forEach { index ->
|
||||
val appThemeIndex = indexOfFirstInstructionReversedOrThrow(index, Opcode.MOVE_RESULT_OBJECT)
|
||||
val appThemeIndex =
|
||||
indexOfFirstInstructionReversedOrThrow(index, Opcode.MOVE_RESULT_OBJECT)
|
||||
val appThemeRegister =
|
||||
getInstruction<OneRegisterInstruction>(appThemeIndex).registerA
|
||||
val darkThemeRegister =
|
||||
|
@ -86,7 +86,11 @@ val shortsActionButtonsPatch = resourcePatch(
|
||||
|
||||
// Some directory is missing in the bundles.
|
||||
if (inputStreamForLegacy != null && fromFileResolved.exists()) {
|
||||
Files.copy(inputStreamForLegacy, fromFileResolved.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
Files.copy(
|
||||
inputStreamForLegacy,
|
||||
fromFileResolved.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
}
|
||||
|
||||
if (is_19_36_or_greater) {
|
||||
@ -95,7 +99,11 @@ val shortsActionButtonsPatch = resourcePatch(
|
||||
|
||||
// Some directory is missing in the bundles.
|
||||
if (inputStreamForNew != null && toFileResolved.exists()) {
|
||||
Files.copy(inputStreamForNew, toFileResolved.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
Files.copy(
|
||||
inputStreamForNew,
|
||||
toFileResolved.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,15 +217,23 @@ val customBrandingIconPatch = resourcePatch(
|
||||
}
|
||||
}
|
||||
|
||||
val styleMap = mutableMapOf<String, String>()
|
||||
styleMap["Base.Theme.YouTube.Launcher"] =
|
||||
"@style/Theme.AppCompat.DayNight.NoActionBar"
|
||||
val styleList = mutableListOf(
|
||||
Pair(
|
||||
"Base.Theme.YouTube.Launcher",
|
||||
"@style/Theme.AppCompat.DayNight.NoActionBar"
|
||||
),
|
||||
)
|
||||
|
||||
if (is_19_32_or_greater) {
|
||||
styleMap["Theme.YouTube.Home"] = "@style/Base.V27.Theme.YouTube.Home"
|
||||
styleList += listOf(
|
||||
Pair(
|
||||
"Theme.YouTube.Home",
|
||||
"@style/Base.V27.Theme.YouTube.Home"
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
styleMap.forEach { (nodeAttributeName, nodeAttributeParent) ->
|
||||
styleList.forEach { (nodeAttributeName, nodeAttributeParent) ->
|
||||
document("res/values-v31/styles.xml").use { document ->
|
||||
val resourcesNode =
|
||||
document.getElementsByTagName("resources").item(0) as Element
|
||||
@ -234,21 +242,27 @@ val customBrandingIconPatch = resourcePatch(
|
||||
style.setAttribute("name", nodeAttributeName)
|
||||
style.setAttribute("parent", nodeAttributeParent)
|
||||
|
||||
val primaryItem = document.createElement("item")
|
||||
primaryItem.setAttribute("name", "android:windowSplashScreenAnimatedIcon")
|
||||
primaryItem.textContent = "@drawable/avd_anim"
|
||||
val secondaryItem = document.createElement("item")
|
||||
secondaryItem.setAttribute(
|
||||
val splashScreenAnimatedIcon = document.createElement("item")
|
||||
splashScreenAnimatedIcon.setAttribute(
|
||||
"name",
|
||||
"android:windowSplashScreenAnimatedIcon"
|
||||
)
|
||||
splashScreenAnimatedIcon.textContent = "@drawable/avd_anim"
|
||||
|
||||
// Deprecated in Android 13+
|
||||
val splashScreenAnimationDuration = document.createElement("item")
|
||||
splashScreenAnimationDuration.setAttribute(
|
||||
"name",
|
||||
"android:windowSplashScreenAnimationDuration"
|
||||
)
|
||||
secondaryItem.textContent = if (appIcon.startsWith("revancify"))
|
||||
"1500"
|
||||
else
|
||||
"1000"
|
||||
splashScreenAnimationDuration.textContent =
|
||||
if (appIcon.startsWith("revancify"))
|
||||
"1500"
|
||||
else
|
||||
"1000"
|
||||
|
||||
style.appendChild(primaryItem)
|
||||
style.appendChild(secondaryItem)
|
||||
style.appendChild(splashScreenAnimatedIcon)
|
||||
style.appendChild(splashScreenAnimationDuration)
|
||||
|
||||
resourcesNode.appendChild(style)
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.CUSTOM_BRANDING_NAME_FOR_YOUTUBE
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusLabel
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.util.removeStringsElements
|
||||
import app.revanced.util.valueOrThrow
|
||||
@ -53,7 +52,5 @@ val customBrandingNamePatch = resourcePatch(
|
||||
.appendChild(stringElement)
|
||||
}
|
||||
|
||||
updatePatchStatusLabel(appName)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -61,8 +61,10 @@ val sharedThemePatch = resourcePatch(
|
||||
0 -> when (nodeAttributeName) {
|
||||
"Base.Theme.YouTube.Launcher.Dark",
|
||||
"Base.Theme.YouTube.Launcher.Cairo.Dark" -> "@color/yt_black1"
|
||||
|
||||
"Base.Theme.YouTube.Launcher.Light",
|
||||
"Base.Theme.YouTube.Launcher.Cairo.Light" -> "@color/yt_white1"
|
||||
|
||||
else -> "null"
|
||||
}
|
||||
|
||||
|
@ -32,8 +32,10 @@ val accessibilityPatch = bytecodePatch(
|
||||
.methods
|
||||
.first { method -> method.name == "<init>" }
|
||||
.apply {
|
||||
val lifecycleObserverIndex = indexOfFirstInstructionReversedOrThrow(Opcode.NEW_INSTANCE)
|
||||
val lifecycleObserverClass = getInstruction<ReferenceInstruction>(lifecycleObserverIndex).reference.toString()
|
||||
val lifecycleObserverIndex =
|
||||
indexOfFirstInstructionReversedOrThrow(Opcode.NEW_INSTANCE)
|
||||
val lifecycleObserverClass =
|
||||
getInstruction<ReferenceInstruction>(lifecycleObserverIndex).reference.toString()
|
||||
|
||||
findMethodOrThrow(lifecycleObserverClass) {
|
||||
accessFlags == AccessFlags.PUBLIC or AccessFlags.FINAL &&
|
||||
|
@ -85,17 +85,19 @@ val backgroundPlaybackPatch = bytecodePatch(
|
||||
backgroundPlaybackManagerCairoFragmentPrimaryFingerprint,
|
||||
backgroundPlaybackManagerCairoFragmentSecondaryFingerprint
|
||||
).forEach { fingerprint ->
|
||||
fingerprint.matchOrThrow(backgroundPlaybackManagerCairoFragmentParentFingerprint).let {
|
||||
it.method.apply {
|
||||
val insertIndex = it.patternMatch!!.startIndex + 4
|
||||
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
fingerprint.matchOrThrow(backgroundPlaybackManagerCairoFragmentParentFingerprint)
|
||||
.let {
|
||||
it.method.apply {
|
||||
val insertIndex = it.patternMatch!!.startIndex + 4
|
||||
val insertRegister =
|
||||
getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
insertIndex,
|
||||
"const/4 v$insertRegister, 0x0"
|
||||
)
|
||||
addInstruction(
|
||||
insertIndex,
|
||||
"const/4 v$insertRegister, 0x0"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pipInputConsumerFeatureFlagFingerprint.injectLiteralInstructionBooleanCall(
|
||||
|
@ -57,20 +57,26 @@ val actionButtonsPatch = bytecodePatch(
|
||||
findMethodOrThrow(parameters[1].type) {
|
||||
name == "toString"
|
||||
}
|
||||
val identifierReference = with (conversionContextToStringMethod) {
|
||||
val identifierReference = with(conversionContextToStringMethod) {
|
||||
val identifierStringIndex =
|
||||
indexOfFirstStringInstructionOrThrow(", identifierProperty=")
|
||||
val identifierStringAppendIndex =
|
||||
indexOfFirstInstructionOrThrow(identifierStringIndex, Opcode.INVOKE_VIRTUAL)
|
||||
val identifierStringAppendIndexRegister = getInstruction<FiveRegisterInstruction>(identifierStringAppendIndex).registerD
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,11 @@ import app.revanced.patches.youtube.utils.resourceid.endScreenElementLayoutCircl
|
||||
import app.revanced.patches.youtube.utils.resourceid.endScreenElementLayoutIcon
|
||||
import app.revanced.patches.youtube.utils.resourceid.endScreenElementLayoutVideo
|
||||
import app.revanced.patches.youtube.utils.resourceid.offlineActionsVideoDeletedUndoSnackbarText
|
||||
import app.revanced.patches.youtube.utils.resourceid.verticalTouchOffsetToEnterFineScrubbing
|
||||
import app.revanced.patches.youtube.utils.resourceid.seekEasyHorizontalTouchOffsetToStartScrubbing
|
||||
import app.revanced.patches.youtube.utils.resourceid.suggestedAction
|
||||
import app.revanced.patches.youtube.utils.resourceid.tapBloomView
|
||||
import app.revanced.patches.youtube.utils.resourceid.touchArea
|
||||
import app.revanced.patches.youtube.utils.resourceid.verticalTouchOffsetToEnterFineScrubbing
|
||||
import app.revanced.patches.youtube.utils.resourceid.verticalTouchOffsetToStartFineScrubbing
|
||||
import app.revanced.patches.youtube.utils.resourceid.videoZoomSnapIndicator
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
|
@ -129,9 +129,11 @@ private val speedOverlayPatch = bytecodePatch(
|
||||
|
||||
// region patch for Custom speed overlay float value
|
||||
|
||||
val speedFieldReference = with (speedOverlayFloatValueFingerprint.methodOrThrow()) {
|
||||
val literalIndex = indexOfFirstLiteralInstructionOrThrow(SPEED_OVERLAY_LEGACY_FEATURE_FLAG)
|
||||
val floatIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.DOUBLE_TO_FLOAT)
|
||||
val speedFieldReference = with(speedOverlayFloatValueFingerprint.methodOrThrow()) {
|
||||
val literalIndex =
|
||||
indexOfFirstLiteralInstructionOrThrow(SPEED_OVERLAY_LEGACY_FEATURE_FLAG)
|
||||
val floatIndex =
|
||||
indexOfFirstInstructionOrThrow(literalIndex, Opcode.DOUBLE_TO_FLOAT)
|
||||
val floatRegister = getInstruction<TwoRegisterInstruction>(floatIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
@ -604,7 +606,9 @@ val playerComponentsPatch = bytecodePatch(
|
||||
)
|
||||
|
||||
if (is_20_12_or_greater) {
|
||||
filmStripOverlayMotionEventPrimaryFingerprint.matchOrThrow(filmStripOverlayStartParentFingerprint).let {
|
||||
filmStripOverlayMotionEventPrimaryFingerprint.matchOrThrow(
|
||||
filmStripOverlayStartParentFingerprint
|
||||
).let {
|
||||
it.method.apply {
|
||||
val index = it.patternMatch!!.startIndex
|
||||
val register = getInstruction<TwoRegisterInstruction>(index).registerA
|
||||
@ -613,7 +617,9 @@ val playerComponentsPatch = bytecodePatch(
|
||||
}
|
||||
}
|
||||
|
||||
filmStripOverlayMotionEventSecondaryFingerprint.matchOrThrow(filmStripOverlayStartParentFingerprint).let {
|
||||
filmStripOverlayMotionEventSecondaryFingerprint.matchOrThrow(
|
||||
filmStripOverlayStartParentFingerprint
|
||||
).let {
|
||||
it.method.apply {
|
||||
val index = it.patternMatch!!.startIndex + 2
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
@ -7,7 +7,6 @@ import app.revanced.patches.youtube.utils.resourceid.quickActionsElementContaine
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
internal val broadcastReceiverFingerprint = legacyFingerprint(
|
||||
name = "broadcastReceiverFingerprint",
|
||||
|
@ -37,7 +37,6 @@ import app.revanced.patches.youtube.utils.youtubeControlsOverlayFingerprint
|
||||
import app.revanced.patches.youtube.video.information.hookBackgroundPlayVideoInformation
|
||||
import app.revanced.patches.youtube.video.information.videoEndMethod
|
||||
import app.revanced.patches.youtube.video.information.videoInformationPatch
|
||||
import app.revanced.util.Utils.printWarn
|
||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
@ -313,8 +312,6 @@ val fullscreenComponentsPatch = bytecodePatch(
|
||||
}
|
||||
|
||||
settingArray += "SETTINGS: KEEP_LANDSCAPE_MODE"
|
||||
} else {
|
||||
printWarn("\"Keep landscape mode\" is not supported in this version. Use YouTube 19.16.39 or earlier.")
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
@ -63,6 +63,7 @@ internal val miniplayerResponseModelSizeCheckFingerprint = legacyFingerprint(
|
||||
// region modern miniplayer
|
||||
|
||||
internal const val MINIPLAYER_MODERN_FEATURE_KEY = 45622882L
|
||||
|
||||
// In later targets this feature flag does nothing and is dead code.
|
||||
internal const val MINIPLAYER_MODERN_FEATURE_LEGACY_KEY = 45630429L
|
||||
internal const val MINIPLAYER_DOUBLE_TAP_FEATURE_KEY = 45628823L
|
||||
|
@ -239,7 +239,8 @@ val miniplayerPatch = bytecodePatch(
|
||||
val register = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
targetIndex + 1, """
|
||||
targetIndex + 1,
|
||||
"""
|
||||
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getMiniplayerDefaultSize(I)I
|
||||
move-result v$register
|
||||
""",
|
||||
|
@ -256,7 +256,8 @@ val overlayButtonsPatch = resourcePatch(
|
||||
width != "0.0dip",
|
||||
)
|
||||
|
||||
val isButton = id.endsWith("_button") && id != "@id/multiview_button" || id == "@id/youtube_controls_fullscreen_button_stub"
|
||||
val isButton =
|
||||
id.endsWith("_button") && id != "@id/multiview_button" || id == "@id/youtube_controls_fullscreen_button_stub"
|
||||
|
||||
// Adjust TimeBar and Chapter bottom padding
|
||||
val timBarItem = mutableMapOf(
|
||||
@ -286,7 +287,10 @@ val overlayButtonsPatch = resourcePatch(
|
||||
if (id.equals("@+id/bottom_margin")) {
|
||||
node.setAttribute("android:layout_height", marginBottom)
|
||||
} else if (id.equals("@id/time_bar_reference_view")) {
|
||||
node.setAttribute("yt:layout_constraintBottom_toTopOf", "@id/quick_actions_container")
|
||||
node.setAttribute(
|
||||
"yt:layout_constraintBottom_toTopOf",
|
||||
"@id/quick_actions_container"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ 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
|
||||
import kotlin.collections.listOf
|
||||
|
||||
internal val shortsSeekbarColorFingerprint = legacyFingerprint(
|
||||
name = "shortsSeekbarColorFingerprint",
|
||||
|
@ -139,7 +139,8 @@ val seekbarComponentsPatch = bytecodePatch(
|
||||
reference?.returnType == "V" &&
|
||||
reference.parameterTypes.isEmpty()
|
||||
}
|
||||
val thisInstanceRegister = getInstruction<FiveRegisterInstruction>(tapSeekIndex).registerC
|
||||
val thisInstanceRegister =
|
||||
getInstruction<FiveRegisterInstruction>(tapSeekIndex).registerC
|
||||
|
||||
val tapSeekClass = getInstruction(tapSeekIndex)
|
||||
.getReference<MethodReference>()!!
|
||||
@ -274,7 +275,10 @@ val seekbarComponentsPatch = bytecodePatch(
|
||||
playerSeekbarHandleColorPrimaryFingerprint,
|
||||
playerSeekbarHandleColorSecondaryFingerprint
|
||||
).forEach {
|
||||
it.methodOrThrow().addColorChangeInstructions(ytStaticBrandRed, "getVideoPlayerSeekbarColorAccent")
|
||||
it.methodOrThrow().addColorChangeInstructions(
|
||||
ytStaticBrandRed,
|
||||
"getVideoPlayerSeekbarColorAccent"
|
||||
)
|
||||
}
|
||||
// If hiding feed seekbar thumbnails, then turn off the cairo gradient
|
||||
// of the watch history menu items as they use the same gradient as the
|
||||
|
@ -22,7 +22,34 @@ import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import kotlin.collections.listOf
|
||||
|
||||
internal val bottomSheetMenuDismissFingerprint = legacyFingerprint(
|
||||
name = "bottomSheetMenuDismissFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = emptyList(),
|
||||
customFingerprint = { method, _ ->
|
||||
indexOfDismissInstruction(method) >= 0
|
||||
}
|
||||
)
|
||||
|
||||
fun indexOfDismissInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.name == "dismiss" &&
|
||||
reference.returnType == "V" &&
|
||||
reference.parameterTypes.isEmpty()
|
||||
}
|
||||
|
||||
internal val bottomSheetMenuItemClickFingerprint = legacyFingerprint(
|
||||
name = "bottomSheetMenuItemClickFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "V",
|
||||
parameters = listOf("Landroid/widget/AdapterView;", "Landroid/view/View;", "I", "J"),
|
||||
customFingerprint = { method, _ ->
|
||||
method.name == "onItemClick"
|
||||
}
|
||||
)
|
||||
|
||||
internal val bottomSheetMenuListBuilderFingerprint = legacyFingerprint(
|
||||
name = "bottomSheetMenuListBuilderFingerprint",
|
||||
|
@ -35,6 +35,7 @@ import app.revanced.patches.youtube.utils.playservice.is_18_31_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.is_18_34_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.is_18_49_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.is_19_02_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.is_19_11_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.is_19_28_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.is_19_34_or_greater
|
||||
@ -74,7 +75,6 @@ import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId
|
||||
import app.revanced.patches.youtube.video.videoid.videoIdPatch
|
||||
import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.addEntryValues
|
||||
import app.revanced.util.cloneMutable
|
||||
import app.revanced.util.copyResources
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
@ -339,7 +339,35 @@ private val shortsCustomActionsPatch = bytecodePatch(
|
||||
}
|
||||
}
|
||||
|
||||
recyclerViewTreeObserverHook("$EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR->onFlyoutMenuCreate(Landroid/support/v7/widget/RecyclerView;)V")
|
||||
if (is_19_11_or_greater) {
|
||||
// The type of the Shorts flyout menu is RecyclerView.
|
||||
recyclerViewTreeObserverHook("$EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR->onFlyoutMenuCreate(Landroid/support/v7/widget/RecyclerView;)V")
|
||||
} else {
|
||||
// The type of the Shorts flyout menu is ListView.
|
||||
val dismissReference = with(
|
||||
bottomSheetMenuDismissFingerprint.methodOrThrow(
|
||||
bottomSheetMenuListBuilderFingerprint
|
||||
)
|
||||
) {
|
||||
val dismissIndex = indexOfDismissInstruction(this)
|
||||
getInstruction<ReferenceInstruction>(dismissIndex).reference
|
||||
}
|
||||
|
||||
bottomSheetMenuItemClickFingerprint
|
||||
.methodOrThrow(bottomSheetMenuListBuilderFingerprint)
|
||||
.addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
invoke-static/range {p2 .. p2}, $EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR->onBottomSheetMenuItemClick(Landroid/view/View;)Z
|
||||
move-result v0
|
||||
if-eqz v0, :ignore
|
||||
invoke-virtual {p0}, $dismissReference
|
||||
return-void
|
||||
:ignore
|
||||
nop
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@ -407,7 +435,7 @@ private val shortsRepeatPatch = bytecodePatch(
|
||||
"setMainActivity"
|
||||
)
|
||||
|
||||
val endScreenReference = with (reelEnumConstructorFingerprint.methodOrThrow()) {
|
||||
val endScreenReference = with(reelEnumConstructorFingerprint.methodOrThrow()) {
|
||||
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID)
|
||||
|
||||
addInstructions(
|
||||
@ -487,7 +515,11 @@ private val shortsRepeatPatch = bytecodePatch(
|
||||
// Manually add the 'Autoplay' code that Google removed.
|
||||
// Tested on YouTube 20.10.
|
||||
if (is_20_09_or_greater) {
|
||||
val (directReference, virtualReference) = with (reelPlaybackFingerprint.methodOrThrow(videoIdFingerprintShorts)) {
|
||||
val (directReference, virtualReference) = with(
|
||||
reelPlaybackFingerprint.methodOrThrow(
|
||||
videoIdFingerprintShorts
|
||||
)
|
||||
) {
|
||||
val directIndex = indexOfInitializationInstruction(this)
|
||||
val virtualIndex = indexOfFirstInstructionOrThrow(directIndex) {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
@ -505,7 +537,8 @@ private val shortsRepeatPatch = bytecodePatch(
|
||||
opcode == Opcode.INVOKE_STATIC &&
|
||||
getReference<MethodReference>()?.definingClass == EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR
|
||||
}
|
||||
val enumRegister = getInstruction<OneRegisterInstruction>(extensionIndex + 1).registerA
|
||||
val enumRegister =
|
||||
getInstruction<OneRegisterInstruction>(extensionIndex + 1).registerA
|
||||
val freeIndex = indexOfFirstInstructionOrThrow(extensionIndex) {
|
||||
opcode == Opcode.SGET_OBJECT &&
|
||||
getReference<FieldReference>()?.name != "a"
|
||||
@ -991,7 +1024,8 @@ val shortsComponentPatch = bytecodePatch(
|
||||
getReference<MethodReference>()?.returnType == PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR
|
||||
}
|
||||
val freeRegister = getInstruction<FiveRegisterInstruction>(index).registerC
|
||||
val playbackStartRegister = getInstruction<OneRegisterInstruction>(index + 1).registerA
|
||||
val playbackStartRegister =
|
||||
getInstruction<OneRegisterInstruction>(index + 1).registerA
|
||||
|
||||
addInstructionsWithLabels(
|
||||
index + 2,
|
||||
|
@ -1,41 +1,17 @@
|
||||
package app.revanced.patches.youtube.shorts.startupshortsreset
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
/**
|
||||
* YouTube v18.15.40 ~ YouTube 19.46.42
|
||||
* YouTube v18.15.40+
|
||||
*/
|
||||
internal val userWasInShortsABConfigFingerprint = legacyFingerprint(
|
||||
internal val userWasInShortsConfigFingerprint = legacyFingerprint(
|
||||
name = "userWasInShortsABConfigFingerprint",
|
||||
returnType = "V",
|
||||
strings = listOf("Failed to get offline response: "),
|
||||
customFingerprint = { method, _ ->
|
||||
indexOfOptionalInstruction(method) >= 0
|
||||
}
|
||||
)
|
||||
|
||||
internal fun indexOfOptionalInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_STATIC &&
|
||||
getReference<MethodReference>().toString() == "Lj${'$'}/util/Optional;->of(Ljava/lang/Object;)Lj${'$'}/util/Optional;"
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube 19.47.53 ~
|
||||
*/
|
||||
internal val userWasInShortsABConfigAlternativeFingerprint = legacyFingerprint(
|
||||
name = "userWasInShortsABConfigAlternativeFingerprint",
|
||||
returnType = "V",
|
||||
parameters = listOf("I"),
|
||||
opcodes = listOf(Opcode.OR_INT_LIT8),
|
||||
strings = listOf("alias", "null"),
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "Z",
|
||||
literals = listOf(45358360L)
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -4,14 +4,10 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.SHORTS_CLASS_DESCRIPTOR
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.DISABLE_RESUMING_SHORTS_ON_STARTUP
|
||||
import app.revanced.patches.youtube.utils.playservice.is_19_46_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.is_20_02_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
@ -19,10 +15,8 @@ import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.getWalkerMethod
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import app.revanced.util.indexOfFirstStringInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
@ -42,50 +36,19 @@ val resumingShortsOnStartupPatch = bytecodePatch(
|
||||
|
||||
execute {
|
||||
|
||||
fun MutableMethod.hookUserWasInShortsABConfig(startIndex: Int) {
|
||||
val walkerIndex = implementation!!.instructions.let {
|
||||
val subListIndex =
|
||||
it.subList(startIndex, startIndex + 20).indexOfFirst { instruction ->
|
||||
val reference = instruction.getReference<MethodReference>()
|
||||
instruction.opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
reference?.returnType == "Z" &&
|
||||
reference.definingClass != "Lj${'$'}/util/Optional;" &&
|
||||
reference.parameterTypes.isEmpty()
|
||||
}
|
||||
if (subListIndex < 0)
|
||||
throw PatchException("subListIndex not found")
|
||||
|
||||
startIndex + subListIndex
|
||||
}
|
||||
val walkerMethod = getWalkerMethod(walkerIndex)
|
||||
|
||||
// This method will only be called for the user being A/B tested.
|
||||
// Presumably a method that processes the ProtoDataStore value (boolean) for the 'user_was_in_shorts' key.
|
||||
walkerMethod.apply {
|
||||
addInstructionsWithLabels(
|
||||
0, """
|
||||
invoke-static {}, $SHORTS_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer()Z
|
||||
move-result v0
|
||||
if-eqz v0, :show
|
||||
const/4 v0, 0x0
|
||||
return v0
|
||||
""", ExternalLabel("show", getInstruction(0))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (is_19_46_or_greater) {
|
||||
userWasInShortsABConfigAlternativeFingerprint.methodOrThrow().apply {
|
||||
val stringIndex = indexOfFirstStringInstructionOrThrow("null")
|
||||
val startIndex = indexOfFirstInstructionOrThrow(stringIndex, Opcode.OR_INT_LIT8)
|
||||
hookUserWasInShortsABConfig(startIndex)
|
||||
}
|
||||
} else {
|
||||
userWasInShortsABConfigFingerprint.methodOrThrow().apply {
|
||||
val startIndex = indexOfOptionalInstruction(this)
|
||||
hookUserWasInShortsABConfig(startIndex)
|
||||
}
|
||||
}
|
||||
userWasInShortsConfigFingerprint
|
||||
.methodOrThrow()
|
||||
.addInstructionsWithLabels(
|
||||
0, """
|
||||
invoke-static {}, $SHORTS_CLASS_DESCRIPTOR->disableResumingStartupShortsPlayer()Z
|
||||
move-result v0
|
||||
if-eqz v0, :show
|
||||
const/4 v0, 0x0
|
||||
return v0
|
||||
:show
|
||||
nop
|
||||
"""
|
||||
)
|
||||
|
||||
if (is_20_02_or_greater) {
|
||||
userWasInShortsAlternativeFingerprint.matchOrThrow().let {
|
||||
|
@ -10,6 +10,7 @@ import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal val fullScreenEngagementOverlayFingerprint = legacyFingerprint(
|
||||
@ -113,11 +114,17 @@ internal val playerGestureConfigSyntheticFingerprint = legacyFingerprint(
|
||||
// This method is always called "a" because this kind of class always has a single method.
|
||||
method.name == "a" &&
|
||||
classDef.methods.count() == 2 &&
|
||||
method.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" &&
|
||||
reference.parameterTypes.isEmpty() &&
|
||||
reference.returnType == "Z"
|
||||
} >= 0
|
||||
indexOfPlayerConfigModelBooleanInstruction(method) >= 0
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
internal fun indexOfPlayerConfigModelBooleanInstruction(
|
||||
method: Method,
|
||||
startIndex: Int = 0
|
||||
) = method.indexOfFirstInstruction(startIndex) {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" &&
|
||||
reference.parameterTypes.isEmpty() &&
|
||||
reference.returnType == "Z"
|
||||
}
|
@ -30,7 +30,6 @@ import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.copyResources
|
||||
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.fingerprint.mutableClassOrThrow
|
||||
import app.revanced.util.getReference
|
||||
@ -212,14 +211,17 @@ val swipeControlsPatch = bytecodePatch(
|
||||
|
||||
// region patch for disable swipe to enter fullscreen mode (in the player) and disable swipe to exit fullscreen mode
|
||||
|
||||
playerGestureConfigSyntheticFingerprint.matchOrThrow().let {
|
||||
val endIndex = it.patternMatch!!.endIndex
|
||||
playerGestureConfigSyntheticFingerprint.methodOrThrow().apply {
|
||||
val disableSwipeToExitFullscreenModeIndex =
|
||||
indexOfPlayerConfigModelBooleanInstruction(this)
|
||||
val disableSwipeToEnterFullscreenModeInThePlayerIndex =
|
||||
indexOfPlayerConfigModelBooleanInstruction(this, disableSwipeToExitFullscreenModeIndex + 1)
|
||||
|
||||
mapOf(
|
||||
3 to "disableSwipeToEnterFullscreenModeInThePlayer",
|
||||
9 to "disableSwipeToExitFullscreenMode"
|
||||
).forEach { (offSet, methodName) ->
|
||||
it.getWalkerMethod(endIndex - offSet).apply {
|
||||
disableSwipeToExitFullscreenModeIndex to "disableSwipeToExitFullscreenMode",
|
||||
disableSwipeToEnterFullscreenModeInThePlayerIndex to "disableSwipeToEnterFullscreenModeInThePlayer"
|
||||
).forEach { (walkerIndex, methodName) ->
|
||||
getWalkerMethod(walkerIndex).apply {
|
||||
val index = implementation!!.instructions.lastIndex
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
|
@ -0,0 +1,34 @@
|
||||
package app.revanced.patches.youtube.utils.auth
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.EXTENSION_PATH
|
||||
import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.utils.request.buildRequestPatch
|
||||
import app.revanced.patches.youtube.utils.request.hookBuildRequest
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
|
||||
private const val EXTENSION_AUTH_UTILS_CLASS_DESCRIPTOR =
|
||||
"$EXTENSION_PATH/utils/AuthUtils;"
|
||||
|
||||
val authHookPatch = bytecodePatch(
|
||||
description = "authHookPatch"
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
buildRequestPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
// Get incognito status and data sync id.
|
||||
accountIdentityFingerprint.methodOrThrow().addInstructions(
|
||||
1, """
|
||||
sput-object p3, $EXTENSION_AUTH_UTILS_CLASS_DESCRIPTOR->dataSyncId:Ljava/lang/String;
|
||||
sput-boolean p4, $EXTENSION_AUTH_UTILS_CLASS_DESCRIPTOR->isIncognito:Z
|
||||
"""
|
||||
)
|
||||
|
||||
// Get the header to use the auth token.
|
||||
hookBuildRequest("$EXTENSION_AUTH_UTILS_CLASS_DESCRIPTOR->setRequestHeaders(Ljava/lang/String;Ljava/util/Map;)V")
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package app.revanced.patches.youtube.utils.auth
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val accountIdentityFingerprint = legacyFingerprint(
|
||||
name = "accountIdentityFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
customFingerprint = { method, _ ->
|
||||
method.definingClass.endsWith("${'$'}AutoValue_AccountIdentity;")
|
||||
}
|
||||
)
|
@ -13,8 +13,7 @@ internal object Constants {
|
||||
"19.16.39", // This is the last version where the 'Restore old seekbar thumbnails' setting works.
|
||||
"19.43.41", // This is the latest version where edge-to-edge display is not enforced on Android 15+.
|
||||
"19.44.39", // This is the only version that has experimental shortcut icons.
|
||||
"19.47.53", // This was the latest version supported by the previous RVX patch.
|
||||
"20.03.43", // This is the latest version supported by the RVX patch.
|
||||
"19.47.53", // This is the latest version supported by the RVX patch.
|
||||
)
|
||||
)
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package app.revanced.patches.youtube.utils.dismiss
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.EXTENSION_PATH
|
||||
import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch
|
||||
import app.revanced.util.addStaticFieldToExtension
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.getWalkerMethod
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
private const val EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR =
|
||||
"$EXTENSION_PATH/utils/VideoUtils;"
|
||||
|
||||
private lateinit var dismissMethod: MutableMethod
|
||||
|
||||
val dismissPlayerHookPatch = bytecodePatch(
|
||||
description = "dismissPlayerHookPatch"
|
||||
) {
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
execute {
|
||||
dismissPlayerOnClickListenerFingerprint.methodOrThrow().apply {
|
||||
val literalIndex =
|
||||
indexOfFirstLiteralInstructionOrThrow(DISMISS_PLAYER_LITERAL)
|
||||
val dismissPlayerIndex = indexOfFirstInstructionOrThrow(literalIndex) {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
reference?.returnType == "V" &&
|
||||
reference.parameterTypes.isEmpty()
|
||||
}
|
||||
|
||||
getWalkerMethod(dismissPlayerIndex).apply {
|
||||
val jumpIndex = indexOfFirstInstructionReversedOrThrow {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.returnType == "V"
|
||||
}
|
||||
getWalkerMethod(jumpIndex).apply {
|
||||
val jumpIndex = indexOfFirstInstructionReversedOrThrow {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.returnType == "V"
|
||||
}
|
||||
dismissMethod = getWalkerMethod(jumpIndex)
|
||||
}
|
||||
}
|
||||
|
||||
val dismissPlayerReference =
|
||||
getInstruction<ReferenceInstruction>(dismissPlayerIndex).reference as MethodReference
|
||||
val dismissPlayerClass = dismissPlayerReference.definingClass
|
||||
|
||||
val fieldIndex =
|
||||
indexOfFirstInstructionReversedOrThrow(dismissPlayerIndex) {
|
||||
opcode == Opcode.IGET_OBJECT &&
|
||||
getReference<FieldReference>()?.type == dismissPlayerClass
|
||||
}
|
||||
val fieldReference =
|
||||
getInstruction<ReferenceInstruction>(fieldIndex).reference as FieldReference
|
||||
|
||||
findMethodOrThrow(fieldReference.definingClass).apply {
|
||||
val insertIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
getReference<FieldReference>() == fieldReference
|
||||
}
|
||||
val insertRegister =
|
||||
getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
insertIndex,
|
||||
"sput-object v$insertRegister, $EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR->dismissPlayerClass:$dismissPlayerClass"
|
||||
)
|
||||
|
||||
val smaliInstructions =
|
||||
"""
|
||||
if-eqz v0, :ignore
|
||||
invoke-virtual {v0}, $dismissPlayerReference
|
||||
:ignore
|
||||
return-void
|
||||
"""
|
||||
|
||||
addStaticFieldToExtension(
|
||||
EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR,
|
||||
"dismissPlayer",
|
||||
"dismissPlayerClass",
|
||||
dismissPlayerClass,
|
||||
smaliInstructions,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when the video is closed.
|
||||
*/
|
||||
internal fun hookDismissObserver(descriptor: String) =
|
||||
dismissMethod.addInstruction(
|
||||
0,
|
||||
"invoke-static {}, $descriptor"
|
||||
)
|
@ -0,0 +1,17 @@
|
||||
package app.revanced.patches.youtube.utils.dismiss
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal const val DISMISS_PLAYER_LITERAL = 34699L
|
||||
|
||||
internal val dismissPlayerOnClickListenerFingerprint = legacyFingerprint(
|
||||
name = "dismissPlayerOnClickListenerFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
literals = listOf(DISMISS_PLAYER_LITERAL),
|
||||
customFingerprint = { method, _ ->
|
||||
method.name == "onClick"
|
||||
}
|
||||
)
|
@ -1,17 +1,9 @@
|
||||
package app.revanced.patches.youtube.utils.extension
|
||||
|
||||
import app.revanced.patches.shared.extension.hooks.cronetEngineContextHook
|
||||
import app.revanced.patches.shared.extension.hooks.firebaseInitProviderContextHook
|
||||
import app.revanced.patches.shared.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.utils.extension.hooks.applicationInitHook
|
||||
import app.revanced.patches.youtube.utils.extension.hooks.mainActivityBaseContextHook
|
||||
import app.revanced.patches.youtube.utils.extension.hooks.urlActivityBaseContextHook
|
||||
|
||||
// TODO: Move this to a "Hook.kt" file. Same for other extension hook patches.
|
||||
val sharedExtensionPatch = sharedExtensionPatch(
|
||||
applicationInitHook,
|
||||
cronetEngineContextHook,
|
||||
firebaseInitProviderContextHook,
|
||||
mainActivityBaseContextHook,
|
||||
urlActivityBaseContextHook,
|
||||
)
|
||||
|
@ -1,36 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.extension.hooks
|
||||
|
||||
import app.revanced.patches.shared.extension.extensionHook
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
private var attachBaseContextIndex = -1
|
||||
|
||||
internal val mainActivityBaseContextHook = extensionHook(
|
||||
insertIndexResolver = { method ->
|
||||
attachBaseContextIndex = method.indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.name == "attachBaseContext"
|
||||
}
|
||||
|
||||
attachBaseContextIndex + 1
|
||||
},
|
||||
contextRegisterResolver = { method ->
|
||||
val overrideInstruction =
|
||||
method.implementation!!.instructions.elementAt(attachBaseContextIndex)
|
||||
as FiveRegisterInstruction
|
||||
"v${overrideInstruction.registerD}"
|
||||
},
|
||||
) {
|
||||
returns("V")
|
||||
parameters("Landroid/content/Context;")
|
||||
custom { method, classDef ->
|
||||
method.name == "attachBaseContext" &&
|
||||
(
|
||||
classDef.endsWith("/MainActivity;") ||
|
||||
// Old versions of YouTube called this class "WatchWhileActivity" instead.
|
||||
classDef.endsWith("/WatchWhileActivity;")
|
||||
)
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.extension.hooks
|
||||
|
||||
import app.revanced.patches.shared.extension.extensionHook
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
private var attachBaseContextIndex = -1
|
||||
|
||||
internal val urlActivityBaseContextHook = extensionHook(
|
||||
insertIndexResolver = { method ->
|
||||
attachBaseContextIndex = method.indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.name == "attachBaseContext"
|
||||
}
|
||||
|
||||
attachBaseContextIndex + 1
|
||||
},
|
||||
contextRegisterResolver = { method ->
|
||||
val overrideInstruction =
|
||||
method.implementation!!.instructions.elementAt(attachBaseContextIndex)
|
||||
as FiveRegisterInstruction
|
||||
"v${overrideInstruction.registerD}"
|
||||
},
|
||||
) {
|
||||
returns("V")
|
||||
parameters("Landroid/content/Context;")
|
||||
custom { method, classDef ->
|
||||
classDef.endsWith("/Shell_UrlActivity;") &&
|
||||
method.name == "attachBaseContext"
|
||||
}
|
||||
}
|
@ -116,7 +116,7 @@ val cairoFragmentPatch = resourcePatch(
|
||||
?.let { node ->
|
||||
node.insertNode("Preference", node) {
|
||||
for (index in 0 until node.attributes.length) {
|
||||
with (node.attributes.item(index)) {
|
||||
with(node.attributes.item(index)) {
|
||||
setAttribute(nodeName, nodeValue)
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package app.revanced.patches.youtube.utils.fix.cairo
|
||||
|
||||
import app.revanced.patches.youtube.utils.resourceid.settingsFragment
|
||||
import app.revanced.patches.youtube.utils.resourceid.settingsFragmentCairo
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patches.youtube.utils.resourceid.settingsFragment
|
||||
import app.revanced.patches.youtube.utils.resourceid.settingsFragmentCairo
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
/**
|
||||
|
@ -1,12 +1,9 @@
|
||||
package app.revanced.patches.youtube.utils.fix.splash
|
||||
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.YOUTUBE_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.utils.playservice.is_19_32_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.restoreOldSplashAnimationIncluded
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.youtubePackageName
|
||||
import app.revanced.util.findElementByAttributeValueOrThrow
|
||||
import org.w3c.dom.Element
|
||||
|
||||
/**
|
||||
@ -28,18 +25,6 @@ val darkModeSplashScreenPatch = resourcePatch(
|
||||
return@finalize
|
||||
}
|
||||
|
||||
// GmsCore support included
|
||||
if (youtubePackageName != YOUTUBE_PACKAGE_NAME) {
|
||||
document("AndroidManifest.xml").use { document ->
|
||||
val mainActivityElement = document.childNodes.findElementByAttributeValueOrThrow(
|
||||
"android:name",
|
||||
"com.google.android.apps.youtube.app.watchwhile.MainActivity",
|
||||
)
|
||||
|
||||
mainActivityElement.setAttribute("android:launchMode", "singleTask")
|
||||
}
|
||||
}
|
||||
|
||||
if (restoreOldSplashAnimationIncluded) {
|
||||
document("res/values-night/styles.xml").use { document ->
|
||||
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
|
||||
|
@ -153,3 +153,18 @@ internal val onesieEncryptionAlternativeFeatureFlagFingerprint = legacyFingerpri
|
||||
name = "onesieEncryptionAlternativeFeatureFlagFingerprint",
|
||||
literals = listOf(ONESIE_ENCRYPTION_ALTERNATIVE_FEATURE_FLAG),
|
||||
)
|
||||
|
||||
// Feature flag that enables different code for parsing and starting video playback,
|
||||
// but it's exact purpose is not known. If this flag is enabled while stream spoofing
|
||||
// then videos will never start playback and load forever.
|
||||
// Flag does not seem to affect playback if spoofing is off.
|
||||
// YouTube 19.50 ~
|
||||
internal const val PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG = 45665455L
|
||||
|
||||
internal val playbackStartDescriptorFeatureFlagFingerprint = legacyFingerprint(
|
||||
name = "playbackStartDescriptorFeatureFlagFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = emptyList(),
|
||||
returnType = ("Z"),
|
||||
literals = listOf(PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG)
|
||||
)
|
||||
|
@ -20,6 +20,7 @@ import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PAC
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.YOUTUBE_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_STREAMING_DATA
|
||||
import app.revanced.patches.youtube.utils.playservice.is_19_34_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.is_19_50_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.is_20_10_or_greater
|
||||
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.utils.request.buildRequestPatch
|
||||
@ -345,11 +346,18 @@ val spoofStreamingDataPatch = bytecodePatch(
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->skipResponseEncryption(Z)Z"
|
||||
)
|
||||
|
||||
if (is_20_10_or_greater) {
|
||||
onesieEncryptionAlternativeFeatureFlagFingerprint.injectLiteralInstructionBooleanCall(
|
||||
ONESIE_ENCRYPTION_ALTERNATIVE_FEATURE_FLAG,
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->skipResponseEncryption(Z)Z"
|
||||
if (is_19_50_or_greater) {
|
||||
playbackStartDescriptorFeatureFlagFingerprint.injectLiteralInstructionBooleanCall(
|
||||
PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG,
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->usePlaybackStartFeatureFlag(Z)Z"
|
||||
)
|
||||
|
||||
if (is_20_10_or_greater) {
|
||||
onesieEncryptionAlternativeFeatureFlagFingerprint.injectLiteralInstructionBooleanCall(
|
||||
ONESIE_ENCRYPTION_ALTERNATIVE_FEATURE_FLAG,
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->skipResponseEncryption(Z)Z"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
settingArray += "SETTINGS: SKIP_RESPONSE_ENCRYPTION"
|
||||
|
@ -22,7 +22,6 @@ 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
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
import kotlin.collections.mutableListOf
|
||||
|
||||
private const val EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR =
|
||||
"$EXTENSION_PATH/utils/VideoUtils;"
|
||||
@ -59,7 +58,10 @@ val fullscreenButtonHookPatch = bytecodePatch(
|
||||
getReference<MethodReference>()?.name == "addListener"
|
||||
}
|
||||
val animatorListenerAdapterClass = getInstruction<ReferenceInstruction>(
|
||||
indexOfFirstInstructionReversedOrThrow(addListenerIndex, Opcode.NEW_INSTANCE)
|
||||
indexOfFirstInstructionReversedOrThrow(
|
||||
addListenerIndex,
|
||||
Opcode.NEW_INSTANCE
|
||||
)
|
||||
).reference.toString()
|
||||
return Pair(
|
||||
findMethodOrThrow(animatorListenerAdapterClass) { parameters.isEmpty() },
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user