Compare commits

...

65 Commits

Author SHA1 Message Date
inotia00
42cc8201f5 Merge branch 'dev' into revanced-extended 2025-04-03 10:23:04 +09:00
inotia00
15d0fafcf8 bump 5.6.2 2025-04-03 10:20:34 +09:00
inotia00
db10e410d3 feat(Translations): Update translation 2025-04-03 10:20:10 +09:00
inotia00
8f23d76f37 feat(Reddit): Change the latest supported version from 2025.12.0 to 2025.12.1 2025-04-03 10:19:36 +09:00
inotia00
7168e49121 fix(Reddit - Disable screenshot popup): No exception is thrown if no matching pattern is found 2025-04-03 10:18:02 +09:00
inotia00
2b52380294 bump 5.6.2-dev.2 2025-04-01 19:12:45 +09:00
inotia00
0cc693961a feat(Translations): Update translation 2025-04-01 19:11:59 +09:00
inotia00
bbf863c630 fix(YouTube - Description components): Expand video descriptions requires longer wait time 2025-04-01 19:11:45 +09:00
inotia00
783e366242 fix(YouTube - Hook download actions, Overlay buttons): Auth key is set before the Context is initialized 2025-04-01 19:09:19 +09:00
inotia00
4a19a960c5 fix(YouTube - Settings): The default value for the patch option is incorrect 2025-04-01 19:06:28 +09:00
inotia00
e55fd4eb74 chore: Remove debug instruction 2025-04-01 19:05:20 +09:00
inotia00
64af7fd8b6 fix(YouTube - Video playback): Overridden to default playback speed in onResume callback https://github.com/inotia00/ReVanced_Extended/issues/2896 2025-04-01 19:02:55 +09:00
inotia00
457dbfec6c feat(YouTube - Disable resuming Shorts on startup): Match with ReVanced 2025-04-01 19:01:18 +09:00
inotia00
22b98336d5 fix(YouTube - Custom branding icon): Splash animation background color is always white https://github.com/inotia00/ReVanced_Extended/issues/2892 2025-04-01 19:00:18 +09:00
inotia00
7e4a71b385 feat(GmsCore support): Add patch option Patch all manifest components 2025-04-01 18:59:47 +09:00
inotia00
bb5964ce98 fix(Reddit - Disable screenshot popup): Screenshot popup not being completely removed https://github.com/inotia00/ReVanced_Extended/issues/1810 2025-04-01 18:51:58 +09:00
inotia00
15db05c636 fix(Reddit - Remove subreddit dialog): Can't open post from search results 2025-04-01 18:49:47 +09:00
KobeW50
3f9edca15d
feat(YouTube - Settings): Place RVX settings at top of settings by default (#147)
Co-authored-by: Kobe <kobew5050@gmail.com>
2025-04-01 08:50:25 +09:00
inotia00
1eca8c854c bump 5.6.2-dev.1 2025-03-31 21:21:14 +09:00
inotia00
692b4f2c53 feat(Translations): Update translation 2025-03-31 21:19:33 +09:00
inotia00
5162dccecc feat(YouTube - Settings): Add App name, App version and Patched date to patch information 2025-03-31 21:16:29 +09:00
inotia00
56b713b0db fix build error 2025-03-31 21:13:36 +09:00
inotia00
f761bc45ec feat(YouTube - Hook download actions, Overlay buttons): Add Add to queue and reload video and Remove from queue and reload video settings, fixed a bug where the queue was unavailable under certain conditions 2025-03-31 21:12:59 +09:00
inotia00
bccd6dc5df fix(YouTube - Swipe controls): Patch fails on YouTube 19.43.41 when Player components patch is included 2025-03-31 21:09:11 +09:00
inotia00
09a8eb7114 fix(YouTube - Custom branding icon): Splash animation background color is always white https://github.com/inotia00/ReVanced_Extended/issues/2892 2025-03-31 21:05:06 +09:00
inotia00
b439ef3ee7 fix(Reddit - Remove subreddit dialog): New type of NSFW dialog not hidden https://github.com/inotia00/ReVanced_Extended/issues/2895 2025-03-31 21:01:10 +09:00
inotia00
7f85e802c2 fix(Reddit - Disable screenshot popup): Patch does not work in certain environments https://github.com/inotia00/ReVanced_Extended/issues/2891 2025-03-31 20:54:10 +09:00
inotia00
bb1498df76 Merge branch 'dev' into revanced-extended 2025-03-30 19:49:58 +09:00
inotia00
1c58c6a36e feat(YouTube): Remove support version 20.03.43
Not reflected in branch due to issue with source comparison tool
2025-03-30 19:49:43 +09:00
inotia00
26bf2f4b82 Merge branch 'dev' into revanced-extended 2025-03-30 19:33:08 +09:00
inotia00
a037b25c13 bump 5.6.1 2025-03-30 19:32:31 +09:00
inotia00
b72bb71e30 feat(YouTube): Remove support version 20.03.43 2025-03-30 19:27:16 +09:00
inotia00
28ff781786 chore: Lint code 2025-03-30 19:13:14 +09:00
inotia00
f249e88ce8 bump 5.6.1-dev.5 2025-03-30 18:24:50 +09:00
inotia00
036e3dad11 feat(Translations): Update translation 2025-03-30 18:24:10 +09:00
inotia00
9342e1f7d2 fix(Reddit - Disable screenshot popup): Restart dialog is missing 2025-03-30 18:23:49 +09:00
inotia00
f19dd54026 fix(YouTube - Hook download actions, Overlay buttons): Queue manager fails to identify brand account 2025-03-30 18:22:29 +09:00
inotia00
edccd61e6b fix(YouTube - Custom branding icon): Remove unnecessary hooks 2025-03-30 18:20:02 +09:00
inotia00
35e6c26823 fix(Extensions): Remove unnecessary Context hooks 2025-03-30 18:18:46 +09:00
inotia00
88d59d05b9 fix(YouTube - Shorts components): Custom actions do not override Shorts flyout menu in YouTube 19.05.36 2025-03-30 18:16:34 +09:00
inotia00
aac38dc8af fix(YouTube - Settings): RVX language no longer changes the YouTube app language 2025-03-30 18:12:07 +09:00
inotia00
1dd7eda606 chore(YouTube): Reflecting the changes in ReVanced 2025-03-30 18:08:49 +09:00
MondayNitro
05195caa5a
feat(YouTube - Custom branding icon): Update old splash animation background color (#146)
* Update revanced_extended_settings_key_icon.xml

* Update $avd_anim__0.xml

* Update strings.xml
2025-03-30 18:05:53 +09:00
inotia00
82158deaf2 chore(YouTube - Fullscreen components): Remove warning for Keep landscape mode setting 2025-03-30 18:01:01 +09:00
inotia00
21be41c2a9 fix(Reddit - Disable screenshot popup): Screenshot popup not being completely removed https://github.com/inotia00/ReVanced_Extended/issues/1810 2025-03-30 17:56:59 +09:00
inotia00
ae69aca189 Merge branch 'dev' into revanced-extended 2025-03-16 13:07:53 +09:00
inotia00
8d84304737 Merge branch 'dev' into revanced-extended 2025-03-07 09:50:18 +09:00
inotia00
1926becdfc Merge branch 'dev' into revanced-extended 2025-02-13 11:21:24 +09:00
inotia00
9146d9283b Merge branch 'dev' into revanced-extended 2025-01-22 17:23:55 +09:00
inotia00
8db38af44f Merge branch 'dev' into revanced-extended 2025-01-16 12:43:34 +09:00
inotia00
1aaf03df50 Merge branch 'dev' into revanced-extended 2025-01-07 14:16:07 +09:00
inotia00
ee0a7b5443 Merge branch 'dev' into revanced-extended 2024-12-22 19:46:21 +09:00
inotia00
a9408b0f51 Merge branch 'dev' into revanced-extended 2024-12-22 18:25:46 +09:00
inotia00
03c0826fab Merge branch 'dev' into revanced-extended 2024-12-22 16:54:00 +09:00
inotia00
0384907324 Merge branch 'dev' into revanced-extended 2024-12-21 15:10:47 +09:00
inotia00
2b0f12cb9e Merge branch 'dev' into revanced-extended 2024-12-09 22:34:28 +09:00
inotia00
5088daa434 Merge branch 'dev' into revanced-extended 2024-12-09 01:40:58 +09:00
inotia00
b5fe136898 Merge branch 'dev' into revanced-extended 2024-12-08 18:48:19 +09:00
inotia00
807494f8a7 Merge branch 'dev' into revanced-extended 2024-11-08 22:42:17 +09:00
inotia00
d8ae741853 Merge branch 'dev' into revanced-extended 2024-10-21 20:29:13 +09:00
inotia00
934c33a0f9 Merge branch 'dev' into revanced-extended 2024-10-13 02:34:39 +09:00
inotia00
b725e54aee Merge branch 'dev' into revanced-extended 2024-09-29 00:26:24 +09:00
inotia00
c57de97576 Merge branch 'dev' into revanced-extended 2024-09-07 09:32:24 +09:00
inotia00
2e3274152b Merge branch 'dev' into revanced-extended 2024-08-12 12:02:15 +09:00
KobeW50
6499b7d3f0
ci: workflow to ping Discord users when patches are released (#72)
* init: Workflow to notify discord users of releases

* Rename workflow

* chore (Background playback): Shorten description

* Revert "chore (Background playback): Shorten description"

This reverts commit 10661b870f0c9c670c5d522f9b2ca7cc82d32772.

* Change message contents
2024-08-11 13:11:10 +09:00
138 changed files with 2481 additions and 1341 deletions

165
README.md
View File

@ -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": []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -377,6 +377,7 @@ public class ReVancedPreferenceFragment extends PreferenceFragment {
@Override
public void onDestroy() {
mSharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
Utils.resetLocalizedContext();
super.onDestroy();
}

View File

@ -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
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.
*/

View File

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

View File

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

View File

@ -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": []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -62,7 +62,7 @@ fun baseAdsPatch(
)
}
val getAdvertisingIdMethod = with (advertisingIdFingerprint.methodOrThrow()) {
val getAdvertisingIdMethod = with(advertisingIdFingerprint.methodOrThrow()) {
val getAdvertisingIdIndex = indexOfGetAdvertisingIdInstruction(this)
getWalkerMethod(getAdvertisingIdIndex)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
)
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
/**

View File

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

View File

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

View File

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

View File

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