diff --git a/README.md b/README.md index fce9448e00..9d71deced4 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ ReVanced Extended Patches. | `Hide layout components` | Adds options to hide general layout components. | 18.29.38 ~ 19.16.39 | | `Hide player buttons` | Adds options to hide buttons in the video player. | 18.29.38 ~ 19.16.39 | | `Hide player flyout menu` | Adds options to hide player flyout menu components. | 18.29.38 ~ 19.16.39 | +| `Hide shortcuts` | Remove, at compile time, the app shortcuts that appears when app icon is long pressed. | 18.29.38 ~ 19.16.39 | +| `Hook YouTube Music actions` | Adds support for opening music in RVX Music using the in-app YouTube Music button. | 18.29.38 ~ 19.16.39 | | `Hook download actions` | Adds support to download videos with an external downloader app using the in-app download button. | 18.29.38 ~ 19.16.39 | | `Layout switch` | Adds an option to spoof the dpi in order to use a tablet or phone layout. | 18.29.38 ~ 19.16.39 | | `MaterialYou` | Applies the MaterialYou theme for Android 12+ devices. | 18.29.38 ~ 19.16.39 | @@ -66,7 +68,7 @@ ReVanced Extended Patches. | `Toolbar components` | Adds options to hide or change components located on the toolbar, such as toolbar buttons, search bar, and header. | 18.29.38 ~ 19.16.39 | | `Translations for YouTube` | Add translations or remove string resources. | 18.29.38 ~ 19.16.39 | | `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 18.29.38 ~ 19.16.39 | -| `Visual preferences icons` | Adds icons to specific preferences in the settings. | 18.29.38 ~ 19.16.39 | +| `Visual preferences icons for YouTube` | Adds icons to specific preferences in the settings. | 18.29.38 ~ 19.16.39 | | `Watch history` | Adds an option to change the domain of the watch history or check its status. | 18.29.38 ~ 19.16.39 | @@ -75,41 +77,42 @@ ReVanced Extended Patches. | 💊 Patch | 📜 Description | 🏹 Target Version | |:--------:|:--------------:|:-----------------:| -| `Amoled` | Applies a pure black theme to some components. | 6.29.58 ~ 7.17.51 | -| `Bitrate default value` | Sets the audio quality to 'Always High' when you first install the app. | 6.29.58 ~ 7.17.51 | -| `Bypass image region restrictions` | Adds an option to use a different host for static images, so that images blocked in some countries can be received. | 6.29.58 ~ 7.17.51 | -| `Certificate spoof` | Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate. | 6.29.58 ~ 7.17.51 | -| `Change share sheet` | Add option to change from in-app share sheet to system share sheet. | 6.29.58 ~ 7.17.51 | -| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 6.29.58 ~ 7.17.51 | -| `Custom branding icon for YouTube Music` | Changes the YouTube Music app icon to the icon specified in options.json. | 6.29.58 ~ 7.17.51 | -| `Custom branding name for YouTube Music` | Renames the YouTube Music app to the name specified in options.json. | 6.29.58 ~ 7.17.51 | -| `Custom header for YouTube Music` | Applies a custom header in the top left corner within the app. | 6.29.58 ~ 7.17.51 | -| `Disable Cairo splash animation` | Adds an option to disable Cairo splash animation. | 7.06.54 ~ 7.17.51 | -| `Disable auto captions` | Adds an option to disable captions from being automatically enabled. | 6.29.58 ~ 7.17.51 | -| `Disable dislike redirection` | Adds an option to disable redirection to the next track when clicking the Dislike button. | 6.29.58 ~ 7.17.51 | -| `Enable OPUS codec` | Adds an options to enable the OPUS audio codec if the player response includes. | 6.29.58 ~ 7.17.51 | -| `Enable debug logging` | Adds an option to enable debug logging. | 6.29.58 ~ 7.17.51 | -| `Enable landscape mode` | Adds an option to enable landscape mode when rotating the screen on phones. | 6.29.58 ~ 7.17.51 | -| `Flyout menu components` | Adds options to hide or change flyout menu components. | 6.29.58 ~ 7.17.51 | -| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 6.29.58 ~ 7.17.51 | -| `Hide account components` | Adds options to hide components related to the account menu. | 6.29.58 ~ 7.17.51 | -| `Hide action bar components` | Adds options to hide action bar components and replace the offline download button with an external download button. | 6.29.58 ~ 7.17.51 | -| `Hide ads` | Adds options to hide ads. | 6.29.58 ~ 7.17.51 | -| `Hide layout components` | Adds options to hide general layout components. | 6.29.58 ~ 7.17.51 | -| `Hide overlay filter` | Removes, at compile time, the dark overlay that appears when player flyout menus are open. | 6.29.58 ~ 7.17.51 | -| `Hide player overlay filter` | Removes, at compile time, the dark overlay that appears when single-tapping in the player. | 6.29.58 ~ 7.17.51 | -| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 6.29.58 ~ 7.17.51 | -| `Player components` | Adds options to hide or change components related to the player. | 6.29.58 ~ 7.17.51 | -| `Remove background playback restrictions` | Removes restrictions on background playback, including for kids videos. | 6.29.58 ~ 7.17.51 | -| `Remove viewer discretion dialog` | Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction. | 6.29.58 ~ 7.17.51 | -| `Restore old style library shelf` | Adds an option to return the Library tab to the old style. | 6.29.58 ~ 7.17.51 | -| `Return YouTube Dislike` | Adds an option to show the dislike count of songs using the Return YouTube Dislike API. | 6.29.58 ~ 7.17.51 | -| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | 6.29.58 ~ 7.17.51 | -| `Settings for YouTube Music` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 6.29.58 ~ 7.17.51 | -| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections. | 6.29.58 ~ 7.17.51 | -| `Spoof app version` | Adds options to spoof the YouTube Music client version. This can remove the radio mode restriction in Canadian regions or disable real-time lyrics. | 6.29.58 ~ 7.17.51 | -| `Translations for YouTube Music` | Add translations or remove string resources. | 6.29.58 ~ 7.17.51 | -| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 6.29.58 ~ 7.17.51 | +| `Amoled` | Applies a pure black theme to some components. | 6.20.51 ~ 7.16.53 | +| `Bitrate default value` | Sets the audio quality to 'Always High' when you first install the app. | 6.20.51 ~ 7.16.53 | +| `Bypass image region restrictions` | Adds an option to use a different host for static images, so that images blocked in some countries can be received. | 6.20.51 ~ 7.16.53 | +| `Certificate spoof` | Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate. | 6.20.51 ~ 7.16.53 | +| `Change share sheet` | Add option to change from in-app share sheet to system share sheet. | 6.20.51 ~ 7.16.53 | +| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 6.20.51 ~ 7.16.53 | +| `Custom branding icon for YouTube Music` | Changes the YouTube Music app icon to the icon specified in options.json. | 6.20.51 ~ 7.16.53 | +| `Custom branding name for YouTube Music` | Renames the YouTube Music app to the name specified in options.json. | 6.20.51 ~ 7.16.53 | +| `Custom header for YouTube Music` | Applies a custom header in the top left corner within the app. | 6.20.51 ~ 7.16.53 | +| `Disable Cairo splash animation` | Adds an option to disable Cairo splash animation. | 7.06.54 ~ 7.16.53 | +| `Disable auto captions` | Adds an option to disable captions from being automatically enabled. | 6.20.51 ~ 7.16.53 | +| `Disable dislike redirection` | Adds an option to disable redirection to the next track when clicking the Dislike button. | 6.20.51 ~ 7.16.53 | +| `Enable OPUS codec` | Adds an options to enable the OPUS audio codec if the player response includes. | 6.20.51 ~ 7.16.53 | +| `Enable debug logging` | Adds an option to enable debug logging. | 6.20.51 ~ 7.16.53 | +| `Enable landscape mode` | Adds an option to enable landscape mode when rotating the screen on phones. | 6.20.51 ~ 7.16.53 | +| `Flyout menu components` | Adds options to hide or change flyout menu components. | 6.20.51 ~ 7.16.53 | +| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 6.20.51 ~ 7.16.53 | +| `Hide account components` | Adds options to hide components related to the account menu. | 6.20.51 ~ 7.16.53 | +| `Hide action bar components` | Adds options to hide action bar components and replace the offline download button with an external download button. | 6.20.51 ~ 7.16.53 | +| `Hide ads` | Adds options to hide ads. | 6.20.51 ~ 7.16.53 | +| `Hide layout components` | Adds options to hide general layout components. | 6.20.51 ~ 7.16.53 | +| `Hide overlay filter` | Removes, at compile time, the dark overlay that appears when player flyout menus are open. | 6.20.51 ~ 7.16.53 | +| `Hide player overlay filter` | Removes, at compile time, the dark overlay that appears when single-tapping in the player. | 6.20.51 ~ 7.16.53 | +| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 6.20.51 ~ 7.16.53 | +| `Player components` | Adds options to hide or change components related to the player. | 6.20.51 ~ 7.16.53 | +| `Remove background playback restrictions` | Removes restrictions on background playback, including for kids videos. | 6.20.51 ~ 7.16.53 | +| `Remove viewer discretion dialog` | Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction. | 6.20.51 ~ 7.16.53 | +| `Restore old style library shelf` | Adds an option to return the Library tab to the old style. | 6.20.51 ~ 7.16.53 | +| `Return YouTube Dislike` | Adds an option to show the dislike count of songs using the Return YouTube Dislike API. | 6.20.51 ~ 7.16.53 | +| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | 6.20.51 ~ 7.16.53 | +| `Settings for YouTube Music` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 6.20.51 ~ 7.16.53 | +| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections. | 6.20.51 ~ 7.16.53 | +| `Spoof app version` | Adds options to spoof the YouTube Music client version. This can remove the radio mode restriction in Canadian regions or disable real-time lyrics. | 6.20.51 ~ 7.16.53 | +| `Translations for YouTube Music` | Add translations or remove string resources. | 6.20.51 ~ 7.16.53 | +| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 6.20.51 ~ 7.16.53 | +| `Visual preferences icons for YouTube Music` | Adds icons to specific preferences in the settings. | 6.20.51 ~ 7.16.53 | ### [📦 `com.reddit.frontpage`](https://play.google.com/store/apps/details?id=com.reddit.frontpage) @@ -169,12 +172,11 @@ Example: { "name": "com.google.android.apps.youtube.music", "versions": [ - "6.29.58", - "6.33.52", + "6.20.51", + "6.29.59", "6.42.55", "6.51.53", - "7.16.53", - "7.17.51" + "7.16.53" ] } ], diff --git a/gradle.properties b/gradle.properties index 45ef12a923..2d36c75824 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official -version = 4.13.1 +version = 4.14.1 diff --git a/patches.json b/patches.json index 9f570ddc01..0f78681548 100644 --- a/patches.json +++ b/patches.json @@ -1 +1 @@ -[{"name":"Alternative thumbnails","description":"Adds options to replace video thumbnails using the DeArrow API or image captures from the video.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Ambient mode control","description":"Adds options to disable Ambient mode and to bypass Ambient mode restrictions.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Amoled","description":"Applies a pure black theme to some components.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Bitrate default value","description":"Sets the audio quality to \u0027Always High\u0027 when you first install the app.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Bypass image region restrictions","description":"Adds an option to use a different host for static images, so that images blocked in some countries can be received.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Bypass image region restrictions","description":"Adds an option to use a different host for static images, so that images blocked in some countries can be received.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Certificate spoof","description":"Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change package name","description":"Changes the package name for Reddit to the name specified in options.json.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":false,"requiresIntegrations":false,"options":[{"key":"PackageNameReddit","default":"com.reddit.frontpage","values":{"Clone":"com.reddit.frontpage.revanced","Default":"com.reddit.frontpage.rvx","Original":"com.reddit.frontpage"},"title":"Package name of Reddit","description":"The name of the package to rename the app to.","required":true}]},{"name":"Change player flyout menu toggles","description":"Adds an option to use text toggles instead of switch toggles within the additional settings menu.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change share sheet","description":"Add option to change from in-app share sheet to system share sheet.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change share sheet","description":"Add option to change from in-app share sheet to system share sheet.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change start page","description":"Adds an option to set which page the app opens in instead of the homepage.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change start page","description":"Adds an option to set which page the app opens in instead of the homepage.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change version code","description":"Changes the version code of the app to the value specified in options.json. Except when mounting, this can prevent app stores from updating the app and allow the app to be installed over an existing installation that has a higher version code. By default, the highest version code is set.","compatiblePackages":null,"use":false,"requiresIntegrations":false,"options":[{"key":"ChangeVersionCode","default":false,"values":null,"title":"Change version code","description":"Changes the version code of the app.","required":true},{"key":"VersionCode","default":"2147483647","values":null,"title":"Version code","description":"The version code to use. (1 ~ 2147483647)","required":true}]},{"name":"Custom Shorts action buttons","description":"Changes, at compile time, the icon of the action buttons of the Shorts player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"IconType","default":"youtubeoutline","values":{"Outline":"outline","OutlineCircle":"outlinecircle","Round":"round","YoutubeOutline":"youtubeoutline","YouTube":"youtube"},"title":"Shorts icon style ","description":"The style of the icons for the action buttons in the Shorts player.","required":true}]},{"name":"Custom branding icon for YouTube","description":"Changes the YouTube app icon to the icon specified in options.json.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"AppIcon","default":"revancify_blue","values":{"AFN Blue":"afn_blue","AFN Red":"afn_red","MMT":"mmt","Revancify Blue":"revancify_blue","Revancify Red":"revancify_red","YouTube":"youtube"},"title":"App icon","description":"The icon to apply to the app.\n\nIf a path to a folder is provided, the folder must contain the following folders:\n\n- mipmap-xxxhdpi\n- mipmap-xxhdpi\n- mipmap-xhdpi\n- mipmap-hdpi\n- mipmap-mdpi\n\nEach of these folders must contain the following files:\n\n- adaptiveproduct_youtube_background_color_108.png\n- adaptiveproduct_youtube_foreground_color_108.png\n- ic_launcher.png\n- ic_launcher_round.png","required":true},{"key":"ChangeSplashIcon","default":true,"values":null,"title":"Change splash icons","description":"Apply the custom branding icon to the splash screen.","required":true},{"key":"RestoreOldSplashAnimation","default":true,"values":null,"title":"Restore old splash animation","description":"Restore the old style splash animation.","required":true}]},{"name":"Custom branding icon for YouTube Music","description":"Changes the YouTube Music app icon to the icon specified in options.json.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"AppIcon","default":"revancify_blue","values":{"AFN Blue":"afn_blue","AFN Red":"afn_red","MMT":"mmt","Revancify Blue":"revancify_blue","Revancify Red":"revancify_red","YouTube Music":"youtube_music"},"title":"App icon","description":"The icon to apply to the app.\n\nIf a path to a folder is provided, the folder must contain the following folders:\n\n- mipmap-xxxhdpi\n- mipmap-xxhdpi\n- mipmap-xhdpi\n- mipmap-hdpi\n- mipmap-mdpi\n\nEach of these folders must contain the following files:\n\n- adaptiveproduct_youtube_music_background_color_108.png\n- adaptiveproduct_youtube_music_foreground_color_108.png\n- ic_launcher_release.png","required":true},{"key":"ChangeSplashIcon","default":true,"values":null,"title":"Change splash icons","description":"Apply the custom branding icon to the splash screen.","required":true},{"key":"RestoreOldSplashIcon","default":false,"values":null,"title":"Restore old splash icon","description":"Restore the old style splash icon.\n\nIf you enable both the old style splash icon and the Cairo splash animation,\n\nOld style splash icon will appear first and then the Cairo splash animation will start.","required":true}]},{"name":"Custom branding name for Reddit","description":"Renames the Reddit app to the name specified in options.json.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":false,"requiresIntegrations":false,"options":[{"key":"AppName","default":"Reddit","values":{"Default":"RVX Reddit","Original":"Reddit"},"title":"App name","description":"The name of the app.","required":true}]},{"name":"Custom branding name for YouTube","description":"Renames the YouTube app to the name specified in options.json.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"AppName","default":"RVX","values":{"ReVanced Extended":"ReVanced Extended","RVX":"RVX","YouTube RVX":"YouTube RVX","YouTube":"YouTube"},"title":"App name","description":"The name of the app.","required":true}]},{"name":"Custom branding name for YouTube Music","description":"Renames the YouTube Music app to the name specified in options.json.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"AppNameNotification","default":"RVX Music","values":{"ReVanced Extended Music":"ReVanced Extended Music","RVX Music":"RVX Music","YouTube Music":"YouTube Music","YT Music":"YT Music"},"title":"App name in notification panel","description":"The name of the app as it appears in the notification panel.","required":true},{"key":"AppNameLauncher","default":"RVX Music","values":{"ReVanced Extended Music":"ReVanced Extended Music","RVX Music":"RVX Music","YouTube Music":"YouTube Music","YT Music":"YT Music"},"title":"App name in launcher","description":"The name of the app as it appears in the launcher.","required":true}]},{"name":"Custom double tap length","description":"Adds Double-tap to seek values that are specified in options.json.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"DoubleTapLengthArrays","default":"3, 5, 10, 15, 20, 30, 60, 120, 180","values":null,"title":"Double-tap to seek values","description":"A list of custom Double-tap to seek lengths to be added, separated by commas.","required":true}]},{"name":"Custom header for YouTube","description":"Applies a custom header in the top left corner within the app.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":false,"requiresIntegrations":false,"options":[{"key":"CustomHeader","default":"custom_branding_icon","values":{"Custom branding icon":"custom_branding_icon"},"title":"Custom header","description":"The header to apply to the app.\n\nPatch option \u0027Custom branding icon\u0027 applies only when:\n\n1. Patch \u0027Custom branding icon for YouTube\u0027 is included.\n2. Patch option for \u0027Custom branding icon for YouTube\u0027 is selected from the preset.\n\nIf a path to a folder is provided, the folder must contain one or more of the following folders, depending on the DPI of the device:\n\n- drawable-xxxhdpi\n- drawable-xxhdpi\n- drawable-xhdpi\n- drawable-hdpi\n- drawable-mdpi\n\nEach of the folders must contain all of the following files:\n\n[Generic header]\n\n- yt_wordmark_header_light.png\n- yt_wordmark_header_dark.png\n\nThe image dimensions must be as follows:\n\n- drawable-xxxhdpi: 488px x 192px\n- drawable-xxhdpi: 366px x 144px\n- drawable-xhdpi: 244px x 96px\n- drawable-hdpi: 184px x 72px\n- drawable-mdpi: 122px x 48px\n\n[Premium header]\n\n- yt_premium_wordmark_header_light.png\n- yt_premium_wordmark_header_dark.png\n\nThe image dimensions must be as follows:\n- drawable-xxxhdpi: 516px x 192px\n- drawable-xxhdpi: 387px x 144px\n- drawable-xhdpi: 258px x 96px\n- drawable-hdpi: 194px x 72px\n- drawable-mdpi: 129px x 48px","required":true}]},{"name":"Custom header for YouTube Music","description":"Applies a custom header in the top left corner within the app.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":false,"requiresIntegrations":false,"options":[{"key":"CustomHeader","default":"custom_branding_icon","values":{"Custom branding icon":"custom_branding_icon"},"title":"Custom header","description":"The header to apply to the app.\n\nPatch option \u0027Custom branding icon\u0027 applies only when:\n\n1. Patch \u0027Custom branding icon for YouTube Music\u0027 is included.\n2. Patch option for \u0027Custom branding icon for YouTube Music\u0027 is selected from the preset.\n\nIf a path to a folder is provided, the folder must contain one or more of the following folders, depending on the DPI of the device:\n\n- drawable-xxxhdpi\n- drawable-xxhdpi\n- drawable-xhdpi\n- drawable-hdpi\n- drawable-mdpi\n\nEach of the folders must contain all of the following files:\n\n- action_bar_logo.png\n- logo_music.png\n- ytm_logo.png\n\nThe image \u0027action_bar_logo.png\u0027 dimensions must be as follows:\n\n- drawable-xxxhdpi: 320px x 96px\n- drawable-xxhdpi: 240px x 72px\n- drawable-xhdpi: 160px x 48px\n- drawable-hdpi: 121px x 36px\n- drawable-mdpi: 80px x 24px\n\nThe image \u0027logo_music.png\u0027 dimensions must be as follows:\n\n- drawable-xxxhdpi: 576px x 200px\n- drawable-xxhdpi: 432px x 150px\n- drawable-xhdpi: 288px x 100px\n- drawable-hdpi: 217px x 76px\n- drawable-mdpi: 144px x 50px\n\nThe image \u0027ytm_logo.png\u0027 dimensions must be as follows:\n\n- drawable-xxxhdpi: 412px x 144px\n- drawable-xxhdpi: 309px x 108px\n- drawable-xhdpi: 206px x 72px\n- drawable-hdpi: 155px x 54px\n- drawable-mdpi: 103px x 36px","required":true}]},{"name":"Description components","description":"Adds options to hide and disable description components.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable Cairo splash animation","description":"Adds an option to disable Cairo splash animation.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["7.06.54","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable QUIC protocol","description":"Adds an option to disable CronetEngine\u0027s QUIC protocol.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable auto audio tracks","description":"Adds an option to disable audio tracks from being automatically enabled.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable auto captions","description":"Adds an option to disable captions from being automatically enabled.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable auto captions","description":"Adds an option to disable captions from being automatically enabled.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable dislike redirection","description":"Adds an option to disable redirection to the next track when clicking the Dislike button.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable haptic feedback","description":"Adds options to disable haptic feedback when swiping in the video player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable resuming Shorts on startup","description":"Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable screenshot popup","description":"Adds an option to disable the popup that appears when taking a screenshot.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable splash animation","description":"Adds an option to disable the splash animation on app startup.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable OPUS codec","description":"Adds an options to enable the OPUS audio codec if the player response includes.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable OPUS codec","description":"Adds an options to enable the OPUS audio codec if the player response includes.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable debug logging","description":"Adds an option to enable debug logging.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable debug logging","description":"Adds an option to enable debug logging.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable external browser","description":"Adds an option to always open links in your browser instead of in the in-app-browser.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable gradient loading screen","description":"Adds an option to enable the gradient loading screen.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable landscape mode","description":"Adds an option to enable landscape mode when rotating the screen on phones.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable open links directly","description":"Adds an option to skip over redirection URLs in external links.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Flyout menu components","description":"Adds options to hide or change flyout menu components.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Force hide player buttons background","description":"Removes, at compile time, the dark background surrounding the video player controls.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":false,"requiresIntegrations":false,"options":[]},{"name":"Fullscreen components","description":"Adds options to hide or change components related to fullscreen.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"GmsCore support","description":"Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":true,"options":[{"key":"GmsCoreVendorGroupId","default":"app.revanced","values":{"ReVanced":"app.revanced"},"title":"GmsCore vendor group ID","description":"The vendor\u0027s group ID for GmsCore.","required":true},{"key":"CheckGmsCore","default":true,"values":null,"title":"Check GmsCore","description":"Check if GmsCore is installed on the device and has battery optimizations disabled when the app starts. \n\nIf GmsCore is not installed the app will not work, so disabling this is not recommended.","required":true},{"key":"PackageNameYouTube","default":"app.rvx.android.youtube","values":{"Clone":"com.rvx.android.youtube","Default":"app.rvx.android.youtube"},"title":"Package name of YouTube","description":"The name of the package to use in GmsCore support.","required":true},{"key":"PackageNameYouTubeMusic","default":"app.rvx.android.apps.youtube.music","values":{"Clone":"com.rvx.android.apps.youtube.music","Default":"app.rvx.android.apps.youtube.music"},"title":"Package name of YouTube Music","description":"The name of the package to use in GmsCore support.","required":true}]},{"name":"GmsCore support","description":"Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":true,"options":[{"key":"GmsCoreVendorGroupId","default":"app.revanced","values":{"ReVanced":"app.revanced"},"title":"GmsCore vendor group ID","description":"The vendor\u0027s group ID for GmsCore.","required":true},{"key":"CheckGmsCore","default":true,"values":null,"title":"Check GmsCore","description":"Check if GmsCore is installed on the device and has battery optimizations disabled when the app starts. \n\nIf GmsCore is not installed the app will not work, so disabling this is not recommended.","required":true},{"key":"PackageNameYouTube","default":"app.rvx.android.youtube","values":{"Clone":"com.rvx.android.youtube","Default":"app.rvx.android.youtube"},"title":"Package name of YouTube","description":"The name of the package to use in GmsCore support.","required":true},{"key":"PackageNameYouTubeMusic","default":"app.rvx.android.apps.youtube.music","values":{"Clone":"com.rvx.android.apps.youtube.music","Default":"app.rvx.android.apps.youtube.music"},"title":"Package name of YouTube Music","description":"The name of the package to use in GmsCore support.","required":true}]},{"name":"Hide Recently Visited shelf","description":"Adds an option to hide the Recently Visited shelf in the sidebar.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide Shorts dimming","description":"Removes, at compile time, the dimming effect at the top and bottom of Shorts videos.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":false,"requiresIntegrations":false,"options":[]},{"name":"Hide account components","description":"Adds options to hide components related to the account menu.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide action bar components","description":"Adds options to hide action bar components and replace the offline download button with an external download button.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide action buttons","description":"Adds options to hide action buttons under videos.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide ads","description":"Adds options to hide ads.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide ads","description":"Adds options to hide ads.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":true,"options":[]},{"name":"Hide ads","description":"Adds options to hide ads.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide comments components","description":"Adds options to hide components related to comments.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide feed components","description":"Adds options to hide components related to feeds.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide feed flyout menu","description":"Adds the ability to hide feed flyout menu components using a custom filter.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide layout components","description":"Adds options to hide general layout components.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide layout components","description":"Adds options to hide general layout components.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide navigation buttons","description":"Adds options to hide buttons in the navigation bar.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide overlay filter","description":"Removes, at compile time, the dark overlay that appears when player flyout menus are open.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":false,"requiresIntegrations":false,"options":[]},{"name":"Hide player buttons","description":"Adds options to hide buttons in the video player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide player flyout menu","description":"Adds options to hide player flyout menu components.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide player overlay filter","description":"Removes, at compile time, the dark overlay that appears when single-tapping in the player.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":false,"requiresIntegrations":false,"options":[]},{"name":"Hide recommended communities shelf","description":"Adds an option to hide the recommended communities shelves in subreddits.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hook download actions","description":"Adds support to download videos with an external downloader app using the in-app download button.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Layout switch","description":"Adds an option to spoof the dpi in order to use a tablet or phone layout.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"MaterialYou","description":"Applies the MaterialYou theme for Android 12+ devices.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":false,"requiresIntegrations":false,"options":[]},{"name":"Miniplayer","description":"Adds options to change the in app minimized player, and if patching target 19.16+ adds options to use modern miniplayers.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Navigation bar components","description":"Adds options to hide or change components related to the navigation bar.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Navigation bar components","description":"Adds options to hide or change components related to the navigation bar.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Open links directly","description":"Adds an option to skip over redirection URLs in external links.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Open links externally","description":"Adds an option to always open links in your browser instead of in the in-app-browser.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Overlay buttons","description":"Adds options to display overlay buttons in the video player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"IconType","default":"bold","values":{"Bold":"bold","Rounded":"rounded","Thin":"thin"},"title":"Icon type","description":"The icon type.","required":true},{"key":"BottomMargin","default":"2.5dip","values":{"Default":"2.5dip","None":"0.0dip","Wider":"5.0dip"},"title":"Bottom margin","description":"The bottom margin for the overlay buttons and timestamp.","required":true},{"key":"ChangeTopButtons","default":false,"values":null,"title":"Change top buttons","description":"Change the icons at the top of the player.","required":true}]},{"name":"Player components","description":"Adds options to hide or change components related to the player.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Player components","description":"Adds options to hide or change components related to the video player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Premium icon","description":"Unlocks premium app icons.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Remove background playback restrictions","description":"Removes restrictions on background playback, including for kids videos.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Remove background playback restrictions","description":"Removes restrictions on background playback, including for music and kids videos.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Remove subreddit dialog","description":"Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Remove viewer discretion dialog","description":"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.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Remove viewer discretion dialog","description":"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.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Restore old style library shelf","description":"Adds an option to return the Library tab to the old style.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Return YouTube Dislike","description":"Adds an option to show the dislike count of songs using the Return YouTube Dislike API.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Return YouTube Dislike","description":"Adds an option to show the dislike count of videos using the Return YouTube Dislike API.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Sanitize sharing links","description":"Adds an option to remove tracking query parameters from URLs when sharing links.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Sanitize sharing links","description":"Adds an option to remove tracking query parameters from URLs when sharing links.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Sanitize sharing links","description":"Adds an option to remove tracking query parameters from URLs when sharing links.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Seekbar components","description":"Adds options to hide or change components related to the seekbar.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Settings for Reddit","description":"Applies mandatory patches to implement ReVanced Extended settings into the application.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":true,"options":[{"key":"RVXSettingsMenuName","default":"ReVanced Extended","values":null,"title":"RVX settings menu name","description":"The name of the RVX settings menu.","required":true}]},{"name":"Settings for YouTube","description":"Applies mandatory patches to implement ReVanced Extended settings into the application.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":true,"options":[{"key":"InsertPosition","default":"@string/about_key","values":{"Parent settings":"@string/parent_tools_key","General":"@string/general_key","Account":"@string/account_switcher_key","Data saving":"@string/data_saving_settings_key","Autoplay":"@string/auto_play_key","Video quality preferences":"@string/video_quality_settings_key","Background":"@string/offline_key","Watch on TV":"@string/pair_with_tv_key","Manage all history":"@string/history_key","Your data in YouTube":"@string/your_data_key","Privacy":"@string/privacy_key","History \u0026 privacy":"@string/privacy_key","Try experimental new features":"@string/premium_early_access_browse_page_key","Purchases and memberships":"@string/subscription_product_setting_key","Billing \u0026 payments":"@string/billing_and_payment_key","Billing and payments":"@string/billing_and_payment_key","Notifications":"@string/notification_key","Connected apps":"@string/connected_accounts_browse_page_key","Live chat":"@string/live_chat_key","Captions":"@string/captions_key","Accessibility":"@string/accessibility_settings_key","About":"@string/about_key"},"title":"Insert position","description":"The settings menu name that the RVX settings menu should be above.","required":true},{"key":"RVXSettingsMenuName","default":"ReVanced Extended","values":null,"title":"RVX settings menu name","description":"The name of the RVX settings menu.","required":true}]},{"name":"Settings for YouTube Music","description":"Applies mandatory patches to implement ReVanced Extended settings into the application.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":true,"options":[{"key":"RVXSettingsMenuName","default":"ReVanced Extended","values":null,"title":"RVX settings menu name","description":"The name of the RVX settings menu.","required":true}]},{"name":"Shorts components","description":"Adds options to hide or change components related to YouTube Shorts.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"SponsorBlock","description":"Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"SponsorBlock","description":"Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as sponsored content.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"OutlineIcon","default":false,"values":null,"title":"Outline icons","description":"Apply the outline icon.","required":true}]},{"name":"Spoof app version","description":"Adds options to spoof the YouTube Music client version. This can remove the radio mode restriction in Canadian regions or disable real-time lyrics.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Spoof app version","description":"Adds options to spoof the YouTube client version. This can be used to restore old UI elements and features.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Spoof streaming data","description":"Adds options to spoof the streaming data to allow video playback.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Swipe controls","description":"Adds options for controlling volume and brightness with swiping, and whether to enter fullscreen when swiping down below the player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Theme","description":"Changes the app\u0027s theme to the values specified in options.json.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"DarkThemeBackgroundColor","default":"@android:color/black","values":{"Amoled Black":"@android:color/black","Catppuccin (Mocha)":"#FF181825","Dark Pink":"#FF290025","Dark Blue":"#FF001029","Dark Green":"#FF002905","Dark Yellow":"#FF282900","Dark Orange":"#FF291800","Dark Red":"#FF290000"},"title":"Dark theme background color","description":"Can be a hex color (#AARRGGBB) or a color resource reference.","required":true},{"key":"LightThemeBackgroundColor","default":"@android:color/white","values":{"White":"@android:color/white","Catppuccin (Latte)":"#FFE6E9EF","Light Pink":"#FFFCCFF3","Light Blue":"#FFD1E0FF","Light Green":"#FFCCFFCC","Light Yellow":"#FFFDFFCC","Light Orange":"#FFFFE6CC","Light Red":"#FFFFD6D6"},"title":"Light theme background color","description":"Can be a hex color (#AARRGGBB) or a color resource reference.","required":true}]},{"name":"Toolbar components","description":"Adds options to hide or change components located on the toolbar, such as toolbar buttons, search bar, and header.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Translations for YouTube","description":"Add translations or remove string resources.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"CustomTranslations","default":"","values":null,"title":"Custom translations","description":"The path to the \u0027strings.xml\u0027 file.\nPlease note that applying the \u0027strings.xml\u0027 file will overwrite all existing translations.","required":true},{"key":"SelectedTranslations","default":"ar, bg-rBG, de-rDE, el-rGR, es-rES, fr-rFR, hu-rHU, it-rIT, ja-rJP, ko-rKR, pl-rPL, pt-rBR, ru-rRU, tr-rTR, uk-rUA, vi-rVN, zh-rCN, zh-rTW","values":null,"title":"Translations to add","description":"A list of translations to be added for the RVX settings, separated by commas.","required":true},{"key":"SelectedStringResources","default":"af, am, ar, ar-rXB, as, az, b+es+419, b+sr+Latn, be, bg, bn, bs, ca, cs, da, de, el, en-rAU, en-rCA, en-rGB, en-rIN, en-rXA, en-rXC, es, es-rUS, et, eu, fa, fi, fr, fr-rCA, gl, gu, hi, hr, hu, hy, id, in, is, it, iw, ja, ka, kk, km, kn, ko, ky, lo, lt, lv, mk, ml, mn, mr, ms, my, nb, ne, nl, no, or, pa, pl, pt, pt-rBR, pt-rPT, ro, ru, si, sk, sl, sq, sr, sv, sw, ta, te, th, tl, tr, uk, ur, uz, vi, zh, zh-rCN, zh-rHK, zh-rTW, zu","values":null,"title":"String resources to keep","description":"A list of string resources to be kept, separated by commas.\nString resources not in the list will be removed from the app.\n\nDefault string resource, English, is not removed.","required":true}]},{"name":"Translations for YouTube Music","description":"Add translations or remove string resources.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"CustomTranslations","default":"","values":null,"title":"Custom translations","description":"The path to the \u0027strings.xml\u0027 file.\nPlease note that applying the \u0027strings.xml\u0027 file will overwrite all existing language translations.","required":true},{"key":"SelectedTranslations","default":"bg-rBG, bn, cs-rCZ, el-rGR, es-rES, fr-rFR, hu-rHU, id-rID, in, it-rIT, ja-rJP, ko-rKR, nl-rNL, pl-rPL, pt-rBR, ro-rRO, ru-rRU, tr-rTR, uk-rUA, vi-rVN, zh-rCN, zh-rTW","values":null,"title":"Translations to add","description":"A list of translations to be added for the RVX settings, separated by commas.","required":true},{"key":"SelectedStringResources","default":"af, am, ar, ar-rXB, as, az, b+es+419, b+sr+Latn, be, bg, bn, bs, ca, cs, da, de, el, en-rAU, en-rCA, en-rGB, en-rIN, en-rXA, en-rXC, es, es-rUS, et, eu, fa, fi, fr, fr-rCA, gl, gu, hi, hr, hu, hy, id, in, is, it, iw, ja, ka, kk, km, kn, ko, ky, lo, lt, lv, mk, ml, mn, mr, ms, my, nb, ne, nl, no, or, pa, pl, pt, pt-rBR, pt-rPT, ro, ru, si, sk, sl, sq, sr, sv, sw, ta, te, th, tl, tr, uk, ur, uz, vi, zh, zh-rCN, zh-rHK, zh-rTW, zu","values":null,"title":"String resources to keep","description":"A list of string resources to be kept, separated by commas.\nString resources not in the list will be removed from the app.\n\nDefault string resource, English, is not removed.","required":true}]},{"name":"Video playback","description":"Adds options to customize settings related to video playback, such as default video quality and playback speed.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.29.58","6.33.52","6.42.55","6.51.53","7.16.53","7.17.51"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Video playback","description":"Adds options to customize settings related to video playback, such as default video quality and playback speed.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Visual preferences icons","description":"Adds icons to specific preferences in the settings.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":false,"requiresIntegrations":false,"options":[{"key":"RVXSettingsMenuIcon","default":"extension","values":{"Custom branding icon":"custom_branding_icon","Extension":"extension","Gear":"gear","ReVanced":"revanced","ReVanced Colored":"revanced_colored"},"title":"RVX settings menu icon","description":"The icon for the RVX settings menu.","required":true}]},{"name":"Watch history","description":"Adds an option to change the domain of the watch history or check its status.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]}] \ No newline at end of file +[{"name":"Alternative thumbnails","description":"Adds options to replace video thumbnails using the DeArrow API or image captures from the video.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Ambient mode control","description":"Adds options to disable Ambient mode and to bypass Ambient mode restrictions.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Amoled","description":"Applies a pure black theme to some components.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Bitrate default value","description":"Sets the audio quality to \u0027Always High\u0027 when you first install the app.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Bypass image region restrictions","description":"Adds an option to use a different host for static images, so that images blocked in some countries can be received.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Bypass image region restrictions","description":"Adds an option to use a different host for static images, so that images blocked in some countries can be received.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Certificate spoof","description":"Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change package name","description":"Changes the package name for Reddit to the name specified in options.json.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":false,"requiresIntegrations":false,"options":[{"key":"PackageNameReddit","default":"com.reddit.frontpage","values":{"Clone":"com.reddit.frontpage.revanced","Default":"com.reddit.frontpage.rvx","Original":"com.reddit.frontpage"},"title":"Package name of Reddit","description":"The name of the package to rename the app to.","required":true}]},{"name":"Change player flyout menu toggles","description":"Adds an option to use text toggles instead of switch toggles within the additional settings menu.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change share sheet","description":"Add option to change from in-app share sheet to system share sheet.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change share sheet","description":"Add option to change from in-app share sheet to system share sheet.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change start page","description":"Adds an option to set which page the app opens in instead of the homepage.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change start page","description":"Adds an option to set which page the app opens in instead of the homepage.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Change version code","description":"Changes the version code of the app to the value specified in options.json. Except when mounting, this can prevent app stores from updating the app and allow the app to be installed over an existing installation that has a higher version code. By default, the highest version code is set.","compatiblePackages":null,"use":false,"requiresIntegrations":false,"options":[{"key":"ChangeVersionCode","default":false,"values":null,"title":"Change version code","description":"Changes the version code of the app.","required":true},{"key":"VersionCode","default":"2147483647","values":null,"title":"Version code","description":"The version code to use. (1 ~ 2147483647)","required":true}]},{"name":"Custom Shorts action buttons","description":"Changes, at compile time, the icon of the action buttons of the Shorts player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"IconType","default":"youtubeoutline","values":{"Outline":"outline","OutlineCircle":"outlinecircle","Round":"round","YoutubeOutline":"youtubeoutline","YouTube":"youtube"},"title":"Shorts icon style ","description":"The style of the icons for the action buttons in the Shorts player.","required":true}]},{"name":"Custom branding icon for YouTube","description":"Changes the YouTube app icon to the icon specified in options.json.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"AppIcon","default":"revancify_blue","values":{"AFN Blue":"afn_blue","AFN Red":"afn_red","MMT":"mmt","Revancify Blue":"revancify_blue","Revancify Red":"revancify_red","YouTube":"youtube","YouTube (Minimal header)":"youtube_minimal_header"},"title":"App icon","description":"The icon to apply to the app.\n\nIf a path to a folder is provided, the folder must contain the following folders:\n\n- mipmap-xxxhdpi\n- mipmap-xxhdpi\n- mipmap-xhdpi\n- mipmap-hdpi\n- mipmap-mdpi\n\nEach of these folders must contain the following files:\n\n- adaptiveproduct_youtube_background_color_108.png\n- adaptiveproduct_youtube_foreground_color_108.png\n- ic_launcher.png\n- ic_launcher_round.png","required":true},{"key":"ChangeSplashIcon","default":true,"values":null,"title":"Change splash icons","description":"Apply the custom branding icon to the splash screen.","required":true},{"key":"RestoreOldSplashAnimation","default":true,"values":null,"title":"Restore old splash animation","description":"Restore the old style splash animation.","required":true}]},{"name":"Custom branding icon for YouTube Music","description":"Changes the YouTube Music app icon to the icon specified in options.json.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"AppIcon","default":"revancify_blue","values":{"AFN Blue":"afn_blue","AFN Red":"afn_red","MMT":"mmt","Revancify Blue":"revancify_blue","Revancify Red":"revancify_red","YouTube Music":"youtube_music"},"title":"App icon","description":"The icon to apply to the app.\n\nIf a path to a folder is provided, the folder must contain the following folders:\n\n- mipmap-xxxhdpi\n- mipmap-xxhdpi\n- mipmap-xhdpi\n- mipmap-hdpi\n- mipmap-mdpi\n\nEach of these folders must contain the following files:\n\n- adaptiveproduct_youtube_music_background_color_108.png\n- adaptiveproduct_youtube_music_foreground_color_108.png\n- ic_launcher_release.png","required":true},{"key":"ChangeSplashIcon","default":true,"values":null,"title":"Change splash icons","description":"Apply the custom branding icon to the splash screen.","required":true},{"key":"RestoreOldSplashIcon","default":false,"values":null,"title":"Restore old splash icon","description":"Restore the old style splash icon.\n\nIf you enable both the old style splash icon and the Cairo splash animation,\n\nOld style splash icon will appear first and then the Cairo splash animation will start.","required":true}]},{"name":"Custom branding name for Reddit","description":"Renames the Reddit app to the name specified in options.json.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":false,"requiresIntegrations":false,"options":[{"key":"AppName","default":"Reddit","values":{"Default":"RVX Reddit","Original":"Reddit"},"title":"App name","description":"The name of the app.","required":true}]},{"name":"Custom branding name for YouTube","description":"Renames the YouTube app to the name specified in options.json.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"AppName","default":"RVX","values":{"ReVanced Extended":"ReVanced Extended","RVX":"RVX","YouTube RVX":"YouTube RVX","YouTube":"YouTube"},"title":"App name","description":"The name of the app.","required":true}]},{"name":"Custom branding name for YouTube Music","description":"Renames the YouTube Music app to the name specified in options.json.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"AppNameNotification","default":"RVX Music","values":{"ReVanced Extended Music":"ReVanced Extended Music","RVX Music":"RVX Music","YouTube Music":"YouTube Music","YT Music":"YT Music"},"title":"App name in notification panel","description":"The name of the app as it appears in the notification panel.","required":true},{"key":"AppNameLauncher","default":"RVX Music","values":{"ReVanced Extended Music":"ReVanced Extended Music","RVX Music":"RVX Music","YouTube Music":"YouTube Music","YT Music":"YT Music"},"title":"App name in launcher","description":"The name of the app as it appears in the launcher.","required":true}]},{"name":"Custom double tap length","description":"Adds Double-tap to seek values that are specified in options.json.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"DoubleTapLengthArrays","default":"3, 5, 10, 15, 20, 30, 60, 120, 180","values":null,"title":"Double-tap to seek values","description":"A list of custom Double-tap to seek lengths to be added, separated by commas.","required":true}]},{"name":"Custom header for YouTube","description":"Applies a custom header in the top left corner within the app.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":false,"requiresIntegrations":false,"options":[{"key":"CustomHeader","default":"custom_branding_icon","values":{"Custom branding icon":"custom_branding_icon"},"title":"Custom header","description":"The header to apply to the app.\n\nPatch option \u0027Custom branding icon\u0027 applies only when:\n\n1. Patch \u0027Custom branding icon for YouTube\u0027 is included.\n2. Patch option for \u0027Custom branding icon for YouTube\u0027 is selected from the preset.\n\nIf a path to a folder is provided, the folder must contain one or more of the following folders, depending on the DPI of the device:\n\n- drawable-xxxhdpi\n- drawable-xxhdpi\n- drawable-xhdpi\n- drawable-hdpi\n- drawable-mdpi\n\nEach of the folders must contain all of the following files:\n\n[Generic header]\n\n- yt_wordmark_header_light.png\n- yt_wordmark_header_dark.png\n\nThe image dimensions must be as follows:\n\n- drawable-xxxhdpi: 488px x 192px\n- drawable-xxhdpi: 366px x 144px\n- drawable-xhdpi: 244px x 96px\n- drawable-hdpi: 184px x 72px\n- drawable-mdpi: 122px x 48px\n\n[Premium header]\n\n- yt_premium_wordmark_header_light.png\n- yt_premium_wordmark_header_dark.png\n\nThe image dimensions must be as follows:\n- drawable-xxxhdpi: 516px x 192px\n- drawable-xxhdpi: 387px x 144px\n- drawable-xhdpi: 258px x 96px\n- drawable-hdpi: 194px x 72px\n- drawable-mdpi: 129px x 48px","required":true}]},{"name":"Custom header for YouTube Music","description":"Applies a custom header in the top left corner within the app.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":false,"requiresIntegrations":false,"options":[{"key":"CustomHeader","default":"custom_branding_icon","values":{"Custom branding icon":"custom_branding_icon"},"title":"Custom header","description":"The header to apply to the app.\n\nPatch option \u0027Custom branding icon\u0027 applies only when:\n\n1. Patch \u0027Custom branding icon for YouTube Music\u0027 is included.\n2. Patch option for \u0027Custom branding icon for YouTube Music\u0027 is selected from the preset.\n\nIf a path to a folder is provided, the folder must contain one or more of the following folders, depending on the DPI of the device:\n\n- drawable-xxxhdpi\n- drawable-xxhdpi\n- drawable-xhdpi\n- drawable-hdpi\n- drawable-mdpi\n\nEach of the folders must contain all of the following files:\n\n- action_bar_logo.png\n- logo_music.png\n- ytm_logo.png\n\nThe image \u0027action_bar_logo.png\u0027 dimensions must be as follows:\n\n- drawable-xxxhdpi: 320px x 96px\n- drawable-xxhdpi: 240px x 72px\n- drawable-xhdpi: 160px x 48px\n- drawable-hdpi: 121px x 36px\n- drawable-mdpi: 80px x 24px\n\nThe image \u0027logo_music.png\u0027 dimensions must be as follows:\n\n- drawable-xxxhdpi: 576px x 200px\n- drawable-xxhdpi: 432px x 150px\n- drawable-xhdpi: 288px x 100px\n- drawable-hdpi: 217px x 76px\n- drawable-mdpi: 144px x 50px\n\nThe image \u0027ytm_logo.png\u0027 dimensions must be as follows:\n\n- drawable-xxxhdpi: 412px x 144px\n- drawable-xxhdpi: 309px x 108px\n- drawable-xhdpi: 206px x 72px\n- drawable-hdpi: 155px x 54px\n- drawable-mdpi: 103px x 36px","required":true}]},{"name":"Description components","description":"Adds options to hide and disable description components.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable Cairo splash animation","description":"Adds an option to disable Cairo splash animation.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["7.06.54","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable QUIC protocol","description":"Adds an option to disable CronetEngine\u0027s QUIC protocol.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable auto audio tracks","description":"Adds an option to disable audio tracks from being automatically enabled.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable auto captions","description":"Adds an option to disable captions from being automatically enabled.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable auto captions","description":"Adds an option to disable captions from being automatically enabled.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable dislike redirection","description":"Adds an option to disable redirection to the next track when clicking the Dislike button.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable haptic feedback","description":"Adds options to disable haptic feedback when swiping in the video player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable resuming Shorts on startup","description":"Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable screenshot popup","description":"Adds an option to disable the popup that appears when taking a screenshot.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Disable splash animation","description":"Adds an option to disable the splash animation on app startup.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable OPUS codec","description":"Adds an options to enable the OPUS audio codec if the player response includes.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable OPUS codec","description":"Adds an options to enable the OPUS audio codec if the player response includes.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable debug logging","description":"Adds an option to enable debug logging.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable debug logging","description":"Adds an option to enable debug logging.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable external browser","description":"Adds an option to always open links in your browser instead of in the in-app-browser.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable gradient loading screen","description":"Adds an option to enable the gradient loading screen.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable landscape mode","description":"Adds an option to enable landscape mode when rotating the screen on phones.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Enable open links directly","description":"Adds an option to skip over redirection URLs in external links.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Flyout menu components","description":"Adds options to hide or change flyout menu components.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Force hide player buttons background","description":"Removes, at compile time, the dark background surrounding the video player controls.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":false,"requiresIntegrations":false,"options":[]},{"name":"Fullscreen components","description":"Adds options to hide or change components related to fullscreen.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"GmsCore support","description":"Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":true,"options":[{"key":"GmsCoreVendorGroupId","default":"app.revanced","values":{"ReVanced":"app.revanced"},"title":"GmsCore vendor group ID","description":"The vendor\u0027s group ID for GmsCore.","required":true},{"key":"CheckGmsCore","default":true,"values":null,"title":"Check GmsCore","description":"Check if GmsCore is installed on the device and has battery optimizations disabled when the app starts. \n\nIf GmsCore is not installed the app will not work, so disabling this is not recommended.","required":true},{"key":"PackageNameYouTube","default":"app.rvx.android.youtube","values":{"Clone":"com.rvx.android.youtube","Default":"app.rvx.android.youtube"},"title":"Package name of YouTube","description":"The name of the package to use in GmsCore support.","required":true},{"key":"PackageNameYouTubeMusic","default":"app.rvx.android.apps.youtube.music","values":{"Clone":"com.rvx.android.apps.youtube.music","Default":"app.rvx.android.apps.youtube.music"},"title":"Package name of YouTube Music","description":"The name of the package to use in GmsCore support.","required":true}]},{"name":"GmsCore support","description":"Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":true,"options":[{"key":"GmsCoreVendorGroupId","default":"app.revanced","values":{"ReVanced":"app.revanced"},"title":"GmsCore vendor group ID","description":"The vendor\u0027s group ID for GmsCore.","required":true},{"key":"CheckGmsCore","default":true,"values":null,"title":"Check GmsCore","description":"Check if GmsCore is installed on the device and has battery optimizations disabled when the app starts. \n\nIf GmsCore is not installed the app will not work, so disabling this is not recommended.","required":true},{"key":"PackageNameYouTube","default":"app.rvx.android.youtube","values":{"Clone":"com.rvx.android.youtube","Default":"app.rvx.android.youtube"},"title":"Package name of YouTube","description":"The name of the package to use in GmsCore support.","required":true},{"key":"PackageNameYouTubeMusic","default":"app.rvx.android.apps.youtube.music","values":{"Clone":"com.rvx.android.apps.youtube.music","Default":"app.rvx.android.apps.youtube.music"},"title":"Package name of YouTube Music","description":"The name of the package to use in GmsCore support.","required":true}]},{"name":"Hide Recently Visited shelf","description":"Adds an option to hide the Recently Visited shelf in the sidebar.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide Shorts dimming","description":"Removes, at compile time, the dimming effect at the top and bottom of Shorts videos.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":false,"requiresIntegrations":false,"options":[]},{"name":"Hide account components","description":"Adds options to hide components related to the account menu.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide action bar components","description":"Adds options to hide action bar components and replace the offline download button with an external download button.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide action buttons","description":"Adds options to hide action buttons under videos.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide ads","description":"Adds options to hide ads.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide ads","description":"Adds options to hide ads.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":true,"options":[]},{"name":"Hide ads","description":"Adds options to hide ads.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide comments components","description":"Adds options to hide components related to comments.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide feed components","description":"Adds options to hide components related to feeds.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide feed flyout menu","description":"Adds the ability to hide feed flyout menu components using a custom filter.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide layout components","description":"Adds options to hide general layout components.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide layout components","description":"Adds options to hide general layout components.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide navigation buttons","description":"Adds options to hide buttons in the navigation bar.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide overlay filter","description":"Removes, at compile time, the dark overlay that appears when player flyout menus are open.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":false,"requiresIntegrations":false,"options":[]},{"name":"Hide player buttons","description":"Adds options to hide buttons in the video player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide player flyout menu","description":"Adds options to hide player flyout menu components.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide player overlay filter","description":"Removes, at compile time, the dark overlay that appears when single-tapping in the player.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":false,"requiresIntegrations":false,"options":[]},{"name":"Hide recommended communities shelf","description":"Adds an option to hide the recommended communities shelves in subreddits.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hide shortcuts","description":"Remove, at compile time, the app shortcuts that appears when app icon is long pressed.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":false,"requiresIntegrations":false,"options":[{"key":"Explore","default":false,"values":null,"title":"Hide Explore","description":"Hide Explore from shortcuts.","required":true},{"key":"Subscriptions","default":false,"values":null,"title":"Hide Subscriptions","description":"Hide Subscriptions from shortcuts.","required":true},{"key":"Search","default":false,"values":null,"title":"Hide Search","description":"Hide Search from shortcuts.","required":true},{"key":"Shorts","default":true,"values":null,"title":"Hide Shorts","description":"Hide Shorts from shortcuts.","required":true}]},{"name":"Hook YouTube Music actions","description":"Adds support for opening music in RVX Music using the in-app YouTube Music button.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Hook download actions","description":"Adds support to download videos with an external downloader app using the in-app download button.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Layout switch","description":"Adds an option to spoof the dpi in order to use a tablet or phone layout.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"MaterialYou","description":"Applies the MaterialYou theme for Android 12+ devices.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":false,"requiresIntegrations":false,"options":[]},{"name":"Miniplayer","description":"Adds options to change the in app minimized player, and if patching target 19.16+ adds options to use modern miniplayers.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Navigation bar components","description":"Adds options to hide or change components related to the navigation bar.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Navigation bar components","description":"Adds options to hide or change components related to the navigation bar.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Open links directly","description":"Adds an option to skip over redirection URLs in external links.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Open links externally","description":"Adds an option to always open links in your browser instead of in the in-app-browser.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Overlay buttons","description":"Adds options to display overlay buttons in the video player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"IconType","default":"bold","values":{"Bold":"bold","Rounded":"rounded","Thin":"thin"},"title":"Icon type","description":"The icon type.","required":true},{"key":"BottomMargin","default":"2.5dip","values":{"Default":"2.5dip","None":"0.0dip","Wider":"5.0dip"},"title":"Bottom margin","description":"The bottom margin for the overlay buttons and timestamp.","required":true},{"key":"ChangeTopButtons","default":false,"values":null,"title":"Change top buttons","description":"Change the icons at the top of the player.","required":true}]},{"name":"Player components","description":"Adds options to hide or change components related to the player.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Player components","description":"Adds options to hide or change components related to the video player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Premium icon","description":"Unlocks premium app icons.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Remove background playback restrictions","description":"Removes restrictions on background playback, including for kids videos.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Remove background playback restrictions","description":"Removes restrictions on background playback, including for music and kids videos.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Remove subreddit dialog","description":"Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Remove viewer discretion dialog","description":"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.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Remove viewer discretion dialog","description":"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.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Restore old style library shelf","description":"Adds an option to return the Library tab to the old style.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Return YouTube Dislike","description":"Adds an option to show the dislike count of songs using the Return YouTube Dislike API.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Return YouTube Dislike","description":"Adds an option to show the dislike count of videos using the Return YouTube Dislike API.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Sanitize sharing links","description":"Adds an option to remove tracking query parameters from URLs when sharing links.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Sanitize sharing links","description":"Adds an option to remove tracking query parameters from URLs when sharing links.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Sanitize sharing links","description":"Adds an option to remove tracking query parameters from URLs when sharing links.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Seekbar components","description":"Adds options to hide or change components related to the seekbar.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Settings for Reddit","description":"Applies mandatory patches to implement ReVanced Extended settings into the application.","compatiblePackages":[{"name":"com.reddit.frontpage","versions":["2023.12.0","2024.17.0"]}],"use":true,"requiresIntegrations":true,"options":[{"key":"RVXSettingsMenuName","default":"ReVanced Extended","values":null,"title":"RVX settings menu name","description":"The name of the RVX settings menu.","required":true}]},{"name":"Settings for YouTube","description":"Applies mandatory patches to implement ReVanced Extended settings into the application.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":true,"options":[{"key":"InsertPosition","default":"@string/about_key","values":{"Parent settings":"@string/parent_tools_key","General":"@string/general_key","Account":"@string/account_switcher_key","Data saving":"@string/data_saving_settings_key","Autoplay":"@string/auto_play_key","Video quality preferences":"@string/video_quality_settings_key","Background":"@string/offline_key","Watch on TV":"@string/pair_with_tv_key","Manage all history":"@string/history_key","Your data in YouTube":"@string/your_data_key","Privacy":"@string/privacy_key","History \u0026 privacy":"@string/privacy_key","Try experimental new features":"@string/premium_early_access_browse_page_key","Purchases and memberships":"@string/subscription_product_setting_key","Billing \u0026 payments":"@string/billing_and_payment_key","Billing and payments":"@string/billing_and_payment_key","Notifications":"@string/notification_key","Connected apps":"@string/connected_accounts_browse_page_key","Live chat":"@string/live_chat_key","Captions":"@string/captions_key","Accessibility":"@string/accessibility_settings_key","About":"@string/about_key"},"title":"Insert position","description":"The settings menu name that the RVX settings menu should be above.","required":true},{"key":"RVXSettingsMenuName","default":"ReVanced Extended","values":null,"title":"RVX settings menu name","description":"The name of the RVX settings menu.","required":true}]},{"name":"Settings for YouTube Music","description":"Applies mandatory patches to implement ReVanced Extended settings into the application.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":true,"options":[{"key":"RVXSettingsMenuName","default":"ReVanced Extended","values":null,"title":"RVX settings menu name","description":"The name of the RVX settings menu.","required":true}]},{"name":"Shorts components","description":"Adds options to hide or change components related to YouTube Shorts.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"SponsorBlock","description":"Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"SponsorBlock","description":"Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as sponsored content.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"OutlineIcon","default":false,"values":null,"title":"Outline icons","description":"Apply the outline icon.","required":true}]},{"name":"Spoof app version","description":"Adds options to spoof the YouTube Music client version. This can remove the radio mode restriction in Canadian regions or disable real-time lyrics.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Spoof app version","description":"Adds options to spoof the YouTube client version. This can be used to restore old UI elements and features.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Spoof streaming data","description":"Adds options to spoof the streaming data to allow video playback.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Swipe controls","description":"Adds options for controlling volume and brightness with swiping, and whether to enter fullscreen when swiping down below the player.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Theme","description":"Changes the app\u0027s theme to the values specified in options.json.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"DarkThemeBackgroundColor","default":"@android:color/black","values":{"Amoled Black":"@android:color/black","Catppuccin (Mocha)":"#FF181825","Dark Pink":"#FF290025","Dark Blue":"#FF001029","Dark Green":"#FF002905","Dark Yellow":"#FF282900","Dark Orange":"#FF291800","Dark Red":"#FF290000"},"title":"Dark theme background color","description":"Can be a hex color (#AARRGGBB) or a color resource reference.","required":true},{"key":"LightThemeBackgroundColor","default":"@android:color/white","values":{"White":"@android:color/white","Catppuccin (Latte)":"#FFE6E9EF","Light Pink":"#FFFCCFF3","Light Blue":"#FFD1E0FF","Light Green":"#FFCCFFCC","Light Yellow":"#FFFDFFCC","Light Orange":"#FFFFE6CC","Light Red":"#FFFFD6D6"},"title":"Light theme background color","description":"Can be a hex color (#AARRGGBB) or a color resource reference.","required":true}]},{"name":"Toolbar components","description":"Adds options to hide or change components located on the toolbar, such as toolbar buttons, search bar, and header.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Translations for YouTube","description":"Add translations or remove string resources.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"CustomTranslations","default":"","values":null,"title":"Custom translations","description":"The path to the \u0027strings.xml\u0027 file.\nPlease note that applying the \u0027strings.xml\u0027 file will overwrite all existing translations.","required":true},{"key":"SelectedTranslations","default":"ar, bg-rBG, de-rDE, el-rGR, es-rES, fr-rFR, hu-rHU, it-rIT, ja-rJP, ko-rKR, pl-rPL, pt-rBR, ru-rRU, tr-rTR, uk-rUA, vi-rVN, zh-rCN, zh-rTW","values":null,"title":"Translations to add","description":"A list of translations to be added for the RVX settings, separated by commas.","required":true},{"key":"SelectedStringResources","default":"af, am, ar, ar-rXB, as, az, b+es+419, b+sr+Latn, be, bg, bn, bs, ca, cs, da, de, el, en-rAU, en-rCA, en-rGB, en-rIN, en-rXA, en-rXC, es, es-rUS, et, eu, fa, fi, fr, fr-rCA, gl, gu, hi, hr, hu, hy, id, in, is, it, iw, ja, ka, kk, km, kn, ko, ky, lo, lt, lv, mk, ml, mn, mr, ms, my, nb, ne, nl, no, or, pa, pl, pt, pt-rBR, pt-rPT, ro, ru, si, sk, sl, sq, sr, sv, sw, ta, te, th, tl, tr, uk, ur, uz, vi, zh, zh-rCN, zh-rHK, zh-rTW, zu","values":null,"title":"String resources to keep","description":"A list of string resources to be kept, separated by commas.\nString resources not in the list will be removed from the app.\n\nDefault string resource, English, is not removed.","required":true}]},{"name":"Translations for YouTube Music","description":"Add translations or remove string resources.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"CustomTranslations","default":"","values":null,"title":"Custom translations","description":"The path to the \u0027strings.xml\u0027 file.\nPlease note that applying the \u0027strings.xml\u0027 file will overwrite all existing language translations.","required":true},{"key":"SelectedTranslations","default":"bg-rBG, bn, cs-rCZ, el-rGR, es-rES, fr-rFR, hu-rHU, id-rID, in, it-rIT, ja-rJP, ko-rKR, nl-rNL, pl-rPL, pt-rBR, ro-rRO, ru-rRU, tr-rTR, uk-rUA, vi-rVN, zh-rCN, zh-rTW","values":null,"title":"Translations to add","description":"A list of translations to be added for the RVX settings, separated by commas.","required":true},{"key":"SelectedStringResources","default":"af, am, ar, ar-rXB, as, az, b+es+419, b+sr+Latn, be, bg, bn, bs, ca, cs, da, de, el, en-rAU, en-rCA, en-rGB, en-rIN, en-rXA, en-rXC, es, es-rUS, et, eu, fa, fi, fr, fr-rCA, gl, gu, hi, hr, hu, hy, id, in, is, it, iw, ja, ka, kk, km, kn, ko, ky, lo, lt, lv, mk, ml, mn, mr, ms, my, nb, ne, nl, no, or, pa, pl, pt, pt-rBR, pt-rPT, ro, ru, si, sk, sl, sq, sr, sv, sw, ta, te, th, tl, tr, uk, ur, uz, vi, zh, zh-rCN, zh-rHK, zh-rTW, zu","values":null,"title":"String resources to keep","description":"A list of string resources to be kept, separated by commas.\nString resources not in the list will be removed from the app.\n\nDefault string resource, English, is not removed.","required":true}]},{"name":"Video playback","description":"Adds options to customize settings related to video playback, such as default video quality and playback speed.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Video playback","description":"Adds options to customize settings related to video playback, such as default video quality and playback speed.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]},{"name":"Visual preferences icons for YouTube","description":"Adds icons to specific preferences in the settings.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"RVXSettingsMenuIcon","default":"extension","values":{"Custom branding icon":"custom_branding_icon","Extension":"extension","Gear":"gear","YT alt":"yt_alt","ReVanced":"revanced","ReVanced Colored":"revanced_colored"},"title":"RVX settings menu icon","description":"The icon for the RVX settings menu.","required":true},{"key":"ApplyToAll","default":false,"values":null,"title":"Apply to all settings menu","description":"Whether to apply Visual preferences icons to all settings menus.\n\nIf true: icons are applied to the parent PreferenceScreen of YouTube settings, the parent PreferenceScreen of RVX settings and the RVX sub-settings (if supports).\n\nIf false: icons are applied only to the parent PreferenceScreen of YouTube settings and RVX settings.","required":true}]},{"name":"Visual preferences icons for YouTube Music","description":"Adds icons to specific preferences in the settings.","compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["6.20.51","6.29.59","6.42.55","6.51.53","7.16.53"]}],"use":true,"requiresIntegrations":false,"options":[{"key":"RVXSettingsMenuIcon","default":"extension","values":{"Custom branding icon":"custom_branding_icon","Extension":"extension","Gear":"gear","ReVanced":"revanced","ReVanced Colored":"revanced_colored"},"title":"RVX settings menu icon","description":"The icon for the RVX settings menu.","required":true}]},{"name":"Watch history","description":"Adds an option to change the domain of the watch history or check its status.","compatiblePackages":[{"name":"com.google.android.youtube","versions":["18.29.38","18.33.40","18.38.44","18.48.39","19.05.36","19.16.39"]}],"use":true,"requiresIntegrations":false,"options":[]}] \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/account/components/AccountComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/music/account/components/AccountComponentsPatch.kt index 9b4288ad16..294d02e129 100644 --- a/src/main/kotlin/app/revanced/patches/music/account/components/AccountComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/account/components/AccountComponentsPatch.kt @@ -14,12 +14,14 @@ import app.revanced.patches.music.utils.integrations.Constants.ACCOUNT_CLASS_DES import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.getTargetIndexWithReferenceOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Suppress("unused") object AccountComponentsPatch : BaseBytecodePatch( @@ -43,8 +45,14 @@ object AccountComponentsPatch : BaseBytecodePatch( MenuEntryFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val textIndex = getTargetIndexWithMethodReferenceNameOrThrow("setText") - val viewIndex = getTargetIndexWithMethodReferenceNameOrThrow("addView") + val textIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setText" + } + val viewIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "addView" + } val textRegister = getInstruction(textIndex).registerD val viewRegister = getInstruction(viewIndex).registerD @@ -64,9 +72,14 @@ object AccountComponentsPatch : BaseBytecodePatch( AccountSwitcherAccessibilityLabelFingerprint.resultOrThrow().let { result -> result.mutableMethod.apply { - val textColorIndex = getTargetIndexWithMethodReferenceNameOrThrow("setTextColor") - val setVisibilityIndex = - getTargetIndexWithMethodReferenceNameOrThrow(textColorIndex, "setVisibility") + val textColorIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setTextColor" + } + val setVisibilityIndex = indexOfFirstInstructionOrThrow(textColorIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setVisibility" + } val textViewInstruction = getInstruction(setVisibilityIndex) @@ -98,8 +111,12 @@ object AccountComponentsPatch : BaseBytecodePatch( TermsOfServiceFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = - getTargetIndexWithReferenceOrThrow("/PrivacyTosFooter;->setVisibility(I)V") + val insertIndex = indexOfFirstInstructionOrThrow { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL && + reference?.name == "setVisibility" && + reference.definingClass.endsWith("/PrivacyTosFooter;") + } val visibilityRegister = getInstruction(insertIndex).registerD diff --git a/src/main/kotlin/app/revanced/patches/music/actionbar/components/ActionBarComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/music/actionbar/components/ActionBarComponentsPatch.kt index 7adeadfdc4..af0f94b917 100644 --- a/src/main/kotlin/app/revanced/patches/music/actionbar/components/ActionBarComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/actionbar/components/ActionBarComponentsPatch.kt @@ -15,10 +15,9 @@ import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.LikeDis import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.SettingsPatch import app.revanced.patches.music.video.information.VideoInformationPatch -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.getTargetIndexWithReferenceOrThrow -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -26,6 +25,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference import kotlin.math.min @Suppress("unused") @@ -48,7 +48,10 @@ object ActionBarComponentsPatch : BaseBytecodePatch( it.mutableMethod.apply { // hook download button - val addViewIndex = getTargetIndexWithMethodReferenceNameOrThrow("addView") + val addViewIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "addView" + } val addViewRegister = getInstruction(addViewIndex).registerD @@ -83,10 +86,15 @@ object ActionBarComponentsPatch : BaseBytecodePatch( removeInstruction(replaceIndex) // hide action button - val hasNextIndex = getTargetIndexWithMethodReferenceNameOrThrow("hasNext") + val hasNextIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_INTERFACE && + getReference()?.name == "hasNext" + } val freeRegister = min(implementation!!.registerCount - parameters.size - 2, 15) - val spannedIndex = getTargetIndexWithReferenceOrThrow(")Landroid/text/Spanned;") + val spannedIndex = indexOfFirstInstructionOrThrow { + getReference()?.returnType == "Landroid/text/Spanned;" + } val spannedRegister = getInstruction(spannedIndex).registerC val spannedReference = getInstruction(spannedIndex).reference @@ -124,7 +132,8 @@ object ActionBarComponentsPatch : BaseBytecodePatch( LikeDislikeContainerFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = getWideLiteralInstructionIndex(LikeDislikeContainer) + 2 + val insertIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(LikeDislikeContainer) + 2 val insertRegister = getInstruction(insertIndex).registerA addInstruction( diff --git a/src/main/kotlin/app/revanced/patches/music/ads/general/AdsPatch.kt b/src/main/kotlin/app/revanced/patches/music/ads/general/AdsPatch.kt index 841f0527a3..9a1d8d21cb 100644 --- a/src/main/kotlin/app/revanced/patches/music/ads/general/AdsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/ads/general/AdsPatch.kt @@ -22,19 +22,21 @@ import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.ButtonContainer import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.FloatingLayout import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.InterstitialsContainer +import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.PrivacyTosFooter import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.SettingsPatch import app.revanced.patches.shared.litho.LithoFilterPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithReferenceOrThrow +import app.revanced.util.getReference import app.revanced.util.getWalkerMethod -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference @Suppress("unused") object AdsPatch : BaseBytecodePatch( @@ -61,9 +63,6 @@ object AdsPatch : BaseBytecodePatch( private const val ADS_FILTER_CLASS_DESCRIPTOR = "$COMPONENTS_PATH/AdsFilter;" - private const val FULLSCREEN_ADS_FILTER_CLASS_DESCRIPTOR = - "${app.revanced.patches.shared.integrations.Constants.COMPONENTS_PATH}/FullscreenAdsFilter;" - private const val PREMIUM_PROMOTION_POP_UP_CLASS_DESCRIPTOR = "$ADS_PATH/PremiumPromotionPatch;" @@ -82,7 +81,7 @@ object AdsPatch : BaseBytecodePatch( // litho view, used in 'ShowDialogCommandOuterClass' in innertube ShowDialogCommandFingerprint .resultOrThrow() - .hookLithoFullscreenAds(context) + .hookLithoFullscreenAds() // endregion @@ -90,7 +89,7 @@ object AdsPatch : BaseBytecodePatch( FloatingLayoutFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = getWideLiteralInstructionIndex(FloatingLayout) + 2 + val targetIndex = indexOfFirstWideLiteralInstructionValueOrThrow(FloatingLayout) + 2 val targetRegister = getInstruction(targetIndex).registerA addInstruction( @@ -106,7 +105,8 @@ object AdsPatch : BaseBytecodePatch( NotifierShelfFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val linearLayoutIndex = getWideLiteralInstructionIndex(ButtonContainer) + 3 + val linearLayoutIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(ButtonContainer) + 3 val linearLayoutRegister = getInstruction(linearLayoutIndex).registerA @@ -138,16 +138,20 @@ object AdsPatch : BaseBytecodePatch( AccountMenuFooterFingerprint.resultOrThrow().let { it.mutableMethod.apply { val constIndex = - getWideLiteralInstructionIndex(SharedResourceIdPatch.PrivacyTosFooter) - val walkerIndex = getTargetIndexOrThrow(constIndex + 2, Opcode.INVOKE_VIRTUAL) - val viewIndex = getTargetIndexOrThrow(constIndex, Opcode.IGET_OBJECT) + indexOfFirstWideLiteralInstructionValueOrThrow(PrivacyTosFooter) + val walkerIndex = + indexOfFirstInstructionOrThrow(constIndex + 2, Opcode.INVOKE_VIRTUAL) + val viewIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.IGET_OBJECT) val viewReference = getInstruction(viewIndex).reference.toString() val walkerMethod = getWalkerMethod(context, walkerIndex) walkerMethod.apply { - val insertIndex = getTargetIndexWithReferenceOrThrow(viewReference) - val nullCheckIndex = getTargetIndexOrThrow(insertIndex - 1, Opcode.IF_NEZ) + val insertIndex = indexOfFirstInstructionOrThrow { + getReference()?.toString() == viewReference + } + val nullCheckIndex = + indexOfFirstInstructionOrThrow(insertIndex - 1, Opcode.IF_NEZ) val nullCheckRegister = getInstruction(nullCheckIndex).registerA @@ -174,19 +178,12 @@ object AdsPatch : BaseBytecodePatch( // endregion LithoFilterPatch.addFilter(ADS_FILTER_CLASS_DESCRIPTOR) - LithoFilterPatch.addFilter(FULLSCREEN_ADS_FILTER_CLASS_DESCRIPTOR) SettingsPatch.addSwitchPreference( CategoryType.ADS, "revanced_hide_fullscreen_ads", "true" ) - SettingsPatch.addSwitchPreference( - CategoryType.ADS, - "revanced_hide_fullscreen_ads_type", - "true", - "revanced_hide_fullscreen_ads" - ) SettingsPatch.addSwitchPreference( CategoryType.ADS, "revanced_hide_general_ads", diff --git a/src/main/kotlin/app/revanced/patches/music/ads/general/fingerprints/NotifierShelfFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/ads/general/fingerprints/NotifierShelfFingerprint.kt index e0ba63d866..d76a6847db 100644 --- a/src/main/kotlin/app/revanced/patches/music/ads/general/fingerprints/NotifierShelfFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/ads/general/fingerprints/NotifierShelfFingerprint.kt @@ -4,14 +4,14 @@ import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.ButtonContainer import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MusicNotifierShelf -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object NotifierShelfFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(MusicNotifierShelf) - && methodDef.containsWideLiteralInstructionIndex(ButtonContainer) + methodDef.containsWideLiteralInstructionValue(MusicNotifierShelf) + && methodDef.containsWideLiteralInstructionValue(ButtonContainer) } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/ads/general/fingerprints/ShowDialogCommandFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/ads/general/fingerprints/ShowDialogCommandFingerprint.kt index fed10e5e95..49e108d668 100644 --- a/src/main/kotlin/app/revanced/patches/music/ads/general/fingerprints/ShowDialogCommandFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/ads/general/fingerprints/ShowDialogCommandFingerprint.kt @@ -1,17 +1,28 @@ package app.revanced.patches.music.ads.general.fingerprints +import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.SlidingDialogAnimation -import app.revanced.util.fingerprint.LiteralValueFingerprint +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.Opcode -internal object ShowDialogCommandFingerprint : LiteralValueFingerprint( +internal object ShowDialogCommandFingerprint : MethodFingerprint( returnType = "V", - parameters = listOf("[B", "L"), opcodes = listOf( Opcode.IF_EQ, Opcode.IGET_OBJECT, Opcode.INVOKE_VIRTUAL, Opcode.IGET, // get dialog code ), - literalSupplier = { SlidingDialogAnimation } + // 6.26 and earlier has a different first parameter. + // Since this fingerprint is somewhat weak, work around by checking for both method parameter signatures. + customFingerprint = custom@{ methodDef, _ -> + if (!methodDef.containsWideLiteralInstructionValue(SlidingDialogAnimation)) { + return@custom false + } + // 6.26 and earlier parameters are: "L", "L" + // 6.27+ parameters are "[B", "L" + val parameterTypes = methodDef.parameterTypes + + parameterTypes.size == 2 && parameterTypes[1].startsWith("L") + }, ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsPatch.kt index 9e67be6352..b3356171fb 100644 --- a/src/main/kotlin/app/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsPatch.kt @@ -5,7 +5,6 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.patch.PatchException import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.music.flyoutmenu.components.fingerprints.DialogSolidFingerprint import app.revanced.patches.music.flyoutmenu.components.fingerprints.EndButtonsContainerFingerprint @@ -20,17 +19,18 @@ import app.revanced.patches.music.utils.integrations.Constants.COMPONENTS_PATH import app.revanced.patches.music.utils.integrations.Constants.FLYOUT_CLASS_DESCRIPTOR import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.EndButtonsContainer +import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.TrimSilenceSwitch import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.SettingsPatch import app.revanced.patches.music.utils.videotype.VideoTypeHookPatch import app.revanced.patches.music.video.information.VideoInformationPatch import app.revanced.patches.shared.litho.LithoFilterPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.findMethodOrThrow +import app.revanced.util.getReference import app.revanced.util.getWalkerMethod -import app.revanced.util.getWideLiteralInstructionIndex import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.literalInstructionBooleanHook +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -88,7 +88,7 @@ object FlyoutMenuComponentsPatch : BaseBytecodePatch( // region patch for enable trim silence TrimSilenceConfigFingerprint.result?.let { - TrimSilenceConfigFingerprint.literalInstructionBooleanHook( + TrimSilenceConfigFingerprint.injectLiteralInstructionBooleanCall( 45619123, "$FLYOUT_CLASS_DESCRIPTOR->enableTrimSilence(Z)Z" ) @@ -96,40 +96,39 @@ object FlyoutMenuComponentsPatch : BaseBytecodePatch( TrimSilenceSwitchFingerprint.resultOrThrow().let { it.mutableMethod.apply { val constIndex = - getWideLiteralInstructionIndex(SharedResourceIdPatch.TrimSilenceSwitch) + indexOfFirstWideLiteralInstructionValueOrThrow(TrimSilenceSwitch) val onCheckedChangedListenerIndex = - getTargetIndexOrThrow(constIndex, Opcode.INVOKE_DIRECT) + indexOfFirstInstructionOrThrow(constIndex, Opcode.INVOKE_DIRECT) val onCheckedChangedListenerReference = getInstruction(onCheckedChangedListenerIndex).reference val onCheckedChangedListenerDefiningClass = (onCheckedChangedListenerReference as MethodReference).definingClass - val onCheckedChangedListenerClass = - context.findClass(onCheckedChangedListenerDefiningClass)!!.mutableClass - - onCheckedChangedListenerClass.methods.find { method -> method.name == "onCheckedChanged" } - ?.apply { - val walkerIndex = indexOfFirstInstructionOrThrow { - val reference = - ((this as? ReferenceInstruction)?.reference as? MethodReference) + context.findMethodOrThrow(onCheckedChangedListenerDefiningClass) { + name == "onCheckedChanged" + }.apply { + val onCheckedChangedWalkerIndex = + indexOfFirstInstructionOrThrow { + val reference = getReference() opcode == Opcode.INVOKE_VIRTUAL && reference?.returnType == "V" && reference.parameterTypes.size == 1 && reference.parameterTypes[0] == "Z" } - getWalkerMethod(context, walkerIndex).apply { - val insertIndex = getTargetIndexOrThrow(Opcode.MOVE_RESULT) - val insertRegister = - getInstruction(insertIndex).registerA - addInstructions( - insertIndex + 1, """ + getWalkerMethod(context, onCheckedChangedWalkerIndex).apply { + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT) + val insertRegister = + getInstruction(insertIndex).registerA + + addInstructions( + insertIndex + 1, """ invoke-static {v$insertRegister}, $FLYOUT_CLASS_DESCRIPTOR->enableTrimSilenceSwitch(Z)Z move-result v$insertRegister """ - ) - } - } ?: throw PatchException("onClickClass not found!") + ) + } + } } } @@ -142,7 +141,7 @@ object FlyoutMenuComponentsPatch : BaseBytecodePatch( MenuItemFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val freeIndex = getTargetIndexOrThrow(Opcode.OR_INT_LIT16) + val freeIndex = indexOfFirstInstructionOrThrow(Opcode.OR_INT_LIT16) val textViewIndex = it.scanResult.patternScanResult!!.startIndex val imageViewIndex = it.scanResult.patternScanResult!!.endIndex @@ -175,8 +174,10 @@ object FlyoutMenuComponentsPatch : BaseBytecodePatch( TouchOutsideFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val setOnClickListenerIndex = - getTargetIndexWithMethodReferenceNameOrThrow("setOnClickListener") + val setOnClickListenerIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setOnClickListener" + } val setOnClickListenerRegister = getInstruction(setOnClickListenerIndex).registerC @@ -189,8 +190,10 @@ object FlyoutMenuComponentsPatch : BaseBytecodePatch( EndButtonsContainerFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val startIndex = getWideLiteralInstructionIndex(EndButtonsContainer) - val targetIndex = getTargetIndexOrThrow(startIndex, Opcode.MOVE_RESULT_OBJECT) + val startIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(EndButtonsContainer) + val targetIndex = + indexOfFirstInstructionOrThrow(startIndex, Opcode.MOVE_RESULT_OBJECT) val targetRegister = getInstruction(targetIndex).registerA addInstruction( diff --git a/src/main/kotlin/app/revanced/patches/music/general/autocaptions/AutoCaptionsPatch.kt b/src/main/kotlin/app/revanced/patches/music/general/autocaptions/AutoCaptionsPatch.kt index 22c0d9b647..6b68863a0b 100644 --- a/src/main/kotlin/app/revanced/patches/music/general/autocaptions/AutoCaptionsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/general/autocaptions/AutoCaptionsPatch.kt @@ -1,39 +1,24 @@ package app.revanced.patches.music.general.autocaptions import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE -import app.revanced.patches.music.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.SettingsPatch -import app.revanced.patches.shared.fingerprints.SubtitleTrackFingerprint +import app.revanced.patches.shared.captions.BaseAutoCaptionsPatch import app.revanced.util.patch.BaseBytecodePatch -import app.revanced.util.resultOrThrow -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @Suppress("unused") object AutoCaptionsPatch : BaseBytecodePatch( name = "Disable auto captions", description = "Adds an option to disable captions from being automatically enabled.", - dependencies = setOf(SettingsPatch::class), + dependencies = setOf( + BaseAutoCaptionsPatch::class, + SettingsPatch::class + ), compatiblePackages = COMPATIBLE_PACKAGE, - fingerprints = setOf(SubtitleTrackFingerprint), ) { override fun execute(context: BytecodeContext) { - SubtitleTrackFingerprint.resultOrThrow().mutableMethod.apply { - val index = implementation!!.instructions.lastIndex - val register = getInstruction(index).registerA - - addInstructions( - index, """ - invoke-static {v$register}, $GENERAL_CLASS_DESCRIPTOR->disableAutoCaptions(Z)Z - move-result v$register - """ - ) - } - SettingsPatch.addSwitchPreference( CategoryType.GENERAL, "revanced_disable_auto_captions", diff --git a/src/main/kotlin/app/revanced/patches/music/general/components/LayoutComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/music/general/components/LayoutComponentsPatch.kt index 0c277ef546..f43d509f2c 100644 --- a/src/main/kotlin/app/revanced/patches/music/general/components/LayoutComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/general/components/LayoutComponentsPatch.kt @@ -27,15 +27,17 @@ import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKA import app.revanced.patches.music.utils.integrations.Constants.COMPONENTS_PATH import app.revanced.patches.music.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch +import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MusicTasteBuilderShelf +import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.PlayerOverlayChip import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.TopBarMenuItemImageView import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.SettingsPatch import app.revanced.patches.shared.litho.LithoFilterPatch import app.revanced.patches.shared.settingmenu.SettingsMenuPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.getWideLiteralInstructionIndex -import app.revanced.util.literalInstructionBooleanHook +import app.revanced.util.alsoResolve +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -99,7 +101,7 @@ object LayoutComponentsPatch : BaseBytecodePatch( PlayerOverlayChipFingerprint.resultOrThrow().let { it.mutableMethod.apply { val targetIndex = - getWideLiteralInstructionIndex(SharedResourceIdPatch.PlayerOverlayChip) + 2 + indexOfFirstWideLiteralInstructionValueOrThrow(PlayerOverlayChip) + 2 val targetRegister = getInstruction(targetIndex).registerA addInstruction( @@ -176,8 +178,10 @@ object LayoutComponentsPatch : BaseBytecodePatch( if (SettingsPatch.upward0642) { TopBarMenuItemImageViewFingerprint.resultOrThrow().mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(TopBarMenuItemImageView) - val targetIndex = getTargetIndexOrThrow(constIndex, Opcode.MOVE_RESULT_OBJECT) + val constIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(TopBarMenuItemImageView) + val targetIndex = + indexOfFirstInstructionOrThrow(constIndex, Opcode.MOVE_RESULT_OBJECT) val targetRegister = getInstruction(targetIndex).registerA addInstruction( @@ -193,7 +197,7 @@ object LayoutComponentsPatch : BaseBytecodePatch( // region patch for hide sound search button SoundSearchFingerprint.result?.let { - SoundSearchFingerprint.literalInstructionBooleanHook( + SoundSearchFingerprint.injectLiteralInstructionBooleanCall( 45625491, "$GENERAL_CLASS_DESCRIPTOR->hideSoundSearchButton(Z)Z" ) @@ -227,8 +231,9 @@ object LayoutComponentsPatch : BaseBytecodePatch( parentResult.mutableMethod.apply { val constIndex = - getWideLiteralInstructionIndex(SharedResourceIdPatch.MusicTasteBuilderShelf) - val targetIndex = getTargetIndexOrThrow(constIndex, Opcode.MOVE_RESULT_OBJECT) + indexOfFirstWideLiteralInstructionValueOrThrow(MusicTasteBuilderShelf) + val targetIndex = + indexOfFirstInstructionOrThrow(constIndex, Opcode.MOVE_RESULT_OBJECT) val targetRegister = getInstruction(targetIndex).registerA addInstruction( @@ -263,14 +268,11 @@ object LayoutComponentsPatch : BaseBytecodePatch( // region patch for hide voice search button - SearchBarFingerprint.resolve( - context, - SearchBarParentFingerprint.resultOrThrow().classDef - ) - SearchBarFingerprint.resultOrThrow().let { + SearchBarFingerprint.alsoResolve( + context, SearchBarParentFingerprint + ).let { it.mutableMethod.apply { - val setVisibilityIndex = - getTargetIndexWithMethodReferenceNameOrThrow("setVisibility") + val setVisibilityIndex = SearchBarFingerprint.indexOfVisibilityInstruction(this) val setVisibilityInstruction = getInstruction(setVisibilityIndex) diff --git a/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/HistoryMenuItemFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/HistoryMenuItemFingerprint.kt index bbf6b50ced..b4a405384c 100644 --- a/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/HistoryMenuItemFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/HistoryMenuItemFingerprint.kt @@ -3,7 +3,7 @@ package app.revanced.patches.music.general.components.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.HistoryMenuItem -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -16,7 +16,7 @@ internal object HistoryMenuItemFingerprint : MethodFingerprint( Opcode.RETURN_VOID ), customFingerprint = { methodDef, classDef -> - methodDef.containsWideLiteralInstructionIndex(HistoryMenuItem) + methodDef.containsWideLiteralInstructionValue(HistoryMenuItem) && classDef.methods.count() == 5 } ) diff --git a/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/HistoryMenuItemOfflineTabFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/HistoryMenuItemOfflineTabFingerprint.kt index e875e2be9a..1453be5ea8 100644 --- a/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/HistoryMenuItemOfflineTabFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/HistoryMenuItemOfflineTabFingerprint.kt @@ -4,7 +4,7 @@ import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.HistoryMenuItem import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.OfflineSettingsMenuItem -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -17,7 +17,7 @@ internal object HistoryMenuItemOfflineTabFingerprint : MethodFingerprint( Opcode.RETURN_VOID ), customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(HistoryMenuItem) - && methodDef.containsWideLiteralInstructionIndex(OfflineSettingsMenuItem) + methodDef.containsWideLiteralInstructionValue(HistoryMenuItem) + && methodDef.containsWideLiteralInstructionValue(OfflineSettingsMenuItem) } ) diff --git a/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/SearchBarFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/SearchBarFingerprint.kt index fbcd0ec913..3ba4a400b6 100644 --- a/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/SearchBarFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/general/components/fingerprints/SearchBarFingerprint.kt @@ -1,8 +1,22 @@ package app.revanced.patches.music.general.components.fingerprints -import app.revanced.util.fingerprint.MethodReferenceNameFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.music.general.components.fingerprints.SearchBarFingerprint.indexOfVisibilityInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversed +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.reference.MethodReference -object SearchBarFingerprint : MethodReferenceNameFingerprint( +object SearchBarFingerprint : MethodFingerprint( returnType = "V", - reference = { "setVisibility" } -) \ No newline at end of file + customFingerprint = { methodDef, _ -> + indexOfVisibilityInstruction(methodDef) >= 0 + } +) { + fun indexOfVisibilityInstruction(methodDef: Method) = + methodDef.indexOfFirstInstructionReversed { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setVisibility" + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/general/oldstylelibraryshelf/OldStyleLibraryShelfPatch.kt b/src/main/kotlin/app/revanced/patches/music/general/oldstylelibraryshelf/OldStyleLibraryShelfPatch.kt index de86290522..8201e07012 100644 --- a/src/main/kotlin/app/revanced/patches/music/general/oldstylelibraryshelf/OldStyleLibraryShelfPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/general/oldstylelibraryshelf/OldStyleLibraryShelfPatch.kt @@ -8,8 +8,8 @@ import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKA import app.revanced.patches.music.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.SettingsPatch -import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndexReversedOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstStringInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -27,8 +27,9 @@ object OldStyleLibraryShelfPatch : BaseBytecodePatch( BrowseIdFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val stringIndex = getStringInstructionIndex("FEmusic_offline") - val targetIndex = getTargetIndexReversedOrThrow(stringIndex, Opcode.IGET_OBJECT) + val stringIndex = indexOfFirstStringInstructionOrThrow("FEmusic_offline") + val targetIndex = + indexOfFirstInstructionReversedOrThrow(stringIndex, Opcode.IGET_OBJECT) val targetRegister = getInstruction(targetIndex).registerA addInstructions( diff --git a/src/main/kotlin/app/revanced/patches/music/general/redirection/DislikeRedirectionPatch.kt b/src/main/kotlin/app/revanced/patches/music/general/redirection/DislikeRedirectionPatch.kt index 703c0072b3..fca2cf297b 100644 --- a/src/main/kotlin/app/revanced/patches/music/general/redirection/DislikeRedirectionPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/general/redirection/DislikeRedirectionPatch.kt @@ -11,11 +11,11 @@ import app.revanced.patches.music.utils.fingerprints.PendingIntentReceiverFinger import app.revanced.patches.music.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.SettingsPatch -import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.getTargetIndexWithReferenceOrThrow +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 app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -41,13 +41,14 @@ object DislikeRedirectionPatch : BaseBytecodePatch( PendingIntentReceiverFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val startIndex = getStringInstructionIndex("YTM Dislike") + val startIndex = indexOfFirstStringInstructionOrThrow("YTM Dislike") val onClickRelayIndex = - getTargetIndexReversedOrThrow(startIndex, Opcode.INVOKE_VIRTUAL) + indexOfFirstInstructionReversedOrThrow(startIndex, Opcode.INVOKE_VIRTUAL) val onClickRelayMethod = getWalkerMethod(context, onClickRelayIndex) onClickRelayMethod.apply { - val onClickMethodIndex = getTargetIndexReversedOrThrow(Opcode.INVOKE_DIRECT) + val onClickMethodIndex = + indexOfFirstInstructionReversedOrThrow(Opcode.INVOKE_DIRECT) val onClickMethod = getWalkerMethod(context, onClickMethodIndex) onClickMethod.apply { @@ -70,7 +71,9 @@ object DislikeRedirectionPatch : BaseBytecodePatch( DislikeButtonOnClickListenerFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val onClickIndex = getTargetIndexWithReferenceOrThrow(onClickReference.toString()) + val onClickIndex = indexOfFirstInstructionOrThrow { + getReference()?.toString() == onClickReference.toString() + } injectCall(onClickIndex) } } @@ -84,7 +87,7 @@ object DislikeRedirectionPatch : BaseBytecodePatch( } private fun MutableMethod.injectCall(onClickIndex: Int) { - val targetIndex = getTargetIndexReversedOrThrow(onClickIndex, Opcode.IF_EQZ) + val targetIndex = indexOfFirstInstructionReversedOrThrow(onClickIndex, Opcode.IF_EQZ) val insertRegister = getInstruction(targetIndex).registerA addInstructionsWithLabels( diff --git a/src/main/kotlin/app/revanced/patches/music/general/redirection/fingerprints/DislikeButtonOnClickListenerFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/general/redirection/fingerprints/DislikeButtonOnClickListenerFingerprint.kt index a405b72046..95a496b57b 100644 --- a/src/main/kotlin/app/revanced/patches/music/general/redirection/fingerprints/DislikeButtonOnClickListenerFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/general/redirection/fingerprints/DislikeButtonOnClickListenerFingerprint.kt @@ -2,7 +2,7 @@ package app.revanced.patches.music.general.redirection.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object DislikeButtonOnClickListenerFingerprint : MethodFingerprint( @@ -10,7 +10,7 @@ internal object DislikeButtonOnClickListenerFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("Landroid/view/View;"), customFingerprint = handler@{ methodDef, _ -> - if (!methodDef.containsWideLiteralInstructionIndex(53465)) + if (!methodDef.containsWideLiteralInstructionValue(53465)) return@handler false methodDef.name == "onClick" diff --git a/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt index e206022ddb..6ce8f15352 100644 --- a/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt @@ -79,7 +79,7 @@ object CustomBrandingIconPatch : BaseResourcePatch( private val splashIconResourceGroups = largeDrawableDirectories.getResourceGroup(splashIconResourceFileNames) - private val AppIcon = stringPatchOption( + val AppIcon = stringPatchOption( key = "AppIcon", default = DEFAULT_ICON, values = availableIcon, @@ -198,7 +198,11 @@ object CustomBrandingIconPatch : BaseResourcePatch( if (oldSplashIconNotExists) { splashIconResourceGroups.let { resourceGroups -> resourceGroups.forEach { - context.copyResources("$youtubeMusicIconResourcePath/splash", it, createDirectoryIfNotExist = true) + context.copyResources( + "$youtubeMusicIconResourcePath/splash", + it, + createDirectoryIfNotExist = true + ) } } } diff --git a/src/main/kotlin/app/revanced/patches/music/layout/visual/VisualPreferencesIconsPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/visual/VisualPreferencesIconsPatch.kt new file mode 100644 index 0000000000..019c90a9b6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/visual/VisualPreferencesIconsPatch.kt @@ -0,0 +1,147 @@ +package app.revanced.patches.music.layout.visual + +import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption +import app.revanced.patches.music.layout.branding.icon.CustomBrandingIconPatch +import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.music.utils.settings.ResourceUtils.SETTINGS_HEADER_PATH +import app.revanced.patches.music.utils.settings.SettingsPatch +import app.revanced.util.ResourceGroup +import app.revanced.util.copyResources +import app.revanced.util.doRecursively +import app.revanced.util.patch.BaseResourcePatch +import app.revanced.util.underBarOrThrow +import org.w3c.dom.Element +import java.io.Closeable + +@Suppress("DEPRECATION", "unused") +object VisualPreferencesIconsPatch : BaseResourcePatch( + name = "Visual preferences icons for YouTube Music", + description = "Adds icons to specific preferences in the settings.", + dependencies = setOf(SettingsPatch::class), + compatiblePackages = COMPATIBLE_PACKAGE +), Closeable { + private const val DEFAULT_ICON = "extension" + + private val RVXSettingsMenuIcon = stringPatchOption( + key = "RVXSettingsMenuIcon", + default = DEFAULT_ICON, + values = mapOf( + "Custom branding icon" to "custom_branding_icon", + "Extension" to DEFAULT_ICON, + "Gear" to "gear", + "ReVanced" to "revanced", + "ReVanced Colored" to "revanced_colored", + ), + title = "RVX settings menu icon", + description = "The icon for the RVX settings menu.", + required = true + ) + + private lateinit var context: ResourceContext + + override fun execute(context: ResourceContext) { + this.context = context + + // Check patch options first. + val selectedIconType = RVXSettingsMenuIcon + .underBarOrThrow() + + val customBrandingIconType = CustomBrandingIconPatch.AppIcon + .underBarOrThrow() + + // region copy shared resources. + + arrayOf( + ResourceGroup( + "drawable", + *preferenceKey.map { it + "_icon.xml" }.toTypedArray() + ), + ).forEach { resourceGroup -> + context.copyResources("music/visual/shared", resourceGroup) + } + + // endregion. + + // region copy RVX settings menu icon. + + val iconPath = when (selectedIconType) { + "custom_branding_icon" -> "music/branding/$customBrandingIconType/settings" + else -> "music/visual/icons/$selectedIconType" + } + val resourceGroup = ResourceGroup( + "drawable", + "revanced_extended_settings_icon.xml" + ) + + try { + context.copyResources(iconPath, resourceGroup) + } catch (_: Exception) { + // Ignore if resource copy fails + } + + // endregion. + } + + override fun close() { + + // region set visual preferences icon. + + context.xmlEditor[SETTINGS_HEADER_PATH].use { editor -> + editor.file.doRecursively loop@{ node -> + if (node !is Element) return@loop + + node.getAttributeNode("android:key") + ?.textContent + ?.removePrefix("@string/") + ?.let { title -> + val drawableName = when (title) { + in preferenceKey -> title + "_icon" + else -> null + } + + drawableName?.let { + node.setAttribute("android:icon", "@drawable/$it") + } + } + } + } + + // endregion. + + } + + // region preference key and icon. + + private val preferenceKey = setOf( + // YouTube settings. + "pref_key_parent_tools", + "settings_header_general", + "settings_header_playback", + "settings_header_data_saving", + "settings_header_downloads_and_storage", + "settings_header_notifications", + "settings_header_privacy_and_location", + "settings_header_recommendations", + "settings_header_paid_memberships", + "settings_header_about_youtube_music", + + // RVX settings. + "revanced_extended_settings", + + "revanced_preference_screen_account", + "revanced_preference_screen_action_bar", + "revanced_preference_screen_ads", + "revanced_preference_screen_flyout", + "revanced_preference_screen_general", + "revanced_preference_screen_navigation", + "revanced_preference_screen_player", + "revanced_preference_screen_video", + "revanced_preference_screen_ryd", + "revanced_preference_screen_sb", + "revanced_preference_screen_misc", + ) + + // endregion. + +} diff --git a/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt index 8e95694bf9..ed4e79c7fe 100644 --- a/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt @@ -10,15 +10,16 @@ import app.revanced.patches.music.misc.backgroundplayback.fingerprints.KidsBackg import app.revanced.patches.music.misc.backgroundplayback.fingerprints.MusicBrowserServiceFingerprint import app.revanced.patches.music.misc.backgroundplayback.fingerprints.PodCastConfigFingerprint import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE -import app.revanced.util.getStartsWithStringInstructionIndex -import app.revanced.util.getStringInstructionIndex +import app.revanced.util.getReference import app.revanced.util.getWalkerMethod +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstStringInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction -import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Suppress("unused") object BackgroundPlaybackPatch : BaseBytecodePatch( @@ -51,26 +52,20 @@ object BackgroundPlaybackPatch : BaseBytecodePatch( // don't play music video MusicBrowserServiceFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = - getStartsWithStringInstructionIndex("MBS: Return empty root for client: %s") - - for (index in targetIndex downTo 0) { - if (getInstruction(index).opcode != Opcode.INVOKE_VIRTUAL) continue - - val targetReference = getInstruction(index).reference - - if (!targetReference.toString().endsWith("()Z")) continue - - val walkerMethod = getWalkerMethod(context, index) - - walkerMethod.addInstructions( - 0, """ - const/4 v0, 0x1 - return v0 - """ - ) - break + val stringIndex = MusicBrowserServiceFingerprint.indexOfMBSInstruction(this) + val targetIndex = indexOfFirstInstructionReversedOrThrow(stringIndex) { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL && + reference?.returnType == "Z" && + reference.parameterTypes.size == 0 } + + getWalkerMethod(context, targetIndex).addInstructions( + 0, """ + const/4 v0, 0x1 + return v0 + """ + ) } } @@ -97,7 +92,8 @@ object BackgroundPlaybackPatch : BaseBytecodePatch( } dataSavingSettingsFragmentFingerprintResult!!.mutableMethod.apply { - val insertIndex = getStringInstructionIndex("pref_key_dont_play_nma_video") + 4 + val insertIndex = + indexOfFirstStringInstructionOrThrow("pref_key_dont_play_nma_video") + 4 val targetRegister = getInstruction(insertIndex).registerD addInstruction( diff --git a/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/fingerprints/MusicBrowserServiceFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/fingerprints/MusicBrowserServiceFingerprint.kt index df191491c3..2823a0079e 100644 --- a/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/fingerprints/MusicBrowserServiceFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/fingerprints/MusicBrowserServiceFingerprint.kt @@ -2,8 +2,13 @@ package app.revanced.patches.music.misc.backgroundplayback.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.util.getStartsWithStringInstructionIndex +import app.revanced.patches.music.misc.backgroundplayback.fingerprints.MusicBrowserServiceFingerprint.indexOfMBSInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction 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.StringReference internal object MusicBrowserServiceFingerprint : MethodFingerprint( returnType = "L", @@ -13,6 +18,12 @@ internal object MusicBrowserServiceFingerprint : MethodFingerprint( if (!methodDef.definingClass.endsWith("/MusicBrowserService;")) return@custom false - methodDef.getStartsWithStringInstructionIndex("MBS: Return empty root for client: %s") > 0 + indexOfMBSInstruction(methodDef) >= 0 } -) \ No newline at end of file +) { + fun indexOfMBSInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + opcode == Opcode.CONST_STRING && + getReference()?.string?.startsWith("MBS: Return empty root for client: %s") == true + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/share/ShareSheetPatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/share/ShareSheetPatch.kt index 204d168a0e..8a3b9b0a57 100644 --- a/src/main/kotlin/app/revanced/patches/music/misc/share/ShareSheetPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/misc/share/ShareSheetPatch.kt @@ -12,8 +12,8 @@ import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.BottomS import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.SettingsPatch import app.revanced.patches.shared.litho.LithoFilterPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -40,8 +40,8 @@ object ShareSheetPatch : BaseBytecodePatch( override fun execute(context: BytecodeContext) { BottomSheetRecyclerViewFingerprint.resultOrThrow().mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(BottomSheetRecyclerView) - val targetIndex = getTargetIndexOrThrow(constIndex, Opcode.CHECK_CAST) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(BottomSheetRecyclerView) + val targetIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST) val targetRegister = getInstruction(targetIndex).registerA addInstruction( diff --git a/src/main/kotlin/app/revanced/patches/music/misc/splash/CairoSplashAnimationPatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/splash/CairoSplashAnimationPatch.kt index a5df7926a4..5b345a1763 100644 --- a/src/main/kotlin/app/revanced/patches/music/misc/splash/CairoSplashAnimationPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/misc/splash/CairoSplashAnimationPatch.kt @@ -1,25 +1,41 @@ package app.revanced.patches.music.misc.splash import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.music.misc.splash.fingerprints.CairoSplashAnimationConfigFingerprint import app.revanced.patches.music.utils.integrations.Constants.MISC_PATH +import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch +import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MainActivityLaunchAnimation import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.SettingsPatch -import app.revanced.util.literalInstructionBooleanHook +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Patch( name = "Disable Cairo splash animation", description = "Adds an option to disable Cairo splash animation.", - dependencies = [SettingsPatch::class], + dependencies = [ + SettingsPatch::class, + SharedResourceIdPatch::class + ], compatiblePackages = [ CompatiblePackage( "com.google.android.apps.youtube.music", [ "7.06.54", - "7.17.51", + "7.16.53", ] ) ] @@ -28,22 +44,57 @@ import app.revanced.util.literalInstructionBooleanHook object CairoSplashAnimationPatch : BytecodePatch( setOf(CairoSplashAnimationConfigFingerprint) ) { + private const val INTEGRATIONS_METHOD_DESCRIPTOR = + "$MISC_PATH/CairoSplashAnimationPatch;->disableCairoSplashAnimation(Z)Z" + override fun execute(context: BytecodeContext) { - CairoSplashAnimationConfigFingerprint.result?.let { - CairoSplashAnimationConfigFingerprint.literalInstructionBooleanHook( + if (!SettingsPatch.upward0706) { + println("WARNING: This patch is not supported in this version. Use YouTube Music 7.06.54 or later.") + return + } else if (!SettingsPatch.upward0720) { + CairoSplashAnimationConfigFingerprint.injectLiteralInstructionBooleanCall( 45635386, - "$MISC_PATH/CairoSplashAnimationPatch;->disableCairoSplashAnimation(Z)Z" - ) - - SettingsPatch.addSwitchPreference( - CategoryType.MISC, - "revanced_disable_cairo_splash_animation", - "false" + INTEGRATIONS_METHOD_DESCRIPTOR ) + } else { + CairoSplashAnimationConfigFingerprint.resultOrThrow().mutableMethod.apply { + val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow( + MainActivityLaunchAnimation + ) + val insertIndex = indexOfFirstInstructionReversedOrThrow(literalIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setContentView" + } + 1 + val viewStubFindViewByIdIndex = indexOfFirstInstructionOrThrow(literalIndex) { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL && + reference?.name == "findViewById" && + reference.definingClass != "Landroid/view/View;" + } + val freeRegister = + getInstruction(viewStubFindViewByIdIndex).registerD + val jumpIndex = indexOfFirstInstructionReversedOrThrow( + viewStubFindViewByIdIndex, + Opcode.IGET_OBJECT + ) + addInstructionsWithLabels( + insertIndex, """ + const/4 v$freeRegister, 0x1 + invoke-static {v$freeRegister}, $INTEGRATIONS_METHOD_DESCRIPTOR + move-result v$freeRegister + if-eqz v$freeRegister, :skip + """, ExternalLabel("skip", getInstruction(jumpIndex)) + ) + } } - ?: println("WARNING: This patch is not supported in this version. Use YouTube Music 7.06.54 or later.") + + SettingsPatch.addSwitchPreference( + CategoryType.MISC, + "revanced_disable_cairo_splash_animation", + "false" + ) } } diff --git a/src/main/kotlin/app/revanced/patches/music/misc/splash/fingerprints/CairoSplashAnimationConfigFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/splash/fingerprints/CairoSplashAnimationConfigFingerprint.kt index 54bf6c3e73..1050b4f562 100644 --- a/src/main/kotlin/app/revanced/patches/music/misc/splash/fingerprints/CairoSplashAnimationConfigFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/misc/splash/fingerprints/CairoSplashAnimationConfigFingerprint.kt @@ -1,10 +1,25 @@ package app.revanced.patches.music.misc.splash.fingerprints -import app.revanced.util.fingerprint.LiteralValueFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MainActivityLaunchAnimation +import app.revanced.patches.music.utils.settings.SettingsPatch +import app.revanced.util.indexOfFirstWideLiteralInstructionValue /** * This fingerprint is compatible with YouTube Music v7.06.53+ */ -internal object CairoSplashAnimationConfigFingerprint : LiteralValueFingerprint( - literalSupplier = { 45635386 } +internal object CairoSplashAnimationConfigFingerprint : MethodFingerprint( + returnType = "V", + customFingerprint = handler@{ methodDef, _ -> + if (methodDef.definingClass != "Lcom/google/android/apps/youtube/music/activities/MusicActivity;") + return@handler false + if (methodDef.name != "onCreate") + return@handler false + + if (SettingsPatch.upward0720) { + methodDef.indexOfFirstWideLiteralInstructionValue(MainActivityLaunchAnimation) >= 0 + } else { + methodDef.indexOfFirstWideLiteralInstructionValue(45635386) >= 0 + } + } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/navigation/components/NavigationBarComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/music/navigation/components/NavigationBarComponentsPatch.kt index adcd24adf3..a2e16a7245 100644 --- a/src/main/kotlin/app/revanced/patches/music/navigation/components/NavigationBarComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/navigation/components/NavigationBarComponentsPatch.kt @@ -11,13 +11,12 @@ import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKA import app.revanced.patches.music.utils.integrations.Constants.NAVIGATION_CLASS_DESCRIPTOR import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.ColorGrey +import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.Text1 import app.revanced.patches.music.utils.settings.CategoryType import app.revanced.patches.music.utils.settings.SettingsPatch import app.revanced.util.getReference -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.getWideLiteralInstructionIndex import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -50,7 +49,7 @@ object NavigationBarComponentsPatch : BaseBytecodePatch( * Enable black navigation bar */ TabLayoutFingerprint.resultOrThrow().mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(ColorGrey) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(ColorGrey) val insertIndex = indexOfFirstInstructionOrThrow(constIndex) { opcode == Opcode.INVOKE_VIRTUAL && getReference()?.name == "setBackgroundColor" @@ -70,8 +69,9 @@ object NavigationBarComponentsPatch : BaseBytecodePatch( */ TabLayoutTextFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(SharedResourceIdPatch.Text1) - val targetIndex = getTargetIndexOrThrow(constIndex, Opcode.CHECK_CAST) + val constIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(Text1) + val targetIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST) val targetParameter = getInstruction(targetIndex).reference val targetRegister = getInstruction(targetIndex).registerA @@ -105,9 +105,12 @@ object NavigationBarComponentsPatch : BaseBytecodePatch( it.mutableMethod.apply { val enumIndex = it.scanResult.patternScanResult!!.startIndex + 3 val enumRegister = getInstruction(enumIndex).registerA - val insertEnumIndex = getTargetIndexOrThrow(Opcode.AND_INT_LIT8) - 2 + val insertEnumIndex = indexOfFirstInstructionOrThrow(Opcode.AND_INT_LIT8) - 2 - val pivotTabIndex = getTargetIndexWithMethodReferenceNameOrThrow("getVisibility") + val pivotTabIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "getVisibility" + } val pivotTabRegister = getInstruction(pivotTabIndex).registerC addInstruction( diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt index 8bc50eed10..1cead791fd 100644 --- a/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt @@ -13,6 +13,8 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.music.player.components.fingerprints.AudioVideoSwitchToggleFingerprint +import app.revanced.patches.music.player.components.fingerprints.EngagementPanelHeightFingerprint +import app.revanced.patches.music.player.components.fingerprints.EngagementPanelHeightParentFingerprint import app.revanced.patches.music.player.components.fingerprints.HandleSearchRenderedFingerprint import app.revanced.patches.music.player.components.fingerprints.HandleSignInEventFingerprint import app.revanced.patches.music.player.components.fingerprints.InteractionLoggingEnumFingerprint @@ -59,17 +61,16 @@ import app.revanced.patches.music.utils.settings.SettingsPatch import app.revanced.patches.music.utils.videotype.VideoTypeHookPatch import app.revanced.patches.shared.litho.LithoFilterPatch import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT +import app.revanced.util.alsoResolve +import app.revanced.util.findMethodOrThrow import app.revanced.util.getReference -import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndex -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.getTargetIndexWithFieldReferenceTypeOrThrow import app.revanced.util.getWalkerMethod -import app.revanced.util.getWideLiteralInstructionIndex import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.literalInstructionBooleanHook -import app.revanced.util.literalInstructionViewHook +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstStringInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall +import app.revanced.util.injectLiteralInstructionViewCall import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import app.revanced.util.transformFields @@ -105,6 +106,7 @@ object PlayerComponentsPatch : BaseBytecodePatch( compatiblePackages = COMPATIBLE_PACKAGE, fingerprints = setOf( AudioVideoSwitchToggleFingerprint, + EngagementPanelHeightParentFingerprint, HandleSearchRenderedFingerprint, InteractionLoggingEnumFingerprint, MinimizedPlayerFingerprint, @@ -145,8 +147,8 @@ object PlayerComponentsPatch : BaseBytecodePatch( PlayerViewPager to "disablePlayerGesture" ).forEach { (literal, methodName) -> val viewPagerReference = playerViewPagerConstructorMethod.let { - val constIndex = it.getWideLiteralInstructionIndex(literal) - val targetIndex = it.getTargetIndexOrThrow(constIndex, Opcode.IPUT_OBJECT) + val constIndex = it.indexOfFirstWideLiteralInstructionValueOrThrow(literal) + val targetIndex = it.indexOfFirstInstructionOrThrow(constIndex, Opcode.IPUT_OBJECT) it.getInstruction(targetIndex).reference.toString() } @@ -156,7 +158,8 @@ object PlayerComponentsPatch : BaseBytecodePatch( && getReference()?.toString() == viewPagerReference } val insertRegister = getInstruction(insertIndex).registerA - val jumpIndex = getTargetIndex(insertIndex, Opcode.INVOKE_VIRTUAL) + 1 + val jumpIndex = + indexOfFirstInstructionOrThrow(insertIndex, Opcode.INVOKE_VIRTUAL) + 1 addInstructionsWithLabels( insertIndex, """ @@ -198,8 +201,8 @@ object PlayerComponentsPatch : BaseBytecodePatch( val relativeIndex = it.scanResult.patternScanResult!!.endIndex + 1 val invokeVirtualIndex = - getTargetIndexOrThrow(relativeIndex, Opcode.INVOKE_VIRTUAL) - val iGetIndex = getTargetIndexOrThrow(relativeIndex, Opcode.IGET) + indexOfFirstInstructionOrThrow(relativeIndex, Opcode.INVOKE_VIRTUAL) + val iGetIndex = indexOfFirstInstructionOrThrow(relativeIndex, Opcode.IGET) colorMathPlayerInvokeVirtualReference = getInstruction(invokeVirtualIndex).reference @@ -207,11 +210,11 @@ object PlayerComponentsPatch : BaseBytecodePatch( getInstruction(iGetIndex).reference // black player background - val invokeDirectIndex = getTargetIndexOrThrow(Opcode.INVOKE_DIRECT) + val invokeDirectIndex = indexOfFirstInstructionOrThrow(Opcode.INVOKE_DIRECT) val targetMethod = getWalkerMethod(context, invokeDirectIndex) targetMethod.apply { - val insertIndex = getTargetIndexOrThrow(0, Opcode.IF_NE) + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.IF_NE) addInstructions( insertIndex, """ @@ -225,8 +228,8 @@ object PlayerComponentsPatch : BaseBytecodePatch( } parentResult.mutableMethod.apply { - val colorGreyIndex = getWideLiteralInstructionIndex(ColorGrey) - val iPutIndex = getTargetIndexOrThrow(colorGreyIndex, Opcode.IPUT) + val colorGreyIndex = indexOfFirstWideLiteralInstructionValueOrThrow(ColorGrey) + val iPutIndex = indexOfFirstInstructionOrThrow(colorGreyIndex, Opcode.IPUT) colorMathPlayerIPutReference = getInstruction(iPutIndex).reference @@ -240,7 +243,8 @@ object PlayerComponentsPatch : BaseBytecodePatch( mutableMethod.apply { val freeRegister = implementation!!.registerCount - parameters.size - 3 - val invokeDirectIndex = getTargetIndexReversedOrThrow(Opcode.INVOKE_DIRECT) + val invokeDirectIndex = + indexOfFirstInstructionReversedOrThrow(Opcode.INVOKE_DIRECT) val invokeDirectReference = getInstruction(invokeDirectIndex).reference @@ -401,9 +405,9 @@ object PlayerComponentsPatch : BaseBytecodePatch( reversed: Boolean ): Reference { val targetIndex = if (reversed) - getTargetIndexReversedOrThrow(swipeToDismissWidgetIndex, opcode) + indexOfFirstInstructionReversedOrThrow(swipeToDismissWidgetIndex, opcode) else - getTargetIndexOrThrow(swipeToDismissWidgetIndex, opcode) + indexOfFirstInstructionOrThrow(swipeToDismissWidgetIndex, opcode) return getInstruction(targetIndex).reference } @@ -411,7 +415,7 @@ object PlayerComponentsPatch : BaseBytecodePatch( if (!SettingsPatch.upward0642) { SwipeToCloseFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = implementation!!.instructions.size - 1 + val insertIndex = implementation!!.instructions.lastIndex val targetRegister = getInstruction(insertIndex).registerA @@ -430,8 +434,9 @@ object PlayerComponentsPatch : BaseBytecodePatch( InteractionLoggingEnumFingerprint.resultOrThrow().let { it.mutableMethod.apply { val stringIndex = - getStringInstructionIndex("INTERACTION_LOGGING_GESTURE_TYPE_SWIPE") - val sPutObjectIndex = getTargetIndexOrThrow(stringIndex, Opcode.SPUT_OBJECT) + indexOfFirstStringInstructionOrThrow("INTERACTION_LOGGING_GESTURE_TYPE_SWIPE") + val sPutObjectIndex = + indexOfFirstInstructionOrThrow(stringIndex, Opcode.SPUT_OBJECT) swipeToDismissSGetObjectReference = getInstruction(sPutObjectIndex).reference @@ -440,7 +445,8 @@ object PlayerComponentsPatch : BaseBytecodePatch( MusicActivityWidgetFingerprint.resultOrThrow().let { it.mutableMethod.apply { - swipeToDismissWidgetIndex = getWideLiteralInstructionIndex(79500) + swipeToDismissWidgetIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(79500) swipeToDismissIGetObjectReference = getSwipeToDismissReference(Opcode.IGET_OBJECT, true) @@ -468,8 +474,9 @@ object PlayerComponentsPatch : BaseBytecodePatch( it.getWalkerMethod(context, it.scanResult.patternScanResult!!.startIndex) dismissBehaviorMethod.apply { - val insertIndex = - getTargetIndexWithFieldReferenceTypeOrThrow("Ljava/util/concurrent/atomic/AtomicBoolean;") + val insertIndex = indexOfFirstInstructionOrThrow { + getReference()?.type == "Ljava/util/concurrent/atomic/AtomicBoolean;" + } val primaryRegister = getInstruction(insertIndex).registerB val secondaryRegister = primaryRegister + 1 @@ -528,15 +535,12 @@ object PlayerComponentsPatch : BaseBytecodePatch( it.mutableClass.methods.find { method -> method.parameters == listOf("Landroid/view/View;", "I") }?.apply { - val bottomSheetBehaviorIndex = - implementation!!.instructions.indexOfFirst { instruction -> - instruction.opcode == Opcode.INVOKE_VIRTUAL - && instruction.getReference()?.definingClass == "Lcom/google/android/material/bottomsheet/BottomSheetBehavior;" - && instruction.getReference()?.parameterTypes?.first() == "Z" - } - if (bottomSheetBehaviorIndex < 0) - throw PatchException("Could not find bottomSheetBehaviorIndex") - + val bottomSheetBehaviorIndex = indexOfFirstInstructionOrThrow { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL + && reference?.definingClass == "Lcom/google/android/material/bottomsheet/BottomSheetBehavior;" + && reference.parameterTypes.first() == "Z" + } val freeRegister = getInstruction(bottomSheetBehaviorIndex).registerD @@ -592,7 +596,7 @@ object PlayerComponentsPatch : BaseBytecodePatch( SwitchToggleColorFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val invokeDirectIndex = getTargetIndexOrThrow(Opcode.INVOKE_DIRECT) + val invokeDirectIndex = indexOfFirstInstructionOrThrow(Opcode.INVOKE_DIRECT) val walkerMethod = getWalkerMethod(context, invokeDirectIndex) walkerMethod.addInstructions( @@ -624,8 +628,8 @@ object PlayerComponentsPatch : BaseBytecodePatch( // region patch for hide audio video switch toggle AudioVideoSwitchToggleFingerprint.resultOrThrow().mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(AudioVideoSwitchToggle) - val viewIndex = getTargetIndexOrThrow(constIndex, Opcode.MOVE_RESULT_OBJECT) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(AudioVideoSwitchToggle) + val viewIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.MOVE_RESULT_OBJECT) val viewRegister = getInstruction(viewIndex).registerA addInstruction( @@ -667,7 +671,7 @@ object PlayerComponentsPatch : BaseBytecodePatch( DarkBackground, TapBloomView ).forEach { literal -> - QuickSeekOverlayFingerprint.literalInstructionViewHook( + QuickSeekOverlayFingerprint.injectLiteralInstructionViewCall( literal, smaliInstruction ) @@ -742,17 +746,20 @@ object PlayerComponentsPatch : BaseBytecodePatch( it.mutableMethod.apply { rememberShuffleStateObjectClass = definingClass - val constIndex = getWideLiteralInstructionIndex(45468) - val iGetObjectIndex = getTargetIndexOrThrow(constIndex, Opcode.IGET_OBJECT) - val checkCastIndex = getTargetIndexOrThrow(iGetObjectIndex, Opcode.CHECK_CAST) - - val ordinalIndex = indexOfOrdinalInstruction(this) val imageViewIndex = indexOfImageViewInstruction(this) + val ordinalIndex = indexOfOrdinalInstruction(this) + + val invokeInterfaceIndex = + indexOfFirstInstructionReversedOrThrow(ordinalIndex, Opcode.INVOKE_INTERFACE) + val iGetObjectIndex = + indexOfFirstInstructionReversedOrThrow(invokeInterfaceIndex, Opcode.IGET_OBJECT) + val checkCastIndex = + indexOfFirstInstructionOrThrow(invokeInterfaceIndex, Opcode.CHECK_CAST) val iGetObjectReference = getInstruction(iGetObjectIndex).reference val invokeInterfaceReference = - getInstruction(iGetObjectIndex + 1).reference + getInstruction(invokeInterfaceIndex).reference val checkCastReference = getInstruction(checkCastIndex).reference val getOrdinalClassReference = @@ -771,7 +778,7 @@ object PlayerComponentsPatch : BaseBytecodePatch( """ rememberShuffleStateShuffleStateLabel += if (getInstruction(checkCastIndex + 1).opcode == Opcode.INVOKE_VIRTUAL) { - // YouTube Music 7.16.52+ + // YouTube Music 7.16.53+ """ invoke-virtual {v1}, $getOrdinalClassReference move-result-object v1 @@ -887,12 +894,84 @@ object PlayerComponentsPatch : BaseBytecodePatch( // region patch for restore old comments popup panels - OldEngagementPanelFingerprint.result?.let { - OldEngagementPanelFingerprint.literalInstructionBooleanHook( + var restoreOldCommentsPopupPanel = false + + if (SettingsPatch.upward0627 && !SettingsPatch.upward0718) { + OldEngagementPanelFingerprint.injectLiteralInstructionBooleanCall( 45427672, "$PLAYER_CLASS_DESCRIPTOR->restoreOldCommentsPopUpPanels(Z)Z" ) + restoreOldCommentsPopupPanel = true + } else if (SettingsPatch.upward0718) { + + // region disable player from being pushed to the top when opening a comment + + MppWatchWhileLayoutFingerprint.resultOrThrow().mutableMethod.apply { + val callableIndex = + MppWatchWhileLayoutFingerprint.indexOfCallableInstruction(this) + val insertIndex = + indexOfFirstInstructionReversedOrThrow(callableIndex, Opcode.NEW_INSTANCE) + val insertRegister = getInstruction(insertIndex).registerA + + addInstructionsWithLabels( + insertIndex, """ + invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->restoreOldCommentsPopUpPanels()Z + move-result v$insertRegister + if-eqz v$insertRegister, :restore + """, ExternalLabel("restore", getInstruction(callableIndex + 1)) + ) + } + + // endregion + + // region region limit the height of the engagement panel + EngagementPanelHeightFingerprint.alsoResolve( + context, EngagementPanelHeightParentFingerprint + ).let { + it.mutableMethod.apply { + val targetIndex = it.scanResult.patternScanResult!!.endIndex + val targetRegister = + getInstruction(targetIndex).registerA + + addInstructions( + targetIndex + 1, """ + invoke-static {v$targetRegister}, $PLAYER_CLASS_DESCRIPTOR->restoreOldCommentsPopUpPanels(Z)Z + move-result v$targetRegister + """ + ) + } + } + + MiniPlayerDefaultViewVisibilityFingerprint.resultOrThrow().let { + it.mutableClass.methods.find { method -> + method.parameters == listOf("Landroid/view/View;", "I") + }?.apply { + val targetIndex = indexOfFirstInstructionOrThrow { + val reference = getReference() + opcode == Opcode.INVOKE_INTERFACE + && reference?.returnType == "Z" + && reference.parameterTypes.size == 0 + } + 1 + val targetRegister = + getInstruction(targetIndex).registerA + + addInstructions( + targetIndex + 1, """ + invoke-static {v$targetRegister}, $PLAYER_CLASS_DESCRIPTOR->restoreOldCommentsPopUpPanels(Z)Z + move-result v$targetRegister + """ + ) + } ?: throw PatchException("Could not find targetMethod") + + } + + // endregion + + restoreOldCommentsPopupPanel = true + } + + if (restoreOldCommentsPopupPanel) { SettingsPatch.addSwitchPreference( CategoryType.PLAYER, "revanced_restore_old_comments_popup_panels", @@ -905,7 +984,7 @@ object PlayerComponentsPatch : BaseBytecodePatch( // region patch for restore old player background OldPlayerBackgroundFingerprint.result?.let { - OldPlayerBackgroundFingerprint.literalInstructionBooleanHook( + OldPlayerBackgroundFingerprint.injectLiteralInstructionBooleanCall( 45415319, "$PLAYER_CLASS_DESCRIPTOR->restoreOldPlayerBackground(Z)Z" ) @@ -922,7 +1001,7 @@ object PlayerComponentsPatch : BaseBytecodePatch( // region patch for restore old player layout OldPlayerLayoutFingerprint.result?.let { - OldPlayerLayoutFingerprint.literalInstructionBooleanHook( + OldPlayerLayoutFingerprint.injectLiteralInstructionBooleanCall( 45399578, "$PLAYER_CLASS_DESCRIPTOR->restoreOldPlayerLayout(Z)Z" ) @@ -943,11 +1022,14 @@ object PlayerComponentsPatch : BaseBytecodePatch( viewId: Long ) { val miniPlayerPlayPauseReplayButtonIndex = - getWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton) + indexOfFirstWideLiteralInstructionValueOrThrow(MiniPlayerPlayPauseReplayButton) val miniPlayerPlayPauseReplayButtonRegister = getInstruction(miniPlayerPlayPauseReplayButtonIndex).registerA val findViewByIdIndex = - getTargetIndexOrThrow(miniPlayerPlayPauseReplayButtonIndex, Opcode.INVOKE_VIRTUAL) + indexOfFirstInstructionOrThrow( + miniPlayerPlayPauseReplayButtonIndex, + Opcode.INVOKE_VIRTUAL + ) val parentViewRegister = getInstruction(findViewByIdIndex).registerC @@ -966,11 +1048,14 @@ object PlayerComponentsPatch : BaseBytecodePatch( viewId: Long ) { val miniPlayerPlayPauseReplayButtonIndex = - getWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton) + indexOfFirstWideLiteralInstructionValueOrThrow(MiniPlayerPlayPauseReplayButton) val constRegister = getInstruction(miniPlayerPlayPauseReplayButtonIndex).registerA val findViewByIdIndex = - getTargetIndexOrThrow(miniPlayerPlayPauseReplayButtonIndex, Opcode.INVOKE_VIRTUAL) + indexOfFirstInstructionOrThrow( + miniPlayerPlayPauseReplayButtonIndex, + Opcode.INVOKE_VIRTUAL + ) val findViewByIdRegister = getInstruction(findViewByIdIndex).registerC @@ -986,9 +1071,12 @@ object PlayerComponentsPatch : BaseBytecodePatch( private fun MutableMethod.setViewArray() { val miniPlayerPlayPauseReplayButtonIndex = - getWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton) + indexOfFirstWideLiteralInstructionValueOrThrow(MiniPlayerPlayPauseReplayButton) val invokeStaticIndex = - getTargetIndexOrThrow(miniPlayerPlayPauseReplayButtonIndex, Opcode.INVOKE_STATIC) + indexOfFirstInstructionOrThrow( + miniPlayerPlayPauseReplayButtonIndex, + Opcode.INVOKE_STATIC + ) val viewArrayRegister = getInstruction(invokeStaticIndex).registerC addInstructions( @@ -1005,21 +1093,18 @@ object PlayerComponentsPatch : BaseBytecodePatch( methodName: String, fieldName: String ) { - val startIndex = getStringInstructionIndex(intentString) - val onClickIndex = getTargetIndexReversedOrThrow(startIndex, Opcode.INVOKE_VIRTUAL) + val startIndex = indexOfFirstStringInstructionOrThrow(intentString) + val onClickIndex = indexOfFirstInstructionReversedOrThrow(startIndex, Opcode.INVOKE_VIRTUAL) val onClickReference = getInstruction(onClickIndex).reference val onClickReferenceDefiningClass = (onClickReference as MethodReference).definingClass - val onClickClass = - context.findClass(onClickReferenceDefiningClass)!!.mutableClass - - onClickClass.methods.find { method -> method.name == "" } - ?.apply { + context.findMethodOrThrow(onClickReferenceDefiningClass) + .apply { addInstruction( - implementation!!.instructions.size - 1, + implementation!!.instructions.lastIndex, "sput-object p0, $PLAYER_CLASS_DESCRIPTOR->$fieldName:$onClickReferenceDefiningClass" ) - } ?: throw PatchException("onClickClass not found!") + } PlayerPatchConstructorFingerprint.resultOrThrow().let { val mutableClass = it.mutableClass diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/EngagementPanelHeightFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/EngagementPanelHeightFingerprint.kt new file mode 100644 index 0000000000..85111f7052 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/EngagementPanelHeightFingerprint.kt @@ -0,0 +1,26 @@ +package app.revanced.patches.music.player.components.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +internal object EngagementPanelHeightFingerprint : MethodFingerprint( + returnType = "L", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + ), + parameters = emptyList(), + customFingerprint = custom@{ methodDef, _ -> + methodDef.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "booleanValue" + } >= 0 + } +) diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/EngagementPanelHeightParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/EngagementPanelHeightParentFingerprint.kt new file mode 100644 index 0000000000..334bbd4364 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/EngagementPanelHeightParentFingerprint.kt @@ -0,0 +1,28 @@ +package app.revanced.patches.music.player.components.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.indexOfFirstInstruction +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction + +internal object EngagementPanelHeightParentFingerprint : MethodFingerprint( + returnType = "L", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + opcodes = listOf(Opcode.NEW_ARRAY), + parameters = emptyList(), + customFingerprint = custom@{ methodDef, _ -> + if (methodDef.definingClass.startsWith("Lcom/")) { + return@custom false + } + if (methodDef.returnType == "Ljava/lang/Object;") { + return@custom false + } + + methodDef.indexOfFirstInstruction { + opcode == Opcode.CHECK_CAST && + (this as? ReferenceInstruction)?.reference?.toString() == "Lcom/google/android/libraries/youtube/engagementpanel/size/EngagementPanelSizeBehavior;" + } >= 0 + } +) diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MiniPlayerConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MiniPlayerConstructorFingerprint.kt index 3ac11537fd..57ec8f9759 100644 --- a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MiniPlayerConstructorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MiniPlayerConstructorFingerprint.kt @@ -3,13 +3,13 @@ package app.revanced.patches.music.player.components.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.ColorGrey import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MiniPlayerPlayPauseReplayButton -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue internal object MiniPlayerConstructorFingerprint : MethodFingerprint( returnType = "V", strings = listOf("sharedToggleMenuItemMutations"), customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(ColorGrey) - && methodDef.containsWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton) + methodDef.containsWideLiteralInstructionValue(ColorGrey) + && methodDef.containsWideLiteralInstructionValue(MiniPlayerPlayPauseReplayButton) } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MppWatchWhileLayoutFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MppWatchWhileLayoutFingerprint.kt index a7e57557d3..5da2c8242e 100644 --- a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MppWatchWhileLayoutFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MppWatchWhileLayoutFingerprint.kt @@ -1,16 +1,42 @@ package app.revanced.patches.music.player.components.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.music.player.components.fingerprints.MppWatchWhileLayoutFingerprint.indexOfCallableInstruction import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MiniPlayerPlayPauseReplayButton -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.patches.music.utils.settings.SettingsPatch +import app.revanced.util.containsWideLiteralInstructionValue +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.reference.MethodReference internal object MppWatchWhileLayoutFingerprint : MethodFingerprint( returnType = "V", opcodes = listOf(Opcode.NEW_ARRAY), - customFingerprint = { methodDef, _ -> - methodDef.definingClass.endsWith("/MppWatchWhileLayout;") - && methodDef.name == "onFinishInflate" - && methodDef.containsWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton) + customFingerprint = custom@{ methodDef, _ -> + if (!methodDef.definingClass.endsWith("/MppWatchWhileLayout;")) { + return@custom false + } + if (methodDef.name != "onFinishInflate") { + return@custom false + } + if (!methodDef.containsWideLiteralInstructionValue(MiniPlayerPlayPauseReplayButton)) { + return@custom false + } + if (!SettingsPatch.upward0718) { + return@custom true + } + + indexOfCallableInstruction(methodDef) >= 0 } -) +) { + fun indexOfCallableInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL && + reference?.returnType == "V" && + reference.parameterTypes.size == 1 && + reference.parameterTypes.firstOrNull() == "Ljava/util/concurrent/Callable;" + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MusicActivityWidgetFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MusicActivityWidgetFingerprint.kt index 0eb77a3748..32c5106e4a 100644 --- a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MusicActivityWidgetFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/MusicActivityWidgetFingerprint.kt @@ -1,13 +1,13 @@ package app.revanced.patches.music.player.components.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue internal object MusicActivityWidgetFingerprint : MethodFingerprint( customFingerprint = handler@{ methodDef, _ -> if (!methodDef.definingClass.endsWith("/MusicActivity;")) return@handler false - methodDef.containsWideLiteralInstructionIndex(79500) + methodDef.containsWideLiteralInstructionValue(79500) } ) diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/PlayerViewPagerConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/PlayerViewPagerConstructorFingerprint.kt index 9a2116941a..e1ad8bc6b1 100644 --- a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/PlayerViewPagerConstructorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/PlayerViewPagerConstructorFingerprint.kt @@ -4,14 +4,14 @@ import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MiniPlayerViewPager import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.PlayerViewPager -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object PlayerViewPagerConstructorFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(MiniPlayerViewPager) - && methodDef.containsWideLiteralInstructionIndex(PlayerViewPager) + methodDef.containsWideLiteralInstructionValue(MiniPlayerViewPager) + && methodDef.containsWideLiteralInstructionValue(PlayerViewPager) }, ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/QuickSeekOverlayFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/QuickSeekOverlayFingerprint.kt index 89714d3b24..1e2add9d40 100644 --- a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/QuickSeekOverlayFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/QuickSeekOverlayFingerprint.kt @@ -3,13 +3,13 @@ package app.revanced.patches.music.player.components.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.DarkBackground import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.TapBloomView -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue internal object QuickSeekOverlayFingerprint : MethodFingerprint( returnType = "V", parameters = emptyList(), customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(DarkBackground) - && methodDef.containsWideLiteralInstructionIndex(TapBloomView) + methodDef.containsWideLiteralInstructionValue(DarkBackground) + && methodDef.containsWideLiteralInstructionValue(TapBloomView) }, ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleClassReferenceFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleClassReferenceFingerprint.kt index 64f792f243..537f121058 100644 --- a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleClassReferenceFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleClassReferenceFingerprint.kt @@ -4,7 +4,8 @@ import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.music.player.components.fingerprints.ShuffleClassReferenceFingerprint.indexOfImageViewInstruction import app.revanced.patches.music.player.components.fingerprints.ShuffleClassReferenceFingerprint.indexOfOrdinalInstruction -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.YtFillArrowShuffle +import app.revanced.util.containsWideLiteralInstructionValue import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstruction import com.android.tools.smali.dexlib2.AccessFlags @@ -19,7 +20,7 @@ internal object ShuffleClassReferenceFingerprint : MethodFingerprint( parameters = emptyList(), strings = listOf("Unknown shuffle mode"), customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(45468) && + methodDef.containsWideLiteralInstructionValue(YtFillArrowShuffle) && indexOfOrdinalInstruction(methodDef) >= 0 && indexOfImageViewInstruction(methodDef) >= 0 } diff --git a/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt b/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt index 6b8abb737b..8836730d59 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt @@ -7,12 +7,11 @@ object Constants { Patch.CompatiblePackage( "com.google.android.apps.youtube.music", setOf( - "6.29.58", // This is the latest version that supports the 'Restore old player layout' setting. - "6.33.52", // This is the latest version with the legacy code of YouTube Music. + "6.20.51", // This is the latest version that supports Android 5.0 + "6.29.59", // This is the latest version that supports the 'Restore old player layout' setting. "6.42.55", // This is the latest version that supports Android 7.0 "6.51.53", // This is the latest version of YouTube Music 6.xx.xx - "7.16.53", // This was the latest version that was supported by the previous patch. - "7.17.51", // This is the latest version supported by the RVX patch. + "7.16.53", // This is the latest version supported by the RVX patch. ) ) ) diff --git a/src/main/kotlin/app/revanced/patches/music/utils/fix/accessibility/AccessibilityNodeInfoPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/fix/accessibility/AccessibilityNodeInfoPatch.kt deleted file mode 100644 index 5dd6b7a7b5..0000000000 --- a/src/main/kotlin/app/revanced/patches/music/utils/fix/accessibility/AccessibilityNodeInfoPatch.kt +++ /dev/null @@ -1,60 +0,0 @@ -package app.revanced.patches.music.utils.fix.accessibility - -import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patches.music.utils.fix.accessibility.fingerprints.TouchExplorationHoverEventFingerprint -import app.revanced.util.containsMethodReferenceNameInstructionIndex -import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction -import com.android.tools.smali.dexlib2.iface.reference.MethodReference - -object AccessibilityNodeInfoPatch : BytecodePatch( - setOf(TouchExplorationHoverEventFingerprint) -) { - override fun execute(context: BytecodeContext) { - - /** - * The "getTouchDelegateInfo" method has been implemented in YT Music v6.44.52. - * For some reason this method sometimes returns null, which throws [IllegalArgumentException]. - * This is considered unimplemented code, so remove all methods associated with it. - */ - TouchExplorationHoverEventFingerprint.result?.let { - it.mutableMethod.apply { - // Target instruction is invoke-static, but can also be invoke-virtual. - // Therefore, the opcode is not checked. - val touchExplorationHoverEventMethodIndex = - implementation!!.instructions.indexOfFirst { instruction -> - val reference = - ((instruction as? ReferenceInstruction)?.reference as? MethodReference) - ((instruction as? ReferenceInstruction)?.reference as? MethodReference)?.definingClass == definingClass - && reference?.returnType == "Z" - } - - // Doesn't raise an exception, even if the target instruction is not found in this method - val touchExplorationHoverEventMethodName = - if (touchExplorationHoverEventMethodIndex > -1) - (getInstruction(touchExplorationHoverEventMethodIndex).reference as MethodReference).name - else - "UNDEFINED" - - val methods = it.mutableClass.methods - - methods.find { method -> - method.name == "getTouchDelegateInfo" - }?.apply { - if (!containsMethodReferenceNameInstructionIndex("isEmpty")) { - arrayOf( - "getTouchDelegateInfo", - name, - touchExplorationHoverEventMethodName - ).forEach { methodName -> - methods.removeIf { method -> - method.name == methodName - } - } - } - } - } - } // If this method has not been added, there is no need to remove it, so it will not raise any exceptions. - } -} diff --git a/src/main/kotlin/app/revanced/patches/music/utils/fix/accessibility/fingerprints/TouchExplorationHoverEventFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/fix/accessibility/fingerprints/TouchExplorationHoverEventFingerprint.kt deleted file mode 100644 index 97920323e3..0000000000 --- a/src/main/kotlin/app/revanced/patches/music/utils/fix/accessibility/fingerprints/TouchExplorationHoverEventFingerprint.kt +++ /dev/null @@ -1,8 +0,0 @@ -package app.revanced.patches.music.utils.fix.accessibility.fingerprints - -import app.revanced.patcher.fingerprint.MethodFingerprint - -internal object TouchExplorationHoverEventFingerprint : MethodFingerprint( - returnType = "Z", - customFingerprint = { methodDef, _ -> methodDef.name == "onTouchExplorationHoverEvent" } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/fix/fileprovider/FileProviderPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/fix/fileprovider/FileProviderPatch.kt index 738aaab185..6b6212d465 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/fix/fileprovider/FileProviderPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/fix/fileprovider/FileProviderPatch.kt @@ -22,7 +22,7 @@ object FileProviderPatch : BytecodePatch( * For some reason, if the app gets "android.support.FILE_PROVIDER_PATHS", * the package name of YouTube is used, not the package name of the YT Music. * - * There is no issue in the stock YT Music, but this is an issue in the MicroG Build. + * There is no issue in the stock YT Music, but this is an issue in the GmsCore Build. * https://github.com/inotia00/ReVanced_Extended/issues/1830 * * To solve this issue, replace the package name of YouTube with YT Music's package name. @@ -31,10 +31,16 @@ object FileProviderPatch : BytecodePatch( it.mutableMethod.apply { addInstructionsWithLabels( 0, """ + const-string v0, "com.google.android.youtube.fileprovider" + invoke-static {p1, v0}, Ljava/util/Objects;->equals(Ljava/lang/Object;Ljava/lang/Object;)Z + move-result v0 + if-nez v0, :fix const-string v0, "$youtubePackageName.fileprovider" invoke-static {p1, v0}, Ljava/util/Objects;->equals(Ljava/lang/Object;Ljava/lang/Object;)Z move-result v0 - if-eqz v0, :ignore + if-nez v0, :fix + goto :ignore + :fix const-string p1, "$musicPackageName.fileprovider" """, ExternalLabel("ignore", getInstruction(0)) ) diff --git a/src/main/kotlin/app/revanced/patches/music/utils/fix/header/RestoreOldHeaderPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/fix/header/RestoreOldHeaderPatch.kt index 907fdeb5f7..c95ad19650 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/fix/header/RestoreOldHeaderPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/fix/header/RestoreOldHeaderPatch.kt @@ -1,16 +1,11 @@ package app.revanced.patches.music.utils.fix.header import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.music.layout.header.ChangeHeaderPatch import app.revanced.patches.music.utils.fix.header.fingerprints.HeaderSwitchConfigFingerprint -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getWideLiteralInstructionIndex -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import app.revanced.util.injectLiteralInstructionBooleanCall @Patch( description = "Fix the issues where new headers are used." @@ -31,19 +26,10 @@ object RestoreOldHeaderPatch : BytecodePatch( * TODO: Add a new header image file to [ChangeHeaderPatch] later. */ HeaderSwitchConfigFingerprint.result?.let { - it.mutableMethod.apply { - val targetIndex = - getTargetIndexOrThrow( - getWideLiteralInstructionIndex(45617851), - Opcode.MOVE_RESULT - ) - val targetRegister = getInstruction(targetIndex).registerA - - addInstruction( - targetIndex + 1, - "const/4 v$targetRegister, 0x0" - ) - } + HeaderSwitchConfigFingerprint.injectLiteralInstructionBooleanCall( + 45617851, + "0x0" + ) } } diff --git a/src/main/kotlin/app/revanced/patches/music/utils/flyoutmenu/FlyoutMenuHookPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/flyoutmenu/FlyoutMenuHookPatch.kt index 37c876d388..53ea63ec3e 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/flyoutmenu/FlyoutMenuHookPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/flyoutmenu/FlyoutMenuHookPatch.kt @@ -6,7 +6,7 @@ import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.music.utils.flyoutmenu.fingerprints.PlaybackRateBottomSheetClassFingerprint import app.revanced.patches.music.utils.integrations.Constants.INTEGRATIONS_PATH import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch -import app.revanced.util.addFieldAndInstructions +import app.revanced.util.addStaticFieldToIntegration import app.revanced.util.resultOrThrow @Patch( @@ -31,15 +31,12 @@ object FlyoutMenuHookPatch : BytecodePatch( return-void """ - context.findClass( - INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR - )!!.mutableClass.addFieldAndInstructions( - context, + context.addStaticFieldToIntegration( + INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR, "showPlaybackSpeedFlyoutMenu", "playbackRateBottomSheetClass", definingClass, - smaliInstructions, - true + smaliInstructions ) } } diff --git a/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt index e911ff8b8d..d2339902ec 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt @@ -8,6 +8,7 @@ import app.revanced.patches.shared.mapping.ResourceMappingPatch.getId import app.revanced.patches.shared.mapping.ResourceType.BOOL import app.revanced.patches.shared.mapping.ResourceType.COLOR import app.revanced.patches.shared.mapping.ResourceType.DIMEN +import app.revanced.patches.shared.mapping.ResourceType.DRAWABLE import app.revanced.patches.shared.mapping.ResourceType.ID import app.revanced.patches.shared.mapping.ResourceType.LAYOUT import app.revanced.patches.shared.mapping.ResourceType.STRING @@ -32,6 +33,7 @@ object SharedResourceIdPatch : ResourcePatch() { var InterstitialsContainer = -1L var IsTablet = -1L var LikeDislikeContainer = -1L + var MainActivityLaunchAnimation = -1L var MenuEntry = -1L var MiniPlayerDefaultText = -1L var MiniPlayerMdxPlaying = -1L @@ -57,6 +59,7 @@ object SharedResourceIdPatch : ResourcePatch() { var TouchOutside = -1L var TrimSilenceSwitch: Long = -1 var VarispeedUnavailableTitle = -1L + var YtFillArrowShuffle = -1L override fun execute(context: ResourceContext) { @@ -77,6 +80,7 @@ object SharedResourceIdPatch : ResourcePatch() { InterstitialsContainer = getId(ID, "interstitials_container") IsTablet = getId(BOOL, "is_tablet") LikeDislikeContainer = getId(ID, "like_dislike_container") + MainActivityLaunchAnimation = getId(LAYOUT, "main_activity_launch_animation") MenuEntry = getId(LAYOUT, "menu_entry") MiniPlayerDefaultText = getId(STRING, "mini_player_default_text") MiniPlayerMdxPlaying = getId(STRING, "mini_player_mdx_playing") @@ -102,6 +106,7 @@ object SharedResourceIdPatch : ResourcePatch() { TouchOutside = getId(ID, "touch_outside") TrimSilenceSwitch = getId(ID, "trim_silence_switch") VarispeedUnavailableTitle = getId(STRING, "varispeed_unavailable_title") + YtFillArrowShuffle = getId(DRAWABLE, "yt_fill_arrow_shuffle_vd_theme_24") } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikeBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikeBytecodePatch.kt index 7c7223d9f2..d1ce151c9c 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikeBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikeBytecodePatch.kt @@ -12,7 +12,7 @@ import app.revanced.patches.music.utils.returnyoutubedislike.fingerprints.Dislik import app.revanced.patches.music.utils.returnyoutubedislike.fingerprints.LikeFingerprint import app.revanced.patches.music.utils.returnyoutubedislike.fingerprints.RemoveLikeFingerprint import app.revanced.patches.music.utils.returnyoutubedislike.fingerprints.TextComponentFingerprint -import app.revanced.patches.music.video.videoid.VideoIdPatch +import app.revanced.patches.music.video.information.VideoInformationPatch import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -22,7 +22,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction @Patch( dependencies = [ SharedResourceIdPatch::class, - VideoIdPatch::class + VideoInformationPatch::class ] ) object ReturnYouTubeDislikeBytecodePatch : BytecodePatch( @@ -70,7 +70,7 @@ object ReturnYouTubeDislikeBytecodePatch : BytecodePatch( } } - VideoIdPatch.hookVideoId("$INTEGRATIONS_RYD_CLASS_DESCRIPTOR->newVideoLoaded(Ljava/lang/String;)V") + VideoInformationPatch.videoIdHook("$INTEGRATIONS_RYD_CLASS_DESCRIPTOR->newVideoLoaded(Ljava/lang/String;)V") } diff --git a/src/main/kotlin/app/revanced/patches/music/utils/settings/ResourceUtils.kt b/src/main/kotlin/app/revanced/patches/music/utils/settings/ResourceUtils.kt index a135b694e6..aa5a2edf53 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/settings/ResourceUtils.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/settings/ResourceUtils.kt @@ -104,6 +104,22 @@ object ResourceUtils { } } + fun ResourceContext.setPreferenceScreenIcon( + category: String + ) { + this.xmlEditor[SETTINGS_HEADER_PATH].use { editor -> + editor.file.doRecursively loop@{ + if (it !is Element) return@loop + + it.getAttributeNode("android:key")?.let { attribute -> + if (attribute.textContent == "revanced_preference_screen_$category") { + it.cloneNodes(it.parentNode) + } + } + } + } + } + fun ResourceContext.sortPreferenceCategory( category: String ) { diff --git a/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsBytecodePatch.kt index d78940ff1d..e3a25fead8 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsBytecodePatch.kt @@ -17,7 +17,7 @@ import app.revanced.patches.music.utils.settings.fingerprints.PreferenceFingerpr import app.revanced.patches.music.utils.settings.fingerprints.SettingsHeadersFragmentFingerprint import app.revanced.patches.shared.fingerprints.SharedSettingFingerprint import app.revanced.patches.shared.integrations.Constants.INTEGRATIONS_UTILS_CLASS_DESCRIPTOR -import app.revanced.util.getTargetIndexOrThrow +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction @@ -52,7 +52,7 @@ object SettingsBytecodePatch : BytecodePatch( */ SharedSettingFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val stringIndex = getTargetIndexOrThrow(Opcode.CONST_STRING) + val stringIndex = indexOfFirstInstructionOrThrow(Opcode.CONST_STRING) val stringRegister = getInstruction(stringIndex).registerA replaceInstruction( diff --git a/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt index f00dc88f65..0af833bda4 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt @@ -3,7 +3,6 @@ package app.revanced.patches.music.utils.settings import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE -import app.revanced.patches.music.utils.fix.accessibility.AccessibilityNodeInfoPatch import app.revanced.patches.music.utils.settings.ResourceUtils.addPreferenceCategory import app.revanced.patches.music.utils.settings.ResourceUtils.addPreferenceWithIntent import app.revanced.patches.music.utils.settings.ResourceUtils.addRVXSettingsPreference @@ -22,10 +21,7 @@ import java.util.concurrent.TimeUnit object SettingsPatch : BaseResourcePatch( name = "Settings for YouTube Music", description = "Applies mandatory patches to implement ReVanced Extended settings into the application.", - dependencies = setOf( - AccessibilityNodeInfoPatch::class, - SettingsBytecodePatch::class - ), + dependencies = setOf(SettingsBytecodePatch::class), compatiblePackages = COMPATIBLE_PACKAGE, requiresIntegrations = true ), Closeable { @@ -42,8 +38,12 @@ object SettingsPatch : BaseResourcePatch( private lateinit var customName: String lateinit var contexts: ResourceContext + internal var upward0627 = false internal var upward0636 = false internal var upward0642 = false + internal var upward0706 = false + internal var upward0718 = false + internal var upward0720 = false override fun execute(context: ResourceContext) { @@ -136,8 +136,12 @@ object SettingsPatch : BaseResourcePatch( val playServicesVersion = node.textContent.toInt() + upward0627 = 234412000 <= playServicesVersion upward0636 = 240399000 <= playServicesVersion upward0642 = 240999000 <= playServicesVersion + upward0706 = 242499000 <= playServicesVersion + upward0718 = 243699000 <= playServicesVersion + upward0720 = 243899000 <= playServicesVersion break } @@ -223,6 +227,14 @@ object SettingsPatch : BaseResourcePatch( } } + /** + * add open default app settings + */ + addPreferenceWithIntent( + CategoryType.MISC, + "revanced_default_app_settings" + ) + /** * add import export settings */ diff --git a/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/SponsorBlockBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/SponsorBlockBytecodePatch.kt index adce2aba86..6f6cb84b3d 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/SponsorBlockBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/SponsorBlockBytecodePatch.kt @@ -6,27 +6,30 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.Patch -import app.revanced.patches.music.utils.fingerprints.SeekBarConstructorFingerprint import app.revanced.patches.music.utils.integrations.Constants.INTEGRATIONS_PATH import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.music.utils.sponsorblock.fingerprints.MusicPlaybackControlsTimeBarDrawFingerprint import app.revanced.patches.music.utils.sponsorblock.fingerprints.MusicPlaybackControlsTimeBarOnMeasureFingerprint +import app.revanced.patches.music.utils.sponsorblock.fingerprints.RectangleFieldInvalidatorFingerprint +import app.revanced.patches.music.utils.sponsorblock.fingerprints.SeekBarConstructorFingerprint import app.revanced.patches.music.utils.sponsorblock.fingerprints.SeekbarOnDrawFingerprint import app.revanced.patches.music.video.information.VideoInformationPatch -import app.revanced.patches.music.video.videoid.VideoIdPatch -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameReversedOrThrow +import app.revanced.util.alsoResolve +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Patch( dependencies = [ SharedResourceIdPatch::class, - VideoInformationPatch::class, - VideoIdPatch::class + VideoInformationPatch::class ] ) object SponsorBlockBytecodePatch : BytecodePatch( @@ -39,7 +42,6 @@ object SponsorBlockBytecodePatch : BytecodePatch( private const val INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR = "$INTEGRATIONS_PATH/sponsorblock/SegmentPlaybackController;" - private lateinit var rectangleFieldName: String override fun execute(context: BytecodeContext) { /** @@ -55,22 +57,41 @@ object SponsorBlockBytecodePatch : BytecodePatch( /** * Responsible for seekbar in fullscreen */ - val seekBarClass = SeekBarConstructorFingerprint.resultOrThrow().mutableClass - SeekbarOnDrawFingerprint.resolve(context, seekBarClass) + var rectangleFieldName = + RectangleFieldInvalidatorFingerprint.alsoResolve( + context, SeekBarConstructorFingerprint + ).let { + with(it.mutableMethod) { + val invalidateIndex = + RectangleFieldInvalidatorFingerprint.indexOfInvalidateInstruction(this) + val rectangleIndex = + indexOfFirstInstructionReversedOrThrow(invalidateIndex + 1) { + getReference()?.type == "Landroid/graphics/Rect;" + } + val rectangleReference = + getInstruction(rectangleIndex).reference - SeekbarOnDrawFingerprint.resultOrThrow().let { + (rectangleReference as FieldReference).name + } + } + + SeekbarOnDrawFingerprint.alsoResolve( + context, SeekBarConstructorFingerprint + ).let { it.mutableMethod.apply { // Initialize seekbar method addInstructions( 0, """ move-object/from16 v0, p0 - const-string v1, "${VideoInformationPatch.rectangleFieldName}" + const-string v1, "$rectangleFieldName" invoke-static {v0, v1}, $INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarRect(Ljava/lang/Object;Ljava/lang/String;)V """ ) // Set seekbar thickness - val roundIndex = getTargetIndexWithMethodReferenceNameOrThrow("round") + 1 + val roundIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "round" + } + 1 val roundRegister = getInstruction(roundIndex).registerA addInstruction( roundIndex + 1, @@ -79,8 +100,9 @@ object SponsorBlockBytecodePatch : BytecodePatch( ) // Draw segment - val drawCircleIndex = - getTargetIndexWithMethodReferenceNameReversedOrThrow("drawCircle") + val drawCircleIndex = indexOfFirstInstructionReversedOrThrow { + getReference()?.name == "drawCircle" + } val drawCircleInstruction = getInstruction(drawCircleIndex) addInstruction( drawCircleIndex, @@ -94,14 +116,15 @@ object SponsorBlockBytecodePatch : BytecodePatch( /** * Responsible for seekbar in player */ - MusicPlaybackControlsTimeBarOnMeasureFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val rectangleIndex = it.scanResult.patternScanResult!!.startIndex - val rectangleReference = - getInstruction(rectangleIndex).reference - rectangleFieldName = (rectangleReference as FieldReference).name + rectangleFieldName = + MusicPlaybackControlsTimeBarOnMeasureFingerprint.resultOrThrow().let { + with(it.mutableMethod) { + val rectangleIndex = it.scanResult.patternScanResult!!.startIndex + val rectangleReference = + getInstruction(rectangleIndex).reference + (rectangleReference as FieldReference).name + } } - } MusicPlaybackControlsTimeBarDrawFingerprint.resultOrThrow().let { it.mutableMethod.apply { @@ -115,7 +138,10 @@ object SponsorBlockBytecodePatch : BytecodePatch( ) // Draw segment - val drawCircleIndex = getTargetIndexWithMethodReferenceNameOrThrow("drawCircle") + val drawCircleIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL + && getReference()?.name == "drawCircle" + } val drawCircleInstruction = getInstruction(drawCircleIndex) addInstruction( drawCircleIndex, @@ -128,6 +154,6 @@ object SponsorBlockBytecodePatch : BytecodePatch( /** * Set current video id */ - VideoIdPatch.hookVideoId("$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V") + VideoInformationPatch.videoIdHook("$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V") } } diff --git a/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/fingerprints/RectangleFieldInvalidatorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/fingerprints/RectangleFieldInvalidatorFingerprint.kt new file mode 100644 index 0000000000..3e779ccccf --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/fingerprints/RectangleFieldInvalidatorFingerprint.kt @@ -0,0 +1,28 @@ +package app.revanced.patches.music.utils.sponsorblock.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.music.utils.sponsorblock.fingerprints.RectangleFieldInvalidatorFingerprint.indexOfInvalidateInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversed +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 object RectangleFieldInvalidatorFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE + ), + customFingerprint = { methodDef, _ -> + indexOfInvalidateInstruction(methodDef) >= 0 + } +) { + fun indexOfInvalidateInstruction(methodDef: Method) = + methodDef.indexOfFirstInstructionReversed { + getReference()?.name == "invalidate" + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/fingerprints/SeekBarConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/fingerprints/SeekBarConstructorFingerprint.kt similarity index 82% rename from src/main/kotlin/app/revanced/patches/music/utils/fingerprints/SeekBarConstructorFingerprint.kt rename to src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/fingerprints/SeekBarConstructorFingerprint.kt index cf5d9a27f4..9edfd4c98a 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/fingerprints/SeekBarConstructorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/fingerprints/SeekBarConstructorFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.music.utils.fingerprints +package app.revanced.patches.music.utils.sponsorblock.fingerprints import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.InlineTimeBarAdBreakMarkerColor import app.revanced.util.fingerprint.LiteralValueFingerprint diff --git a/src/main/kotlin/app/revanced/patches/music/video/information/VideoInformationPatch.kt b/src/main/kotlin/app/revanced/patches/music/video/information/VideoInformationPatch.kt index 53cd1e098e..8dd9730a7f 100644 --- a/src/main/kotlin/app/revanced/patches/music/video/information/VideoInformationPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/video/information/VideoInformationPatch.kt @@ -4,40 +4,36 @@ import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprintResult import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.Patch -import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.smali.toInstructions -import app.revanced.patches.music.utils.fingerprints.SeekBarConstructorFingerprint import app.revanced.patches.music.utils.integrations.Constants.SHARED_PATH import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.music.video.information.fingerprints.PlaybackSpeedFingerprint import app.revanced.patches.music.video.information.fingerprints.PlaybackSpeedParentFingerprint import app.revanced.patches.music.video.information.fingerprints.PlayerControllerSetTimeReferenceFingerprint import app.revanced.patches.music.video.information.fingerprints.VideoEndFingerprint -import app.revanced.patches.music.video.information.fingerprints.VideoLengthFingerprint +import app.revanced.patches.music.video.information.fingerprints.VideoIdFingerprint import app.revanced.patches.music.video.information.fingerprints.VideoQualityListFingerprint import app.revanced.patches.music.video.information.fingerprints.VideoQualityTextFingerprint -import app.revanced.patches.music.video.videoid.VideoIdPatch import app.revanced.patches.shared.fingerprints.MdxPlayerDirectorSetVideoStageFingerprint -import app.revanced.util.addFieldAndInstructions +import app.revanced.patches.shared.fingerprints.VideoLengthFingerprint +import app.revanced.util.addStaticFieldToIntegration +import app.revanced.util.alsoResolve import app.revanced.util.getReference -import app.revanced.util.getTargetIndexWithFieldReferenceTypeReversedOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameReversedOrThrow import app.revanced.util.getWalkerMethod import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow 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.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction -import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation @@ -45,10 +41,7 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter import com.android.tools.smali.dexlib2.util.MethodUtil @Patch( - dependencies = [ - SharedResourceIdPatch::class, - VideoIdPatch::class - ] + dependencies = [SharedResourceIdPatch::class] ) @Suppress("MemberVisibilityCanBePrivate") object VideoInformationPatch : BytecodePatch( @@ -56,8 +49,9 @@ object VideoInformationPatch : BytecodePatch( MdxPlayerDirectorSetVideoStageFingerprint, PlayerControllerSetTimeReferenceFingerprint, PlaybackSpeedParentFingerprint, - SeekBarConstructorFingerprint, VideoEndFingerprint, + VideoIdFingerprint, + VideoLengthFingerprint, VideoQualityListFingerprint, VideoQualityTextFingerprint ) @@ -65,6 +59,20 @@ object VideoInformationPatch : BytecodePatch( private const val INTEGRATIONS_CLASS_DESCRIPTOR = "$SHARED_PATH/VideoInformation;" + private const val REGISTER_PLAYER_RESPONSE_MODEL = 4 + + private const val REGISTER_VIDEO_ID = 0 + private const val REGISTER_VIDEO_LENGTH = 1 + + @Suppress("unused") + private const val REGISTER_VIDEO_LENGTH_DUMMY = 2 + + private lateinit var PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR: String + private lateinit var videoIdMethodCall: String + private lateinit var videoLengthMethodCall: String + + private lateinit var videoInformationMethod: MutableMethod + /** * Used in [VideoEndFingerprint] and [MdxPlayerDirectorSetVideoStageFingerprint]. * Since both classes are inherited from the same class, @@ -73,7 +81,6 @@ object VideoInformationPatch : BytecodePatch( private var seekSourceEnumType = "" private var seekSourceMethodName = "" - private lateinit var videoInformationMutableClass: MutableClass private lateinit var context: BytecodeContext private lateinit var playerConstructorMethod: MutableMethod @@ -86,7 +93,6 @@ object VideoInformationPatch : BytecodePatch( private var videoTimeConstructorInsertIndex = 2 // Used by other patches. - lateinit var rectangleFieldName: String internal lateinit var playbackSpeedResult: MethodFingerprintResult private fun addSeekInterfaceMethods( @@ -109,7 +115,7 @@ object VideoInformationPatch : BytecodePatch( 4, """ # first enum (field a) is SEEK_SOURCE_UNKNOWN sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType - invoke-virtual {p0, p1, p2, v0}, ${definingClass}->$seekMethodName(J$seekSourceEnumType)Z + invoke-virtual {p0, p1, p2, v0}, $definingClass->$seekMethodName(J$seekSourceEnumType)Z move-result p1 return p1 """.toInstructions(), @@ -130,21 +136,18 @@ object VideoInformationPatch : BytecodePatch( return v0 """ - videoInformationMutableClass.addFieldAndInstructions( - context, + context.addStaticFieldToIntegration( + INTEGRATIONS_CLASS_DESCRIPTOR, methodName, fieldName, definingClass, - smaliInstructions, - true + smaliInstructions ) } } override fun execute(context: BytecodeContext) { this.context = context - videoInformationMutableClass = - context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR)!!.mutableClass VideoEndFingerprint.resultOrThrow().let { it.mutableMethod.apply { @@ -194,6 +197,34 @@ object VideoInformationPatch : BytecodePatch( } } + /** + * Set current video information + */ + VideoIdFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + val playerResponseModelIndex = it.scanResult.patternScanResult!!.startIndex + + PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR = + getInstruction(playerResponseModelIndex) + .getReference() + ?.definingClass + ?: throw PatchException("Could not find Player Response Model class") + + videoIdMethodCall = + VideoIdFingerprint.getPlayerResponseInstruction("Ljava/lang/String;") + videoLengthMethodCall = + VideoLengthFingerprint.getPlayerResponseInstruction("J") + + videoInformationMethod = getVideoInformationMethod() + it.mutableClass.methods.add(videoInformationMethod) + + addInstruction( + playerResponseModelIndex + 2, + "invoke-direct/range {p0 .. p1}, $definingClass->setVideoInformation($PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR)V" + ) + } + } + /** * Set the video time method */ @@ -210,47 +241,19 @@ object VideoInformationPatch : BytecodePatch( /** * Set current video length */ - VideoLengthFingerprint.resolve( - context, - SeekBarConstructorFingerprint.resultOrThrow().classDef - ) - VideoLengthFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val invalidateIndex = - getTargetIndexWithMethodReferenceNameReversedOrThrow("invalidate") - val rectangleIndex = getTargetIndexWithFieldReferenceTypeReversedOrThrow( - invalidateIndex + 1, - "Landroid/graphics/Rect;" - ) - rectangleFieldName = - (getInstruction(rectangleIndex).reference as FieldReference).name - - val videoLengthRegisterIndex = it.scanResult.patternScanResult!!.startIndex + 1 - val videoLengthRegister = - getInstruction(videoLengthRegisterIndex).registerA - val dummyRegisterForLong = - videoLengthRegister + 1 // required for long values since they are wide - - addInstruction( - videoLengthRegisterIndex + 1, - "invoke-static {v$videoLengthRegister, v$dummyRegisterForLong}, $INTEGRATIONS_CLASS_DESCRIPTOR->setVideoLength(J)V" - ) - } - } + videoLengthHook("$INTEGRATIONS_CLASS_DESCRIPTOR->setVideoLength(J)V") /** * Set current video id */ - VideoIdPatch.hookVideoId("$INTEGRATIONS_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V") + videoIdHook("$INTEGRATIONS_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V") /** * Hook current playback speed */ - PlaybackSpeedFingerprint.resolve( - context, - PlaybackSpeedParentFingerprint.resultOrThrow().classDef - ) - PlaybackSpeedFingerprint.resultOrThrow().let { + PlaybackSpeedFingerprint.alsoResolve( + context, PlaybackSpeedParentFingerprint + ).let { it.mutableMethod.apply { playbackSpeedResult = it val endIndex = it.scanResult.patternScanResult!!.endIndex @@ -294,13 +297,12 @@ object VideoInformationPatch : BytecodePatch( return-void """ - videoInformationMutableClass.addFieldAndInstructions( - context, + VideoInformationPatch.context.addStaticFieldToIntegration( + INTEGRATIONS_CLASS_DESCRIPTOR, "overrideVideoQuality", "videoQualityClass", videoQualityClass, - smaliInstructions, - true + smaliInstructions ) } @@ -318,6 +320,49 @@ object VideoInformationPatch : BytecodePatch( } } + private fun MethodFingerprint.getPlayerResponseInstruction(returnType: String): String { + resultOrThrow().mutableMethod.apply { + val targetReference = getInstruction( + indexOfFirstInstructionOrThrow { + val reference = getReference() + (opcode == Opcode.INVOKE_INTERFACE_RANGE || opcode == Opcode.INVOKE_INTERFACE) && + reference?.definingClass == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR && + reference.returnType == returnType + } + ).reference + + return "invoke-interface/range {v$REGISTER_PLAYER_RESPONSE_MODEL .. v$REGISTER_PLAYER_RESPONSE_MODEL}, $targetReference" + } + } + + private fun MutableMethod.getVideoInformationMethod(): MutableMethod = + ImmutableMethod( + definingClass, + "setVideoInformation", + listOf( + ImmutableMethodParameter( + PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR, + annotations, + null + ) + ), + "V", + AccessFlags.PRIVATE or AccessFlags.FINAL, + annotations, + null, + ImmutableMethodImplementation( + REGISTER_PLAYER_RESPONSE_MODEL + 1, """ + $videoIdMethodCall + move-result-object v$REGISTER_VIDEO_ID + $videoLengthMethodCall + move-result-wide v$REGISTER_VIDEO_LENGTH + return-void + """.toInstructions(), + null, + null + ) + ).toMutable() + private fun MutableMethod.insert(insertIndex: Int, register: String, descriptor: String) = addInstruction(insertIndex, "invoke-static { $register }, $descriptor") @@ -351,6 +396,24 @@ object VideoInformationPatch : BytecodePatch( "invoke-static { }, $targetMethodClass->$targetMethodName()V" ) + internal fun videoIdHook( + descriptor: String + ) = videoInformationMethod.apply { + addInstruction( + implementation!!.instructions.lastIndex, + "invoke-static {v$REGISTER_VIDEO_ID}, $descriptor" + ) + } + + internal fun videoLengthHook( + descriptor: String + ) = videoInformationMethod.apply { + addInstruction( + implementation!!.instructions.lastIndex, + "invoke-static {v$REGISTER_VIDEO_LENGTH, v$REGISTER_VIDEO_LENGTH_DUMMY}, $descriptor" + ) + } + /** * Hook the video time. * The hook is usually called once per second. diff --git a/src/main/kotlin/app/revanced/patches/music/video/videoid/fingerprints/VideoIdFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/video/information/fingerprints/VideoIdFingerprint.kt similarity index 90% rename from src/main/kotlin/app/revanced/patches/music/video/videoid/fingerprints/VideoIdFingerprint.kt rename to src/main/kotlin/app/revanced/patches/music/video/information/fingerprints/VideoIdFingerprint.kt index bc75a03029..5f901dc11b 100644 --- a/src/main/kotlin/app/revanced/patches/music/video/videoid/fingerprints/VideoIdFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/music/video/information/fingerprints/VideoIdFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.music.video.videoid.fingerprints +package app.revanced.patches.music.video.information.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint diff --git a/src/main/kotlin/app/revanced/patches/music/video/information/fingerprints/VideoLengthFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/video/information/fingerprints/VideoLengthFingerprint.kt deleted file mode 100644 index 09da6b37d2..0000000000 --- a/src/main/kotlin/app/revanced/patches/music/video/information/fingerprints/VideoLengthFingerprint.kt +++ /dev/null @@ -1,16 +0,0 @@ -package app.revanced.patches.music.video.information.fingerprints - -import app.revanced.util.fingerprint.MethodReferenceNameFingerprint -import com.android.tools.smali.dexlib2.Opcode - -internal object VideoLengthFingerprint : MethodReferenceNameFingerprint( - opcodes = listOf( - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_WIDE, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_WIDE, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_WIDE - ), - reference = { "invalidate" } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/video/playback/VideoPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/music/video/playback/VideoPlaybackPatch.kt index 4173158b07..6c58a8c677 100644 --- a/src/main/kotlin/app/revanced/patches/music/video/playback/VideoPlaybackPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/video/playback/VideoPlaybackPatch.kt @@ -12,13 +12,13 @@ import app.revanced.patches.music.utils.settings.SettingsPatch import app.revanced.patches.music.video.information.VideoInformationPatch import app.revanced.patches.music.video.playback.fingerprints.PlaybackSpeedBottomSheetFingerprint import app.revanced.patches.music.video.playback.fingerprints.UserQualityChangeFingerprint -import app.revanced.patches.music.video.videoid.VideoIdPatch -import app.revanced.util.getTargetIndexOrThrow +import app.revanced.util.findMethodOrThrow +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction @Suppress("unused") @@ -29,7 +29,6 @@ object VideoPlaybackPatch : BaseBytecodePatch( dependencies = setOf( CustomPlaybackSpeedPatch::class, SettingsPatch::class, - VideoIdPatch::class, VideoInformationPatch::class ), compatiblePackages = COMPATIBLE_PACKAGE, @@ -52,7 +51,7 @@ object VideoPlaybackPatch : BaseBytecodePatch( it.mutableClass.methods.find { method -> method.name == "onItemClick" } onItemClickMethod?.apply { - val targetIndex = getTargetIndexOrThrow(Opcode.IGET) + val targetIndex = indexOfFirstInstructionOrThrow(Opcode.IGET) val targetRegister = getInstruction(targetIndex).registerA @@ -86,23 +85,18 @@ object VideoPlaybackPatch : BaseBytecodePatch( it.mutableMethod.apply { val endIndex = it.scanResult.patternScanResult!!.endIndex val qualityChangedClass = - context.findClass( - (getInstruction(endIndex)) - .reference.toString() - )!! - .mutableClass + getInstruction(endIndex).reference.toString() - val onItemClickMethod = - qualityChangedClass.methods.find { method -> method.name == "onItemClick" } - - onItemClickMethod?.addInstruction( + context.findMethodOrThrow(qualityChangedClass) { + name == "onItemClick" + }.addInstruction( 0, "invoke-static {}, $INTEGRATIONS_VIDEO_QUALITY_CLASS_DESCRIPTOR->userSelectedVideoQuality()V" - ) ?: throw PatchException("Failed to find onItemClick method") + ) } } - VideoIdPatch.hookVideoId("$INTEGRATIONS_VIDEO_QUALITY_CLASS_DESCRIPTOR->newVideoStarted(Ljava/lang/String;)V") + VideoInformationPatch.videoIdHook("$INTEGRATIONS_VIDEO_QUALITY_CLASS_DESCRIPTOR->newVideoStarted(Ljava/lang/String;)V") // endregion diff --git a/src/main/kotlin/app/revanced/patches/music/video/videoid/VideoIdPatch.kt b/src/main/kotlin/app/revanced/patches/music/video/videoid/VideoIdPatch.kt deleted file mode 100644 index 85c190197e..0000000000 --- a/src/main/kotlin/app/revanced/patches/music/video/videoid/VideoIdPatch.kt +++ /dev/null @@ -1,38 +0,0 @@ -package app.revanced.patches.music.video.videoid - -import app.revanced.patcher.data.BytecodeContext -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.music.video.videoid.fingerprints.VideoIdFingerprint -import app.revanced.util.resultOrThrow -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction - -object VideoIdPatch : BytecodePatch( - setOf(VideoIdFingerprint) -) { - private var videoIdRegister = 0 - private var videoIdInsertIndex = 0 - private lateinit var videoIdMethod: MutableMethod - - override fun execute(context: BytecodeContext) { - - VideoIdFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - videoIdMethod = this - videoIdInsertIndex = it.scanResult.patternScanResult!!.startIndex + 2 - videoIdRegister = - getInstruction(videoIdInsertIndex - 1).registerA - } - } - } - - fun hookVideoId( - methodDescriptor: String - ) = videoIdMethod.addInstruction( - videoIdInsertIndex++, - "invoke-static {v$videoIdRegister}, $methodDescriptor" - ) -} - diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/general/AdsPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/general/AdsPatch.kt index de989f0693..dd2595576a 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/ad/general/AdsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/ad/general/AdsPatch.kt @@ -12,12 +12,15 @@ import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACK import app.revanced.patches.reddit.utils.integrations.Constants.PATCHES_PATH import app.revanced.patches.reddit.utils.settings.SettingsBytecodePatch.updateSettingsStatus import app.revanced.patches.reddit.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexWithFieldReferenceNameOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction 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 @Suppress("unused") object AdsPatch : BaseBytecodePatch( @@ -42,7 +45,9 @@ object AdsPatch : BaseBytecodePatch( // region Filter promoted ads (does not work in popular or latest feed) AdPostFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = getTargetIndexWithFieldReferenceNameOrThrow("children") + val targetIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "children" + } val targetRegister = getInstruction(targetIndex).registerA addInstructions( @@ -59,7 +64,10 @@ object AdsPatch : BaseBytecodePatch( // By removing the appending instruction no ad posts gets appended to the feed. NewAdPostFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = getTargetIndexWithMethodReferenceNameOrThrow("add") + val targetIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL + && getReference()?.toString() == "Ljava/util/ArrayList;->add(Ljava/lang/Object;)Z" + } val targetInstruction = getInstruction(targetIndex) replaceInstruction( diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/recentlyvisited/RecentlyVisitedShelfPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/recentlyvisited/RecentlyVisitedShelfPatch.kt index d9338e2743..8956f23e05 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/layout/recentlyvisited/RecentlyVisitedShelfPatch.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/layout/recentlyvisited/RecentlyVisitedShelfPatch.kt @@ -9,15 +9,15 @@ import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACK import app.revanced.patches.reddit.utils.integrations.Constants.PATCHES_PATH import app.revanced.patches.reddit.utils.settings.SettingsBytecodePatch.updateSettingsStatus import app.revanced.patches.reddit.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.getTargetIndexWithFieldReferenceNameOrThrow -import app.revanced.util.getTargetIndexWithReferenceOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.Reference @Suppress("unused") @@ -40,20 +40,31 @@ object RecentlyVisitedShelfPatch : BaseBytecodePatch( it.mutableClass.methods.find { method -> method.name == "" } ?.apply { - val recentlyVisitedFieldIndex = - getTargetIndexWithFieldReferenceNameOrThrow("RECENTLY_VISITED") + val recentlyVisitedFieldIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "RECENTLY_VISITED" + } val recentlyVisitedObjectIndex = - getTargetIndexOrThrow(recentlyVisitedFieldIndex, Opcode.IPUT_OBJECT) + indexOfFirstInstructionOrThrow( + recentlyVisitedFieldIndex, + Opcode.IPUT_OBJECT + ) recentlyVisitedReference = getInstruction(recentlyVisitedObjectIndex).reference } ?: throw PatchException("Constructor method not found!") it.mutableMethod.apply { - val recentlyVisitedObjectIndex = - getTargetIndexWithReferenceOrThrow(recentlyVisitedReference.toString()) + val recentlyVisitedObjectIndex = indexOfFirstInstructionOrThrow { + getReference()?.toString() == recentlyVisitedReference.toString() + } arrayOf( - getTargetIndexOrThrow(recentlyVisitedObjectIndex, Opcode.INVOKE_STATIC), - getTargetIndexReversedOrThrow(recentlyVisitedObjectIndex, Opcode.INVOKE_STATIC) + indexOfFirstInstructionOrThrow( + recentlyVisitedObjectIndex, + Opcode.INVOKE_STATIC + ), + indexOfFirstInstructionReversedOrThrow( + recentlyVisitedObjectIndex, + Opcode.INVOKE_STATIC + ) ).forEach { staticIndex -> val insertRegister = getInstruction(staticIndex + 1).registerA diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/fingerprints/ScreenshotTakenBannerFingerprint.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/fingerprints/ScreenshotTakenBannerFingerprint.kt index 541efade0b..6154e86698 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/fingerprints/ScreenshotTakenBannerFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/fingerprints/ScreenshotTakenBannerFingerprint.kt @@ -3,14 +3,14 @@ package app.revanced.patches.reddit.layout.screenshotpopup.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.reddit.utils.resourceid.SharedResourceIdPatch.ScreenShotShareBanner -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object ScreenshotTakenBannerFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, customFingerprint = { methodDef, classDef -> - methodDef.containsWideLiteralInstructionIndex(ScreenShotShareBanner) + methodDef.containsWideLiteralInstructionValue(ScreenShotShareBanner) && classDef.sourceFile == "ScreenshotTakenBanner.kt" } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt index ebc2f2e656..83adfb7b02 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt @@ -12,7 +12,7 @@ import app.revanced.patches.reddit.utils.resourceid.SharedResourceIdPatch.Cancel import app.revanced.patches.reddit.utils.resourceid.SharedResourceIdPatch.TextAppearanceRedditBaseOldButtonColored import app.revanced.patches.reddit.utils.settings.SettingsBytecodePatch.updateSettingsStatus import app.revanced.patches.reddit.utils.settings.SettingsPatch -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction @@ -39,7 +39,8 @@ object SubRedditDialogPatch : BaseBytecodePatch( FrequentUpdatesSheetScreenFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val cancelButtonViewIndex = getWideLiteralInstructionIndex(CancelButton) + 2 + val cancelButtonViewIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(CancelButton) + 2 val cancelButtonViewRegister = getInstruction(cancelButtonViewIndex).registerA @@ -53,7 +54,9 @@ object SubRedditDialogPatch : BaseBytecodePatch( RedditAlertDialogsFingerprint.resultOrThrow().let { it.mutableMethod.apply { val insertIndex = - getWideLiteralInstructionIndex(TextAppearanceRedditBaseOldButtonColored) + 1 + indexOfFirstWideLiteralInstructionValueOrThrow( + TextAppearanceRedditBaseOldButtonColored + ) + 1 val insertRegister = getInstruction(insertIndex).registerC addInstruction( diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/fingerprints/FrequentUpdatesSheetScreenFingerprint.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/fingerprints/FrequentUpdatesSheetScreenFingerprint.kt index 33e648aac6..ab8942a7b1 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/fingerprints/FrequentUpdatesSheetScreenFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/fingerprints/FrequentUpdatesSheetScreenFingerprint.kt @@ -3,14 +3,14 @@ package app.revanced.patches.reddit.layout.subredditdialog.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.reddit.utils.resourceid.SharedResourceIdPatch.CancelButton -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object FrequentUpdatesSheetScreenFingerprint : MethodFingerprint( returnType = "Landroid/view/View;", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, customFingerprint = { methodDef, classDef -> - methodDef.containsWideLiteralInstructionIndex(CancelButton) + methodDef.containsWideLiteralInstructionValue(CancelButton) && classDef.sourceFile == "FrequentUpdatesSheetScreen.kt" } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/fingerprints/RedditAlertDialogsFingerprint.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/fingerprints/RedditAlertDialogsFingerprint.kt index 44515f97b2..a992cad694 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/fingerprints/RedditAlertDialogsFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/fingerprints/RedditAlertDialogsFingerprint.kt @@ -3,14 +3,14 @@ package app.revanced.patches.reddit.layout.subredditdialog.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.reddit.utils.resourceid.SharedResourceIdPatch.TextAppearanceRedditBaseOldButtonColored -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object RedditAlertDialogsFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, customFingerprint = { methodDef, classDef -> - methodDef.containsWideLiteralInstructionIndex(TextAppearanceRedditBaseOldButtonColored) + methodDef.containsWideLiteralInstructionValue(TextAppearanceRedditBaseOldButtonColored) && classDef.sourceFile == "RedditAlertDialogs.kt" } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/toolbar/ToolBarButtonPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/toolbar/ToolBarButtonPatch.kt index d71fc2f39e..f2a305f202 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/layout/toolbar/ToolBarButtonPatch.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/layout/toolbar/ToolBarButtonPatch.kt @@ -10,7 +10,7 @@ import app.revanced.patches.reddit.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.reddit.utils.resourceid.SharedResourceIdPatch.ToolBarNavSearchCtaContainer import app.revanced.patches.reddit.utils.settings.SettingsBytecodePatch.updateSettingsStatus import app.revanced.patches.reddit.utils.settings.SettingsPatch -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @@ -35,7 +35,7 @@ object ToolBarButtonPatch : BaseBytecodePatch( HomePagerScreenFingerprint.resultOrThrow().let { it.mutableMethod.apply { val targetIndex = - getWideLiteralInstructionIndex(ToolBarNavSearchCtaContainer) + 3 + indexOfFirstWideLiteralInstructionValueOrThrow(ToolBarNavSearchCtaContainer) + 3 val targetRegister = getInstruction(targetIndex - 1).registerA diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/toolbar/fingerprints/HomePagerScreenFingerprint.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/toolbar/fingerprints/HomePagerScreenFingerprint.kt index 4f42a5df86..ecec1bce51 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/layout/toolbar/fingerprints/HomePagerScreenFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/layout/toolbar/fingerprints/HomePagerScreenFingerprint.kt @@ -3,7 +3,7 @@ package app.revanced.patches.reddit.layout.toolbar.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.reddit.utils.resourceid.SharedResourceIdPatch.ToolBarNavSearchCtaContainer -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object HomePagerScreenFingerprint : MethodFingerprint( @@ -12,6 +12,6 @@ internal object HomePagerScreenFingerprint : MethodFingerprint( parameters = listOf("Landroid/view/LayoutInflater;", "Landroid/view/ViewGroup;"), customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("/HomePagerScreen;") - && methodDef.containsWideLiteralInstructionIndex(ToolBarNavSearchCtaContainer) + && methodDef.containsWideLiteralInstructionValue(ToolBarNavSearchCtaContainer) } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/reddit/misc/openlink/OpenLinksExternallyPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/misc/openlink/OpenLinksExternallyPatch.kt index fb18505792..5a9e81681c 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/misc/openlink/OpenLinksExternallyPatch.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/misc/openlink/OpenLinksExternallyPatch.kt @@ -9,7 +9,7 @@ import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACK import app.revanced.patches.reddit.utils.integrations.Constants.PATCHES_PATH import app.revanced.patches.reddit.utils.settings.SettingsBytecodePatch.updateSettingsStatus import app.revanced.patches.reddit.utils.settings.SettingsPatch -import app.revanced.util.getStringInstructionIndex +import app.revanced.util.indexOfFirstStringInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow @@ -27,7 +27,7 @@ object OpenLinksExternallyPatch : BaseBytecodePatch( override fun execute(context: BytecodeContext) { ScreenNavigatorFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = getStringInstructionIndex("uri") + 2 + val insertIndex = indexOfFirstStringInstructionOrThrow("uri") + 2 addInstructionsWithLabels( insertIndex, """ diff --git a/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsBytecodePatch.kt index cf6aa5851d..cd15139229 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsBytecodePatch.kt @@ -15,8 +15,8 @@ import app.revanced.patches.reddit.utils.settings.fingerprints.AcknowledgementsL import app.revanced.patches.reddit.utils.settings.fingerprints.OssLicensesMenuActivityOnCreateFingerprint import app.revanced.patches.reddit.utils.settings.fingerprints.SettingsStatusLoadFingerprint import app.revanced.patches.shared.fingerprints.SharedSettingFingerprint -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @@ -39,7 +39,7 @@ object SettingsBytecodePatch : BytecodePatch( internal fun updateSettingsLabel(label: String) = acknowledgementsLabelBuilderMethod.apply { val insertIndex = - getWideLiteralInstructionIndex(LabelAcknowledgements) + 3 + indexOfFirstWideLiteralInstructionValueOrThrow(LabelAcknowledgements) + 3 val insertRegister = getInstruction(insertIndex - 1).registerA @@ -62,7 +62,7 @@ object SettingsBytecodePatch : BytecodePatch( */ SharedSettingFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val stringIndex = getTargetIndexOrThrow(Opcode.CONST_STRING) + val stringIndex = indexOfFirstInstructionOrThrow(Opcode.CONST_STRING) val stringRegister = getInstruction(stringIndex).registerA replaceInstruction( diff --git a/src/main/kotlin/app/revanced/patches/reddit/utils/settings/fingerprints/AcknowledgementsLabelBuilderFingerprint.kt b/src/main/kotlin/app/revanced/patches/reddit/utils/settings/fingerprints/AcknowledgementsLabelBuilderFingerprint.kt index 6954a037c0..6bccc65941 100644 --- a/src/main/kotlin/app/revanced/patches/reddit/utils/settings/fingerprints/AcknowledgementsLabelBuilderFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/reddit/utils/settings/fingerprints/AcknowledgementsLabelBuilderFingerprint.kt @@ -3,7 +3,7 @@ package app.revanced.patches.reddit.utils.settings.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.reddit.utils.resourceid.SharedResourceIdPatch.LabelAcknowledgements -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object AcknowledgementsLabelBuilderFingerprint : MethodFingerprint( @@ -12,6 +12,6 @@ internal object AcknowledgementsLabelBuilderFingerprint : MethodFingerprint( parameters = listOf("Landroidx/preference/Preference;"), customFingerprint = { methodDef, _ -> methodDef.definingClass.startsWith("Lcom/reddit/screen/settings/preferences/") - && methodDef.containsWideLiteralInstructionIndex(LabelAcknowledgements) + && methodDef.containsWideLiteralInstructionValue(LabelAcknowledgements) } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/ads/BaseAdsPatch.kt b/src/main/kotlin/app/revanced/patches/shared/ads/BaseAdsPatch.kt index 590371d12c..931751c760 100644 --- a/src/main/kotlin/app/revanced/patches/shared/ads/BaseAdsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/ads/BaseAdsPatch.kt @@ -12,14 +12,12 @@ import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.ads.fingerprints.MusicAdsFingerprint import app.revanced.patches.shared.ads.fingerprints.VideoAdsFingerprint import app.revanced.patches.shared.integrations.Constants.PATCHES_PATH -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.getReference import app.revanced.util.getWalkerMethod -import app.revanced.util.getWideLiteralInstructionIndex import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference @@ -34,12 +32,16 @@ abstract class BaseAdsPatch( VideoAdsFingerprint ) ) { + private companion object { + const val INTEGRATIONS_CLASS_DESCRIPTOR = + "$PATCHES_PATH/FullscreenAdsPatch;" + } + override fun execute(context: BytecodeContext) { MusicAdsFingerprint.resultOrThrow().let { it.mutableMethod.apply { val targetIndex = indexOfFirstInstructionOrThrow { - val reference = ((this as? ReferenceInstruction)?.reference as? MethodReference) - + val reference = getReference() opcode == Opcode.INVOKE_VIRTUAL && reference?.returnType == "V" && reference.parameterTypes.size == 1 @@ -72,7 +74,7 @@ abstract class BaseAdsPatch( internal fun MethodFingerprintResult.hookNonLithoFullscreenAds(literal: Long) { mutableMethod.apply { - val targetIndex = getWideLiteralInstructionIndex(literal) + 2 + val targetIndex = indexOfFirstWideLiteralInstructionValueOrThrow(literal) + 2 val targetRegister = getInstruction(targetIndex).registerA addInstruction( @@ -82,7 +84,7 @@ abstract class BaseAdsPatch( } } - internal fun MethodFingerprintResult.hookLithoFullscreenAds(context: BytecodeContext) { + internal fun MethodFingerprintResult.hookLithoFullscreenAds() { mutableMethod.apply { val dialogCodeIndex = scanResult.patternScanResult!!.endIndex val dialogCodeField = @@ -90,58 +92,35 @@ abstract class BaseAdsPatch( if (dialogCodeField.type != "I") throw PatchException("Invalid dialogCodeField: $dialogCodeField") - // Disable fullscreen ads - addInstructionsWithLabels( - 0, + var prependInstructions = """ + move-object/from16 v0, p1 + move-object/from16 v1, p2 """ - move-object/from16 v0, p2 - - # In the latest version of YouTube and YouTube Music, it is used after being cast - - check-cast v0, ${dialogCodeField.definingClass} - iget v0, v0, $dialogCodeField - invoke-static {v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->disableFullscreenAds(I)Z - move-result v0 - if-eqz v0, :show - return-void - """, ExternalLabel("show", getInstruction(0)) - ) - - // Close fullscreen ads - // Find the instruction whose name is "show" in [MethodReference] and click the 'AlertDialog.BUTTON_POSITIVE' button. - // In this case, an instruction for 'getButton' must be added to smali, not in integrations - // (This custom dialog cannot be cast to [AlertDialog] or [Dialog]) - val dialogIndex = getTargetIndexWithMethodReferenceNameOrThrow("show") - val dialogReference = getInstruction(dialogIndex).reference - val dialogDefiningClass = (dialogReference as MethodReference).definingClass - val getButtonMethod = context.findClass(dialogDefiningClass)!! - .mutableClass.methods.first { method -> - method.parameters == listOf("I") - && method.returnType == "Landroid/widget/Button;" - } - val getButtonCall = - dialogDefiningClass + "->" + getButtonMethod.name + "(I)Landroid/widget/Button;" - val dialogRegister = getInstruction(dialogIndex).registerC - val freeIndex = getTargetIndexOrThrow(dialogIndex, Opcode.IF_EQZ) - val freeRegister = getInstruction(freeIndex).registerA + if (parameterTypes.firstOrNull() != "[B") { + val toByteArrayReference = getInstruction( + indexOfFirstInstructionOrThrow { + getReference()?.name == "toByteArray" + } + ).reference - addInstructions( - dialogIndex + 1, """ - # Get the 'AlertDialog.BUTTON_POSITIVE' from custom dialog - # Since this custom dialog cannot be cast to AlertDialog or Dialog, - # It should come from smali, not integrations. - const/4 v$freeRegister, -0x1 - invoke-virtual {v$dialogRegister, v$freeRegister}, $getButtonCall - move-result-object v$freeRegister - invoke-static {v$freeRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->setCloseButton(Landroid/widget/Button;)V + prependInstructions += """ + invoke-virtual {v0}, $toByteArrayReference + move-result-object v0 """ + } + + // Disable fullscreen ads + addInstructionsWithLabels( + 0, prependInstructions + """ + check-cast v1, ${dialogCodeField.definingClass} + iget v1, v1, $dialogCodeField + invoke-static {v0, v1}, $INTEGRATIONS_CLASS_DESCRIPTOR->disableFullscreenAds([BI)Z + move-result v1 + if-eqz v1, :show + return-void + """, ExternalLabel("show", getInstruction(0)) ) } } - - private companion object { - const val INTEGRATIONS_CLASS_DESCRIPTOR = - "$PATCHES_PATH/FullscreenAdsPatch;" - } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/captions/BaseAutoCaptionsPatch.kt b/src/main/kotlin/app/revanced/patches/shared/captions/BaseAutoCaptionsPatch.kt new file mode 100644 index 0000000000..1ae96bccb2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/captions/BaseAutoCaptionsPatch.kt @@ -0,0 +1,56 @@ +package app.revanced.patches.shared.captions + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.shared.captions.fingerprints.StoryboardRendererDecoderRecommendedLevelFingerprint +import app.revanced.patches.shared.captions.fingerprints.SubtitleTrackFingerprint +import app.revanced.patches.shared.fingerprints.StartVideoInformerFingerprint +import app.revanced.patches.shared.integrations.Constants.PATCHES_PATH +import app.revanced.util.resultOrThrow + +@Patch( + description = "Disable forced auto captions for YouTube or YouTube Music." +) +object BaseAutoCaptionsPatch : BytecodePatch( + setOf( + StartVideoInformerFingerprint, + StoryboardRendererDecoderRecommendedLevelFingerprint, + SubtitleTrackFingerprint, + ) +) { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = + "$PATCHES_PATH/AutoCaptionsPatch;" + + override fun execute(context: BytecodeContext) { + + SubtitleTrackFingerprint.resultOrThrow().mutableMethod.apply { + addInstructionsWithLabels( + 0, """ + invoke-static {}, $INTEGRATIONS_CLASS_DESCRIPTOR->disableAutoCaptions()Z + move-result v0 + if-eqz v0, :disabled + const/4 v0, 0x1 + return v0 + """, ExternalLabel("disabled", getInstruction(0)) + ) + } + + mapOf( + StartVideoInformerFingerprint to 0, + StoryboardRendererDecoderRecommendedLevelFingerprint to 1 + ).forEach { (fingerprint, enabled) -> + fingerprint.resultOrThrow().mutableMethod.addInstructions( + 0, """ + const/4 v0, 0x$enabled + invoke-static {v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->setCaptionsButtonStatus(Z)V + """ + ) + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/autocaptions/fingerprints/StoryboardRendererDecoderRecommendedLevelFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/captions/fingerprints/StoryboardRendererDecoderRecommendedLevelFingerprint.kt similarity index 66% rename from src/main/kotlin/app/revanced/patches/youtube/general/autocaptions/fingerprints/StoryboardRendererDecoderRecommendedLevelFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/captions/fingerprints/StoryboardRendererDecoderRecommendedLevelFingerprint.kt index c20b3d2b90..62875e32cc 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/autocaptions/fingerprints/StoryboardRendererDecoderRecommendedLevelFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/captions/fingerprints/StoryboardRendererDecoderRecommendedLevelFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.youtube.general.autocaptions.fingerprints +package app.revanced.patches.shared.captions.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint @@ -7,6 +7,6 @@ import com.android.tools.smali.dexlib2.AccessFlags internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"), + parameters = listOf("L"), strings = listOf("#-1#") ) diff --git a/src/main/kotlin/app/revanced/patches/shared/fingerprints/SubtitleTrackFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/captions/fingerprints/SubtitleTrackFingerprint.kt similarity index 86% rename from src/main/kotlin/app/revanced/patches/shared/fingerprints/SubtitleTrackFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/captions/fingerprints/SubtitleTrackFingerprint.kt index b974573866..b1a6012a79 100644 --- a/src/main/kotlin/app/revanced/patches/shared/fingerprints/SubtitleTrackFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/captions/fingerprints/SubtitleTrackFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.shared.fingerprints +package app.revanced.patches.shared.captions.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint diff --git a/src/main/kotlin/app/revanced/patches/shared/customspeed/BaseCustomPlaybackSpeedPatch.kt b/src/main/kotlin/app/revanced/patches/shared/customspeed/BaseCustomPlaybackSpeedPatch.kt index ca1e9b1de0..b2e29869b1 100644 --- a/src/main/kotlin/app/revanced/patches/shared/customspeed/BaseCustomPlaybackSpeedPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/customspeed/BaseCustomPlaybackSpeedPatch.kt @@ -8,14 +8,14 @@ import app.revanced.patcher.patch.BytecodePatch import app.revanced.patches.shared.customspeed.fingerprints.SpeedArrayGeneratorFingerprint import app.revanced.patches.shared.customspeed.fingerprints.SpeedLimiterFallBackFingerprint import app.revanced.patches.shared.customspeed.fingerprints.SpeedLimiterFingerprint -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithFieldReferenceTypeOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference abstract class BaseCustomPlaybackSpeedPatch( private val descriptor: String, @@ -39,7 +39,9 @@ abstract class BaseCustomPlaybackSpeedPatch( """ ) - val sizeIndex = getTargetIndexWithMethodReferenceNameOrThrow("size") + 1 + val sizeIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "size" + } + 1 val sizeRegister = getInstruction(sizeIndex).registerA addInstructions( @@ -49,7 +51,9 @@ abstract class BaseCustomPlaybackSpeedPatch( """ ) - val arrayIndex = getTargetIndexWithFieldReferenceTypeOrThrow("[F") + val arrayIndex = indexOfFirstInstructionOrThrow { + getReference()?.type == "[F" + } val arrayRegister = getInstruction(arrayIndex).registerA addInstructions( @@ -73,7 +77,7 @@ abstract class BaseCustomPlaybackSpeedPatch( val limiterMinConstIndex = indexOfFirstInstructionOrThrow { (this as? NarrowLiteralInstruction)?.narrowLiteral == 0.25f.toRawBits() } val limiterMaxConstIndex = - getTargetIndexOrThrow(limiterMinConstIndex + 1, Opcode.CONST_HIGH16) + indexOfFirstInstructionOrThrow(limiterMinConstIndex + 1, Opcode.CONST_HIGH16) val limiterMinConstDestination = getInstruction(limiterMinConstIndex).registerA diff --git a/src/main/kotlin/app/revanced/patches/shared/dialog/BaseViewerDiscretionDialogPatch.kt b/src/main/kotlin/app/revanced/patches/shared/dialog/BaseViewerDiscretionDialogPatch.kt index 93473d2bad..79000567ef 100644 --- a/src/main/kotlin/app/revanced/patches/shared/dialog/BaseViewerDiscretionDialogPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/dialog/BaseViewerDiscretionDialogPatch.kt @@ -7,10 +7,12 @@ import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patches.shared.dialog.fingerprints.CreateDialogFingerprint -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.getReference import app.revanced.util.getWalkerMethod +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference abstract class BaseViewerDiscretionDialogPatch( private val classDescriptor: String, @@ -22,7 +24,9 @@ abstract class BaseViewerDiscretionDialogPatch( } ) { private fun MutableMethod.invoke(isAgeVerified: Boolean) { - val showDialogIndex = getTargetIndexWithMethodReferenceNameOrThrow("show") + val showDialogIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "show" + } val dialogRegister = getInstruction(showDialogIndex).registerC val methodName = diff --git a/src/main/kotlin/app/revanced/patches/shared/drawable/DrawableColorPatch.kt b/src/main/kotlin/app/revanced/patches/shared/drawable/DrawableColorPatch.kt index cf7ee092d8..c0c53cf1e6 100644 --- a/src/main/kotlin/app/revanced/patches/shared/drawable/DrawableColorPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/drawable/DrawableColorPatch.kt @@ -6,9 +6,11 @@ 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.shared.drawable.fingerprints.DrawableFingerprint -import app.revanced.util.getTargetIndexWithMethodReferenceNameReversedOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference object DrawableColorPatch : BytecodePatch( setOf(DrawableFingerprint) @@ -17,26 +19,26 @@ object DrawableColorPatch : BytecodePatch( DrawableFingerprint.resultOrThrow().mutableMethod.apply { insertMethod = this - insertIndex = getTargetIndexWithMethodReferenceNameReversedOrThrow("setColor") + insertIndex = indexOfFirstInstructionReversedOrThrow { + getReference()?.name == "setColor" + } insertRegister = getInstruction(insertIndex).registerD } } - private var offset = 0 - + private lateinit var insertMethod: MutableMethod private var insertIndex: Int = 0 private var insertRegister: Int = 0 - private lateinit var insertMethod: MutableMethod - + private var offset = 0 fun injectCall( methodDescriptor: String ) { insertMethod.addInstructions( insertIndex + offset, """ - invoke-static {v$insertRegister}, $methodDescriptor - move-result v$insertRegister - """ + invoke-static {v$insertRegister}, $methodDescriptor + move-result v$insertRegister + """ ) offset += 2 } diff --git a/src/main/kotlin/app/revanced/patches/shared/fingerprints/StartVideoInformerFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/fingerprints/StartVideoInformerFingerprint.kt new file mode 100644 index 0000000000..ca8d33f53d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/fingerprints/StartVideoInformerFingerprint.kt @@ -0,0 +1,28 @@ +package app.revanced.patches.shared.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +internal object StartVideoInformerFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + opcodes = listOf( + Opcode.INVOKE_INTERFACE, + Opcode.RETURN_VOID + ), + strings = listOf("pc"), + customFingerprint = custom@{ methodDef, _ -> + if (methodDef.implementation == null) + return@custom false + + methodDef.implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + instruction.opcode == Opcode.CONST_STRING + } + .map { (index, _) -> index } + .size == 1 + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/VideoLengthFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/fingerprints/VideoLengthFingerprint.kt similarity index 84% rename from src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/VideoLengthFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/fingerprints/VideoLengthFingerprint.kt index 34b4eb74af..bd916f6a64 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/VideoLengthFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/fingerprints/VideoLengthFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.youtube.video.information.fingerprints +package app.revanced.patches.shared.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint diff --git a/src/main/kotlin/app/revanced/patches/shared/gms/BaseGmsCoreSupportPatch.kt b/src/main/kotlin/app/revanced/patches/shared/gms/BaseGmsCoreSupportPatch.kt index 082c25e906..542b491e00 100644 --- a/src/main/kotlin/app/revanced/patches/shared/gms/BaseGmsCoreSupportPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/gms/BaseGmsCoreSupportPatch.kt @@ -18,14 +18,12 @@ import app.revanced.patches.shared.gms.fingerprints.CastContextFetchFingerprint import app.revanced.patches.shared.gms.fingerprints.CastDynamiteModuleFingerprint import app.revanced.patches.shared.gms.fingerprints.CastDynamiteModuleV2Fingerprint import app.revanced.patches.shared.gms.fingerprints.CertificateFingerprint -import app.revanced.patches.shared.gms.fingerprints.CertificateFingerprint.GET_PACKAGE_NAME_METHOD_REFERENCE import app.revanced.patches.shared.gms.fingerprints.GmsCoreSupportFingerprint import app.revanced.patches.shared.gms.fingerprints.GooglePlayUtilityFingerprint import app.revanced.patches.shared.gms.fingerprints.PrimeMethodFingerprint import app.revanced.patches.shared.gms.fingerprints.ServiceCheckFingerprint import app.revanced.patches.shared.integrations.Constants.PATCHES_PATH import app.revanced.util.getReference -import app.revanced.util.getTargetIndexWithReference import app.revanced.util.resultOrThrow import app.revanced.util.returnEarly import com.android.tools.smali.dexlib2.Opcode @@ -178,7 +176,7 @@ abstract class BaseGmsCoreSupportPatch( CertificateFingerprint.result?.mutableClass?.methods?.forEach { mutableMethod -> mutableMethod.apply { val getPackageNameIndex = - getTargetIndexWithReference(GET_PACKAGE_NAME_METHOD_REFERENCE) + CertificateFingerprint.indexOfGetPackageNameInstruction(this) if (getPackageNameIndex > -1) { val targetRegister = diff --git a/src/main/kotlin/app/revanced/patches/shared/gms/fingerprints/CertificateFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/gms/fingerprints/CertificateFingerprint.kt index 0632e3b1fd..29d08c1785 100644 --- a/src/main/kotlin/app/revanced/patches/shared/gms/fingerprints/CertificateFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/gms/fingerprints/CertificateFingerprint.kt @@ -1,20 +1,28 @@ package app.revanced.patches.shared.gms.fingerprints import app.revanced.patcher.extensions.or -import app.revanced.patches.shared.gms.fingerprints.CertificateFingerprint.GET_PACKAGE_NAME_METHOD_REFERENCE -import app.revanced.util.fingerprint.ReferenceFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.shared.gms.fingerprints.CertificateFingerprint.indexOfGetPackageNameInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.reference.MethodReference /** * Method which the package name is used to check the app signature. */ -internal object CertificateFingerprint : ReferenceFingerprint( +internal object CertificateFingerprint : MethodFingerprint( returnType = "Ljava/lang/String;", accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL, parameters = emptyList(), strings = listOf("X.509", "user", "S"), - reference = { GET_PACKAGE_NAME_METHOD_REFERENCE } + customFingerprint = { methodDef, _ -> + indexOfGetPackageNameInstruction(methodDef) >= 0 + } ) { - const val GET_PACKAGE_NAME_METHOD_REFERENCE = - "Landroid/content/Context;->getPackageName()Ljava/lang/String;" + fun indexOfGetPackageNameInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + getReference()?.toString() == "Landroid/content/Context;->getPackageName()Ljava/lang/String;" + } } diff --git a/src/main/kotlin/app/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch.kt b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch.kt index 8adebbc473..3e918ed47f 100644 --- a/src/main/kotlin/app/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch.kt @@ -102,8 +102,8 @@ abstract class BaseCronetImageUrlHookPatch( // Add a helper get method that returns the URL field. RequestFingerprint.resultOrThrow().apply { // The url is the only string field that is set inside the constructor. - val urlFieldInstruction = mutableMethod.getInstructions().single { - if (it.opcode != Opcode.IPUT_OBJECT) return@single false + val urlFieldInstruction = mutableMethod.getInstructions().first { + if (it.opcode != Opcode.IPUT_OBJECT) return@first false val reference = (it as ReferenceInstruction).reference as FieldReference reference.type == "Ljava/lang/String;" diff --git a/src/main/kotlin/app/revanced/patches/shared/integrations/BaseIntegrationsPatch.kt b/src/main/kotlin/app/revanced/patches/shared/integrations/BaseIntegrationsPatch.kt index 3a33da4e97..4821a51081 100644 --- a/src/main/kotlin/app/revanced/patches/shared/integrations/BaseIntegrationsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/integrations/BaseIntegrationsPatch.kt @@ -4,10 +4,11 @@ import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.PatchException import app.revanced.patches.shared.integrations.BaseIntegrationsPatch.IntegrationsFingerprint.IRegisterResolver import app.revanced.patches.shared.integrations.Constants.INTEGRATIONS_UTILS_CLASS_DESCRIPTOR -import app.revanced.util.resultOrThrow +import app.revanced.util.exception +import app.revanced.util.findMethodOrThrow +import app.revanced.util.isDeprecated import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.ClassDef import com.android.tools.smali.dexlib2.iface.Method @@ -17,11 +18,7 @@ abstract class BaseIntegrationsPatch( ) : BytecodePatch(hooks) { override fun execute(context: BytecodeContext) { - if (context.findClass(INTEGRATIONS_UTILS_CLASS_DESCRIPTOR) == null) { - throw PatchException( - "Integrations have not been merged yet. This patch can not succeed without merging the integrations.", - ) - } + context.findMethodOrThrow(INTEGRATIONS_UTILS_CLASS_DESCRIPTOR) hooks.forEach { hook -> hook.invoke(INTEGRATIONS_UTILS_CLASS_DESCRIPTOR) @@ -53,16 +50,21 @@ abstract class BaseIntegrationsPatch( ) { fun invoke(integrationsDescriptor: String) { - resultOrThrow().mutableMethod.let { method -> - val insertIndex = insertIndexResolver(method) - val contextRegister = contextRegisterResolver(method) + val method = result?.mutableMethod + ?: if (!isDeprecated()) { + throw exception + } else { + return + } - method.addInstruction( - insertIndex, - "invoke-static/range { $contextRegister .. $contextRegister }, " + - "$integrationsDescriptor->setContext(Landroid/content/Context;)V", - ) - } + val insertIndex = insertIndexResolver(method) + val contextRegister = contextRegisterResolver(method) + + method.addInstruction( + insertIndex, + "invoke-static/range { $contextRegister .. $contextRegister }, " + + "$integrationsDescriptor->setContext(Landroid/content/Context;)V", + ) } interface IHookInsertIndexResolver : (Method) -> Int { diff --git a/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt b/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt index 67d4c54a63..5f30924095 100644 --- a/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt @@ -5,84 +5,112 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions +import app.revanced.patcher.extensions.or import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.integrations.Constants.COMPONENTS_PATH +import app.revanced.patches.shared.litho.fingerprints.ByteBufferFingerprint import app.revanced.patches.shared.litho.fingerprints.EmptyComponentsFingerprint -import app.revanced.patches.shared.litho.fingerprints.LithoFilterPatchConstructorFingerprint import app.revanced.patches.shared.litho.fingerprints.PathBuilderFingerprint -import app.revanced.patches.shared.litho.fingerprints.SetByteBufferFingerprint -import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.getTargetIndexWithFieldReferenceTypeOrThrow +import app.revanced.util.findMethodsOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstStringInstructionOrThrow import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c +import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.immutable.ImmutableMethod +import com.android.tools.smali.dexlib2.util.MethodUtil import java.io.Closeable @Suppress("SpellCheckingInspection", "unused") object LithoFilterPatch : BytecodePatch( setOf( + ByteBufferFingerprint, EmptyComponentsFingerprint, - LithoFilterPatchConstructorFingerprint, - SetByteBufferFingerprint ) ), Closeable { private const val INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR = "$COMPONENTS_PATH/LithoFilterPatch;" - private const val INTEGRATIONS_FILER_CLASS_DESCRIPTOR = - "$COMPONENTS_PATH/Filter;" + private const val INTEGRATIONS_FILER_ARRAY_DESCRIPTOR = + "[$COMPONENTS_PATH/Filter;" + + private lateinit var filterArrayMethod: MutableMethod + private var filterCount = 0 internal lateinit var addFilter: (String) -> Unit private set - private lateinit var emptyComponentMethod: MutableMethod - - private lateinit var emptyComponentLabel: String - private lateinit var emptyComponentMethodName: String - - private lateinit var pathBuilderMethodCall: String - - private var filterCount = 0 - override fun execute(context: BytecodeContext) { - SetByteBufferFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val insertIndex = getTargetIndexOrThrow(Opcode.IF_EQZ) + 1 + // region Pass the buffer into Integrations. - addInstruction( - insertIndex, - "invoke-static { p2 }, $INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V" - ) - } - } + ByteBufferFingerprint.resultOrThrow().mutableMethod.addInstruction( + 0, + "invoke-static { p2 }, $INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V" + ) - EmptyComponentsFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - // resolves fingerprint. + // endregion + + var (emptyComponentMethod, emptyComponentLabel) = + EmptyComponentsFingerprint.resultOrThrow().let { PathBuilderFingerprint.resolve(context, it.classDef) - emptyComponentMethod = this - emptyComponentMethodName = name + with(it.mutableMethod) { + val emptyComponentMethodIndex = it.scanResult.patternScanResult!!.startIndex + 1 + val emptyComponentMethodReference = + getInstruction(emptyComponentMethodIndex).reference + val emptyComponentFieldReference = + getInstruction(emptyComponentMethodIndex + 2).reference + + val label = """ + move-object/from16 v0, p1 + invoke-static {v0}, $emptyComponentMethodReference + move-result-object v0 + iget-object v0, v0, $emptyComponentFieldReference + return-object v0 + """ + + Pair(this, label) + } + } - val emptyComponentMethodIndex = it.scanResult.patternScanResult!!.startIndex + 1 - val emptyComponentMethodReference = - getInstruction(emptyComponentMethodIndex).reference - val emptyComponentFieldReference = - getInstruction(emptyComponentMethodIndex + 2).reference + fun checkMethodSignatureMatch(pathBuilder: MutableMethod) = emptyComponentMethod.apply { + if (!MethodUtil.methodSignaturesMatch(pathBuilder, this)) { + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + val reference = (instruction as? ReferenceInstruction)?.reference + reference is MethodReference && + MethodUtil.methodSignaturesMatch(pathBuilder, reference) + } + .map { (index, _) -> index } + .reversed() + .forEach { + val insertRegister = + getInstruction(it + 1).registerA + val insertIndex = it + 2 + + addInstructionsWithLabels( + insertIndex, """ + if-nez v$insertRegister, :ignore + """ + emptyComponentLabel, + ExternalLabel("ignore", getInstruction(insertIndex)) + ) + } emptyComponentLabel = """ - move-object/from16 v0, p1 - invoke-static {v0}, $emptyComponentMethodReference - move-result-object v0 - iget-object v0, v0, $emptyComponentFieldReference + const/4 v0, 0x0 return-object v0 """ } @@ -90,65 +118,27 @@ object LithoFilterPatch : BytecodePatch( PathBuilderFingerprint.resultOrThrow().let { it.mutableMethod.apply { - // If the EmptyComponents Method and the PathBuilder Method are different, - // new inject way is required. - // TODO: Refactor LithoFilter patch when support for YouTube 18.29.38 ~ 19.17.41 and YT Music 6.29.58 ~ 6.51.53 is dropped. - if (emptyComponentMethodName != name) { - // In this case, the access modifier of the method that handles PathBuilder is 'AccessFlags.PRIVATE or AccessFlags.FINAL. - // Methods that handle PathBuilder are invoked by methods that handle EmptyComponents. - // 'pathBuilderMethodCall' is a reference that invokes the PathBuilder Method. - pathBuilderMethodCall = "$definingClass->$name(" - for (i in 0 until parameters.size) { - pathBuilderMethodCall += parameterTypes[i] - } - pathBuilderMethodCall += ")$returnType" - - emptyComponentMethod.apply { - // If the return value of the PathBuilder Method is null, - // it means that pathBuilder has been filtered by the LithoFilterPatch. - // (Refer comments below.) - // Returns emptyComponents. - for (index in implementation!!.instructions.size - 1 downTo 0) { - val instruction = getInstruction(index) - if ((instruction as? ReferenceInstruction)?.reference.toString() != pathBuilderMethodCall) - continue - - val insertRegister = - getInstruction(index + 1).registerA - val insertIndex = index + 2 - - addInstructionsWithLabels( - insertIndex, """ - if-nez v$insertRegister, :ignore - """ + emptyComponentLabel, - ExternalLabel("ignore", getInstruction(insertIndex)) - ) - } - } + checkMethodSignatureMatch(this) - // If the EmptyComponents Method and the PathBuilder Method are different, - // PathBuilder Method's returnType cannot cast emptyComponents. - // So just returns null value. - emptyComponentLabel = """ - const/4 v0, 0x0 - return-object v0 - """ + val stringBuilderIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.IPUT_OBJECT && + getReference()?.type == "Ljava/lang/StringBuilder;" } - - val stringBuilderIndex = - getTargetIndexWithFieldReferenceTypeOrThrow("Ljava/lang/StringBuilder;") val stringBuilderRegister = getInstruction(stringBuilderIndex).registerA - val emptyStringIndex = getStringInstructionIndex("") - - val identifierIndex = - getTargetIndexReversedOrThrow(emptyStringIndex, Opcode.IPUT_OBJECT) - val identifierRegister = - getInstruction(identifierIndex).registerA - - val objectIndex = getTargetIndexOrThrow(emptyStringIndex, Opcode.INVOKE_VIRTUAL) - val objectRegister = getInstruction(objectIndex).registerC + val emptyStringIndex = indexOfFirstStringInstructionOrThrow("") + val identifierRegister = getInstruction( + indexOfFirstInstructionReversedOrThrow(emptyStringIndex) { + opcode == Opcode.IPUT_OBJECT + && getReference()?.type == "Ljava/lang/String;" + } + ).registerA + val objectRegister = getInstruction( + indexOfFirstInstructionOrThrow(emptyStringIndex) { + opcode == Opcode.INVOKE_VIRTUAL + } + ).registerC val insertIndex = stringBuilderIndex + 1 @@ -163,30 +153,67 @@ object LithoFilterPatch : BytecodePatch( } } - LithoFilterPatchConstructorFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - removeInstructions(0, 6) - - addFilter = { classDescriptor -> - addInstructions( - 0, """ - new-instance v0, $classDescriptor - invoke-direct {v0}, $classDescriptor->()V - const/16 v3, ${filterCount++} - aput-object v0, v2, v3 - """ + // Create a new method to get the filter array to avoid register conflicts. + // This fixes an issue with Integrations compiled with Android Gradle Plugin 8.3.0+. + // https://github.com/ReVanced/revanced-patches/issues/2818 + val lithoFilterMethods = + context.findMethodsOrThrow(INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR) + + lithoFilterMethods + .first { it.name == "" } + .apply { + val setArrayIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.SPUT_OBJECT && + getReference()?.type == INTEGRATIONS_FILER_ARRAY_DESCRIPTOR + } + val setArrayRegister = + getInstruction(setArrayIndex).registerA + val addedMethodName = "getFilterArray" + + addInstructions( + setArrayIndex, """ + invoke-static {}, $INTEGRATIONS_LITHO_FILER_CLASS_DESCRIPTOR->$addedMethodName()$INTEGRATIONS_FILER_ARRAY_DESCRIPTOR + move-result-object v$setArrayRegister + """ + ) + + filterArrayMethod = ImmutableMethod( + definingClass, + addedMethodName, + emptyList(), + INTEGRATIONS_FILER_ARRAY_DESCRIPTOR, + AccessFlags.PRIVATE or AccessFlags.STATIC, + null, + null, + MutableMethodImplementation(3), + ).toMutable().apply { + addInstruction( + 0, + "return-object v2" ) } + + lithoFilterMethods.add(filterArrayMethod) } + + addFilter = { classDescriptor -> + filterArrayMethod.addInstructions( + 0, + """ + new-instance v0, $classDescriptor + invoke-direct {v0}, $classDescriptor->()V + const/16 v1, ${filterCount++} + aput-object v0, v2, v1 + """ + ) } } - override fun close() = LithoFilterPatchConstructorFingerprint.result!! - .mutableMethod.addInstructions( - 0, """ - const/16 v1, $filterCount - new-array v2, v1, [$INTEGRATIONS_FILER_CLASS_DESCRIPTOR - const/4 v1, 0x1 - """ - ) + override fun close() = filterArrayMethod.addInstructions( + 0, + """ + const/16 v0, $filterCount + new-array v2, v0, $INTEGRATIONS_FILER_ARRAY_DESCRIPTOR + """ + ) } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/SetByteBufferFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/ByteBufferFingerprint.kt similarity index 94% rename from src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/SetByteBufferFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/ByteBufferFingerprint.kt index bfa2fc004c..8a14c41fd9 100644 --- a/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/SetByteBufferFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/ByteBufferFingerprint.kt @@ -5,7 +5,7 @@ import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode -internal object SetByteBufferFingerprint : MethodFingerprint( +internal object ByteBufferFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("I", "Ljava/nio/ByteBuffer;"), diff --git a/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/LithoFilterPatchConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/LithoFilterPatchConstructorFingerprint.kt deleted file mode 100644 index a307628be3..0000000000 --- a/src/main/kotlin/app/revanced/patches/shared/litho/fingerprints/LithoFilterPatchConstructorFingerprint.kt +++ /dev/null @@ -1,14 +0,0 @@ -package app.revanced.patches.shared.litho.fingerprints - -import app.revanced.patcher.extensions.or -import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.patches.shared.integrations.Constants.COMPONENTS_PATH -import com.android.tools.smali.dexlib2.AccessFlags - -internal object LithoFilterPatchConstructorFingerprint : MethodFingerprint( - returnType = "V", - accessFlags = AccessFlags.STATIC or AccessFlags.CONSTRUCTOR, - customFingerprint = { methodDef, _ -> - methodDef.definingClass == "$COMPONENTS_PATH/LithoFilterPatch;" - } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/mainactivity/BaseMainActivityResolvePatch.kt b/src/main/kotlin/app/revanced/patches/shared/mainactivity/BaseMainActivityResolvePatch.kt index 02e3c81355..e34e5d56fe 100644 --- a/src/main/kotlin/app/revanced/patches/shared/mainactivity/BaseMainActivityResolvePatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/mainactivity/BaseMainActivityResolvePatch.kt @@ -7,7 +7,7 @@ import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.PatchException import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod -import app.revanced.util.getTargetIndexOrThrow +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import kotlin.properties.Delegates @@ -38,7 +38,8 @@ abstract class BaseMainActivityResolvePatch( // set onBackPressed method onBackPressedMethod = getMethod("onBackPressed") - onBackPressedMethodIndex = onBackPressedMethod.getTargetIndexOrThrow(Opcode.RETURN_VOID) + onBackPressedMethodIndex = + onBackPressedMethod.indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) // set onConfigurationChanged method onConfigurationChangedMethod = getMethod("onConfigurationChanged") diff --git a/src/main/kotlin/app/revanced/patches/shared/opus/BaseOpusCodecsPatch.kt b/src/main/kotlin/app/revanced/patches/shared/opus/BaseOpusCodecsPatch.kt index 6a8984cf2a..8344971ad5 100644 --- a/src/main/kotlin/app/revanced/patches/shared/opus/BaseOpusCodecsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/opus/BaseOpusCodecsPatch.kt @@ -7,11 +7,13 @@ import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.opus.fingerprints.CodecReferenceFingerprint import app.revanced.patches.shared.opus.fingerprints.CodecSelectorFingerprint -import app.revanced.util.getTargetIndexWithReferenceOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction -import com.android.tools.smali.dexlib2.iface.reference.Reference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference /** * This patch is generally not required for the latest versions of YouTube and YouTube Music. @@ -25,15 +27,14 @@ abstract class BaseOpusCodecsPatch( CodecSelectorFingerprint ) ) { - private lateinit var opusCodecReference: Reference - override fun execute(context: BytecodeContext) { - CodecReferenceFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val targetIndex = getTargetIndexWithReferenceOrThrow("Ljava/util/Set;") - opusCodecReference = getInstruction(targetIndex).reference + val opusCodecReference = with(CodecReferenceFingerprint.resultOrThrow().mutableMethod) { + val codecIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_STATIC && + getReference()?.returnType == "Ljava/util/Set;" } + getInstruction(codecIndex).reference } CodecSelectorFingerprint.resultOrThrow().let { diff --git a/src/main/kotlin/app/revanced/patches/shared/settingmenu/SettingsMenuPatch.kt b/src/main/kotlin/app/revanced/patches/shared/settingmenu/SettingsMenuPatch.kt index 3ed3ba57c8..d6a487dfb5 100644 --- a/src/main/kotlin/app/revanced/patches/shared/settingmenu/SettingsMenuPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/settingmenu/SettingsMenuPatch.kt @@ -8,9 +8,11 @@ import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.shared.integrations.Constants.PATCHES_PATH import app.revanced.patches.shared.settingmenu.fingerprints.SettingsMenuFingerprint import app.revanced.patches.shared.viewgroup.ViewGroupMarginLayoutParamsHookPatch -import app.revanced.util.getTargetIndexWithFieldReferenceTypeOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference @Patch( description = "Hide the settings menu for YouTube or YouTube Music.", @@ -25,8 +27,9 @@ object SettingsMenuPatch : BytecodePatch( override fun execute(context: BytecodeContext) { SettingsMenuFingerprint.resultOrThrow().mutableMethod.apply { - val insertIndex = - getTargetIndexWithFieldReferenceTypeOrThrow("Landroid/support/v7/widget/RecyclerView;") + val insertIndex = indexOfFirstInstructionOrThrow { + getReference()?.type == "Landroid/support/v7/widget/RecyclerView;" + } val insertRegister = getInstruction(insertIndex).registerA addInstruction( diff --git a/src/main/kotlin/app/revanced/patches/shared/spoofappversion/BaseSpoofAppVersionPatch.kt b/src/main/kotlin/app/revanced/patches/shared/spoofappversion/BaseSpoofAppVersionPatch.kt index 9bcca0159b..c8254ad5f4 100644 --- a/src/main/kotlin/app/revanced/patches/shared/spoofappversion/BaseSpoofAppVersionPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/spoofappversion/BaseSpoofAppVersionPatch.kt @@ -6,7 +6,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfReleaseInstruction -import app.revanced.util.getTargetIndexReversedOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction @@ -20,7 +20,8 @@ abstract class BaseSpoofAppVersionPatch( CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().mutableMethod.apply { val versionIndex = indexOfReleaseInstruction(this) + 1 - val insertIndex = getTargetIndexReversedOrThrow(versionIndex, Opcode.IPUT_OBJECT) + val insertIndex = + indexOfFirstInstructionReversedOrThrow(versionIndex, Opcode.IPUT_OBJECT) val insertRegister = getInstruction(insertIndex).registerA addInstructions( diff --git a/src/main/kotlin/app/revanced/patches/shared/viewgroup/ViewGroupMarginLayoutParamsHookPatch.kt b/src/main/kotlin/app/revanced/patches/shared/viewgroup/ViewGroupMarginLayoutParamsHookPatch.kt index 3dfe48d4c2..b504a8a7fe 100644 --- a/src/main/kotlin/app/revanced/patches/shared/viewgroup/ViewGroupMarginLayoutParamsHookPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/viewgroup/ViewGroupMarginLayoutParamsHookPatch.kt @@ -3,12 +3,12 @@ package app.revanced.patches.shared.viewgroup import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.shared.integrations.Constants.INTEGRATIONS_UTILS_CLASS_DESCRIPTOR import app.revanced.patches.shared.viewgroup.fingerprints.ViewGroupMarginFingerprint import app.revanced.patches.shared.viewgroup.fingerprints.ViewGroupMarginParentFingerprint -import app.revanced.util.resultOrThrow +import app.revanced.util.alsoResolve +import app.revanced.util.findMethodOrThrow @Patch( description = "Hook YouTube or YouTube Music to use ViewGroup.MarginLayoutParams in the integration.", @@ -18,26 +18,21 @@ object ViewGroupMarginLayoutParamsHookPatch : BytecodePatch( ) { override fun execute(context: BytecodeContext) { - val method = - context.findClass(INTEGRATIONS_UTILS_CLASS_DESCRIPTOR)?.mutableClass?.methods?.first { method -> - method.name == "hideViewGroupByMarginLayoutParams" - } ?: throw PatchException("Could not find hideViewGroupByMarginLayoutParams method") + val setViewGroupMarginCall = with( + ViewGroupMarginFingerprint.alsoResolve( + context, ViewGroupMarginParentFingerprint + ).mutableMethod + ) { + "$definingClass->$name(Landroid/view/View;II)V" + } - ViewGroupMarginFingerprint.resolve( - context, - ViewGroupMarginParentFingerprint.resultOrThrow().classDef + context.findMethodOrThrow(INTEGRATIONS_UTILS_CLASS_DESCRIPTOR) { + name == "hideViewGroupByMarginLayoutParams" + }.addInstructions( + 0, """ + const/4 v0, 0x0 + invoke-static {p0, v0, v0}, $setViewGroupMarginCall + """ ) - ViewGroupMarginFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val setViewGroupMarginCall = "$definingClass->$name(Landroid/view/View;II)V" - - method.addInstructions( - 0, """ - const/4 v0, 0x0 - invoke-static {p0, v0, v0}, $setViewGroupMarginCall - """ - ) - } - } } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsBytecodePatch.kt index c6470cfcf7..9a9868502c 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsBytecodePatch.kt @@ -43,7 +43,7 @@ object AdsBytecodePatch : BytecodePatch( // litho view, used in 'ShowDialogCommandOuterClass' in innertube ShowDialogCommandFingerprint .resultOrThrow() - .hookLithoFullscreenAds(context) + .hookLithoFullscreenAds() // endregion diff --git a/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsPatch.kt index 3e7c435ee2..613c33dde4 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsPatch.kt @@ -50,12 +50,8 @@ object AdsPatch : BaseResourcePatch( private const val ADS_FILTER_CLASS_DESCRIPTOR = "$COMPONENTS_PATH/AdsFilter;" - private const val FULLSCREEN_ADS_FILTER_CLASS_DESCRIPTOR = - "${app.revanced.patches.shared.integrations.Constants.COMPONENTS_PATH}/FullscreenAdsFilter;" - override fun execute(context: ResourceContext) { LithoFilterPatch.addFilter(ADS_FILTER_CLASS_DESCRIPTOR) - LithoFilterPatch.addFilter(FULLSCREEN_ADS_FILTER_CLASS_DESCRIPTOR) context.forEach { diff --git a/src/main/kotlin/app/revanced/patches/youtube/ads/general/fingerprints/ShowDialogCommandFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/ads/general/fingerprints/ShowDialogCommandFingerprint.kt index 1edafb0b12..e747815bf3 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/ads/general/fingerprints/ShowDialogCommandFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/ads/general/fingerprints/ShowDialogCommandFingerprint.kt @@ -2,7 +2,7 @@ package app.revanced.patches.youtube.ads.general.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.SlidingDialogAnimation -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.Opcode internal object ShowDialogCommandFingerprint : MethodFingerprint( @@ -16,7 +16,7 @@ internal object ShowDialogCommandFingerprint : MethodFingerprint( // 18.43 and earlier has a different first parameter. // Since this fingerprint is somewhat weak, work around by checking for both method parameter signatures. customFingerprint = custom@{ methodDef, _ -> - if (!methodDef.containsWideLiteralInstructionIndex(SlidingDialogAnimation)) { + if (!methodDef.containsWideLiteralInstructionValue(SlidingDialogAnimation)) { return@custom false } // 18.43 and earlier parameters are: "L", "L" diff --git a/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt index 0e513c8f74..1ed6fc0114 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt @@ -7,6 +7,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.patch.PatchException +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.litho.LithoFilterPatch import app.revanced.patches.youtube.feed.components.fingerprints.BreakingNewsFingerprint @@ -19,33 +20,42 @@ import app.revanced.patches.youtube.feed.components.fingerprints.ChannelTabBuild import app.revanced.patches.youtube.feed.components.fingerprints.ChannelTabRendererFingerprint import app.revanced.patches.youtube.feed.components.fingerprints.ElementParserFingerprint import app.revanced.patches.youtube.feed.components.fingerprints.ElementParserParentFingerprint +import app.revanced.patches.youtube.feed.components.fingerprints.EngagementPanelUpdateFingerprint import app.revanced.patches.youtube.feed.components.fingerprints.FilterBarHeightFingerprint import app.revanced.patches.youtube.feed.components.fingerprints.LatestVideosButtonFingerprint +import app.revanced.patches.youtube.feed.components.fingerprints.LinearLayoutManagerItemCountsFingerprint import app.revanced.patches.youtube.feed.components.fingerprints.RelatedChipCloudFingerprint import app.revanced.patches.youtube.feed.components.fingerprints.SearchResultsChipBarFingerprint import app.revanced.patches.youtube.feed.components.fingerprints.ShowMoreButtonFingerprint +import app.revanced.patches.youtube.utils.bottomsheet.BottomSheetHookPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.fingerprints.EngagementPanelBuilderFingerprint import app.revanced.patches.youtube.utils.fingerprints.ScrollTopParentFingerprint import app.revanced.patches.youtube.utils.integrations.Constants.COMPONENTS_PATH import app.revanced.patches.youtube.utils.integrations.Constants.FEED_CLASS_DESCRIPTOR +import app.revanced.patches.youtube.utils.integrations.Constants.FEED_PATH import app.revanced.patches.youtube.utils.navigation.NavigationBarHookPatch import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.CaptionToggleContainer import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceName -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.alsoResolve +import app.revanced.util.getReference +import app.revanced.util.getWalkerMethod +import app.revanced.util.indexOfFirstInstruction import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.util.MethodUtil @Suppress("unused") object FeedComponentsPatch : BaseBytecodePatch( @@ -56,7 +66,8 @@ object FeedComponentsPatch : BaseBytecodePatch( NavigationBarHookPatch::class, PlayerTypeHookPatch::class, SettingsPatch::class, - SharedResourceIdPatch::class + SharedResourceIdPatch::class, + BottomSheetHookPatch::class, ), compatiblePackages = COMPATIBLE_PACKAGE, fingerprints = setOf( @@ -68,14 +79,18 @@ object FeedComponentsPatch : BaseBytecodePatch( ChannelListSubMenuTabletSyntheticFingerprint, ChannelTabRendererFingerprint, ElementParserParentFingerprint, + EngagementPanelBuilderFingerprint, FilterBarHeightFingerprint, LatestVideosButtonFingerprint, + LinearLayoutManagerItemCountsFingerprint, RelatedChipCloudFingerprint, ScrollTopParentFingerprint, SearchResultsChipBarFingerprint, - ShowMoreButtonFingerprint + ShowMoreButtonFingerprint, ) ) { + private const val CAROUSEL_SHELF_FILTER_CLASS_DESCRIPTOR = + "$COMPONENTS_PATH/CarouselShelfFilter;" private const val FEED_COMPONENTS_FILTER_CLASS_DESCRIPTOR = "$COMPONENTS_PATH/FeedComponentsFilter;" private const val FEED_VIDEO_FILTER_CLASS_DESCRIPTOR = @@ -84,6 +99,8 @@ object FeedComponentsPatch : BaseBytecodePatch( "$COMPONENTS_PATH/FeedVideoViewsFilter;" private const val KEYWORD_FILTER_CLASS_DESCRIPTOR = "$COMPONENTS_PATH/KeywordContentFilter;" + private const val RELATED_VIDEO_CLASS_DESCRIPTOR = + "$FEED_PATH/RelatedVideoPatch;" override fun execute(context: BytecodeContext) { @@ -113,8 +130,8 @@ object FeedComponentsPatch : BaseBytecodePatch( // region patch for hide caption button CaptionsButtonFingerprint.resultOrThrow().mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(CaptionToggleContainer) - val insertIndex = getTargetIndexReversedOrThrow(constIndex, Opcode.IF_EQZ) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(CaptionToggleContainer) + val insertIndex = indexOfFirstInstructionReversedOrThrow(constIndex, Opcode.IF_EQZ) val insertRegister = getInstruction(insertIndex).registerA addInstructions( @@ -126,8 +143,8 @@ object FeedComponentsPatch : BaseBytecodePatch( } CaptionsButtonSyntheticFingerprint.resultOrThrow().mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(CaptionToggleContainer) - val targetIndex = getTargetIndexOrThrow(constIndex, Opcode.MOVE_RESULT_OBJECT) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(CaptionToggleContainer) + val targetIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.MOVE_RESULT_OBJECT) val targetRegister = getInstruction(targetIndex).registerA addInstruction( @@ -138,6 +155,62 @@ object FeedComponentsPatch : BaseBytecodePatch( // endregion + // region patch for hide relative video + + fun Method.indexOfEngagementPanelBuilderInstruction(targetMethod: MutableMethod) = + indexOfFirstInstruction { + opcode == Opcode.INVOKE_DIRECT && + MethodUtil.methodSignaturesMatch( + targetMethod, + getReference()!! + ) + } + + EngagementPanelBuilderFingerprint.resultOrThrow().let { + it.mutableClass.methods.filter { method -> + method.indexOfEngagementPanelBuilderInstruction(it.mutableMethod) >= 0 + }.forEach { method -> + method.apply { + val index = indexOfEngagementPanelBuilderInstruction(it.mutableMethod) + val register = getInstruction(index + 1).registerA + + addInstruction( + index + 2, + "invoke-static {v$register}, " + + "$RELATED_VIDEO_CLASS_DESCRIPTOR->showEngagementPanel(Ljava/lang/Object;)V" + ) + } + } + } + + EngagementPanelUpdateFingerprint.alsoResolve( + context, EngagementPanelBuilderFingerprint + ).mutableMethod.addInstruction( + 0, + "invoke-static {}, $RELATED_VIDEO_CLASS_DESCRIPTOR->hideEngagementPanel()V" + ) + + // BytecodeUtils.getWalkerMethod must be used here + // Otherwise, MethodWalker finds the wrong class in YouTube 18.29.38: + // https://github.com/ReVanced/revanced-patcher/issues/309 + LinearLayoutManagerItemCountsFingerprint.resultOrThrow().let { + val methodWalker = + it.getWalkerMethod(context, it.scanResult.patternScanResult!!.endIndex) + methodWalker.apply { + val index = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT) + val register = getInstruction(index).registerA + + addInstructions( + index + 1, """ + invoke-static {v$register}, $RELATED_VIDEO_CLASS_DESCRIPTOR->overrideItemCounts(I)I + move-result v$register + """ + ) + } + } + + // endregion + // region patch for hide subscriptions channel section for tablet arrayOf( @@ -198,7 +271,7 @@ object FeedComponentsPatch : BaseBytecodePatch( && reference.returnType.startsWith("L") } - val objectIndex = getTargetIndexOrThrow(Opcode.MOVE_OBJECT) + val objectIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_OBJECT) val objectRegister = getInstruction(objectIndex).registerA val jumpIndex = it.scanResult.patternScanResult!!.startIndex @@ -253,7 +326,9 @@ object FeedComponentsPatch : BaseBytecodePatch( ChannelTabRendererFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val iteratorIndex = getTargetIndexWithMethodReferenceName("hasNext") + val iteratorIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "hasNext" + } val iteratorRegister = getInstruction(iteratorIndex).registerC @@ -265,7 +340,8 @@ object FeedComponentsPatch : BaseBytecodePatch( && reference.parameterTypes == channelTabBuilderMethod.parameterTypes } - val objectIndex = getTargetIndexReversedOrThrow(targetIndex, Opcode.IGET_OBJECT) + val objectIndex = + indexOfFirstInstructionReversedOrThrow(targetIndex, Opcode.IGET_OBJECT) val objectInstruction = getInstruction(objectIndex) val objectReference = getInstruction(objectIndex).reference @@ -285,6 +361,7 @@ object FeedComponentsPatch : BaseBytecodePatch( // endregion + LithoFilterPatch.addFilter(CAROUSEL_SHELF_FILTER_CLASS_DESCRIPTOR) LithoFilterPatch.addFilter(FEED_COMPONENTS_FILTER_CLASS_DESCRIPTOR) LithoFilterPatch.addFilter(FEED_VIDEO_FILTER_CLASS_DESCRIPTOR) LithoFilterPatch.addFilter(FEED_VIDEO_VIEWS_FILTER_CLASS_DESCRIPTOR) diff --git a/src/main/kotlin/app/revanced/patches/youtube/feed/components/fingerprints/EngagementPanelUpdateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/feed/components/fingerprints/EngagementPanelUpdateFingerprint.kt new file mode 100644 index 0000000000..4d378d659a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/feed/components/fingerprints/EngagementPanelUpdateFingerprint.kt @@ -0,0 +1,21 @@ +package app.revanced.patches.youtube.feed.components.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +internal object EngagementPanelUpdateFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, + parameters = listOf("L", "Z"), + customFingerprint = { methodDef, _ -> + methodDef.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference().toString() == "Ljava/util/ArrayDeque;->pop()Ljava/lang/Object;" + } >= 0 + } +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/feed/components/fingerprints/LinearLayoutManagerItemCountsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/feed/components/fingerprints/LinearLayoutManagerItemCountsFingerprint.kt new file mode 100644 index 0000000000..0a4dc4688d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/feed/components/fingerprints/LinearLayoutManagerItemCountsFingerprint.kt @@ -0,0 +1,19 @@ +package app.revanced.patches.youtube.feed.components.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +internal object LinearLayoutManagerItemCountsFingerprint : MethodFingerprint( + returnType = "I", + accessFlags = AccessFlags.FINAL.value, + parameters = listOf("L", "L", "L", "Z"), + opcodes = listOf( + Opcode.IF_NEZ, + Opcode.IF_LEZ, + Opcode.INVOKE_VIRTUAL, + ), + customFingerprint = { methodDef, _ -> + methodDef.definingClass == "Landroid/support/v7/widget/LinearLayoutManager;" + } +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/audiotracks/AudioTracksPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/audiotracks/AudioTracksPatch.kt index bfa0c5270d..a5a087b43f 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/audiotracks/AudioTracksPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/audiotracks/AudioTracksPatch.kt @@ -7,7 +7,7 @@ import app.revanced.patches.youtube.general.audiotracks.fingerprints.StreamingMo import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexWithReferenceOrThrow +import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow @@ -15,6 +15,7 @@ import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Suppress("unused") object AudioTracksPatch : BaseBytecodePatch( @@ -32,15 +33,14 @@ object AudioTracksPatch : BaseBytecodePatch( opcode == Opcode.CHECK_CAST && (this as ReferenceInstruction).reference.toString() == "Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;" } - val arrayListIndex = getTargetIndexWithReferenceOrThrow( - formatStreamModelIndex, - "Ljava/util/List;->add(Ljava/lang/Object;)Z" - ) - val insertIndex = - getTargetIndexWithReferenceOrThrow( - arrayListIndex, - "Ljava/util/List;->isEmpty()Z" - ) + 2 + val arrayListIndex = indexOfFirstInstructionOrThrow(formatStreamModelIndex) { + opcode == Opcode.INVOKE_INTERFACE && + getReference()?.toString() == "Ljava/util/List;->add(Ljava/lang/Object;)Z" + } + val insertIndex = indexOfFirstInstructionOrThrow(arrayListIndex) { + opcode == Opcode.INVOKE_INTERFACE && + getReference()?.toString() == "Ljava/util/List;->isEmpty()Z" + } + 2 val formatStreamModelRegister = getInstruction(formatStreamModelIndex).registerA diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/autocaptions/AutoCaptionsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/autocaptions/AutoCaptionsPatch.kt index 4ed2f9a3da..763631b01e 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/autocaptions/AutoCaptionsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/autocaptions/AutoCaptionsPatch.kt @@ -1,57 +1,23 @@ package app.revanced.patches.youtube.general.autocaptions import app.revanced.patcher.data.BytecodeContext -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.util.smali.ExternalLabel -import app.revanced.patches.shared.fingerprints.SubtitleTrackFingerprint -import app.revanced.patches.youtube.general.autocaptions.fingerprints.StoryboardRendererDecoderRecommendedLevelFingerprint +import app.revanced.patches.shared.captions.BaseAutoCaptionsPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE -import app.revanced.patches.youtube.utils.fingerprints.StartVideoInformerFingerprint -import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.util.patch.BaseBytecodePatch -import app.revanced.util.resultOrThrow @Suppress("unused") object AutoCaptionsPatch : BaseBytecodePatch( name = "Disable auto captions", description = "Adds an option to disable captions from being automatically enabled.", - dependencies = setOf(SettingsPatch::class), - compatiblePackages = COMPATIBLE_PACKAGE, - fingerprints = setOf( - SubtitleTrackFingerprint, - StartVideoInformerFingerprint, - StoryboardRendererDecoderRecommendedLevelFingerprint, - ) + dependencies = setOf( + BaseAutoCaptionsPatch::class, + SettingsPatch::class + ), + compatiblePackages = COMPATIBLE_PACKAGE ) { override fun execute(context: BytecodeContext) { - SubtitleTrackFingerprint.resultOrThrow().mutableMethod.apply { - addInstructionsWithLabels( - 0, """ - invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->disableAutoCaptions()Z - move-result v0 - if-eqz v0, :disabled - const/4 v0, 0x1 - return v0 - """, ExternalLabel("disabled", getInstruction(0)) - ) - } - - mapOf( - StartVideoInformerFingerprint to 0, - StoryboardRendererDecoderRecommendedLevelFingerprint to 1 - ).forEach { (fingerprint, enabled) -> - fingerprint.resultOrThrow().mutableMethod.addInstructions( - 0, """ - const/4 v0, 0x$enabled - invoke-static {v0}, $GENERAL_CLASS_DESCRIPTOR->setCaptionsButtonStatus(Z)V - """ - ) - } - /** * Add settings */ diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt index 297c3a8784..dded8f6318 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt @@ -27,9 +27,9 @@ import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_CLASS_D import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.AccountSwitcherAccessibility import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceName -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -37,6 +37,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.util.MethodUtil @Suppress("unused") @@ -171,10 +172,13 @@ object LayoutComponentsPatch : BaseBytecodePatch( AccountSwitcherAccessibilityLabelFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(AccountSwitcherAccessibility) - val insertIndex = getTargetIndexOrThrow(constIndex, Opcode.IF_EQZ) - val setVisibilityIndex = - getTargetIndexWithMethodReferenceName(insertIndex, "setVisibility") + val constIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(AccountSwitcherAccessibility) + val insertIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.IF_EQZ) + val setVisibilityIndex = indexOfFirstInstructionOrThrow(insertIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setVisibility" + } val visibilityRegister = getInstruction(setVisibilityIndex).registerD @@ -209,8 +213,8 @@ object LayoutComponentsPatch : BaseBytecodePatch( // region patch for hide tooltip content TooltipContentFullscreenFingerprint.resultOrThrow().mutableMethod.apply { - val literalIndex = getWideLiteralInstructionIndex(45384061) - val targetIndex = getTargetIndexOrThrow(literalIndex, Opcode.MOVE_RESULT) + val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow(45384061) + val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT) val targetRegister = getInstruction(targetIndex).registerA addInstruction( diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt index f1fe0d3f1c..688d344548 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt @@ -19,6 +19,7 @@ import app.revanced.patches.youtube.utils.pip.PiPStateHookPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.util.alsoResolve +import app.revanced.util.findMethodOrThrow import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch @@ -87,23 +88,22 @@ object DownloadActionsPatch : BaseBytecodePatch( ?: throw PatchException("Could not find onClickListenerClass") } - context.findClass(onClickListenerClass) - ?.mutableClass - ?.methods - ?.first { method -> method.name == "onClick" }?.apply { - val insertIndex = indexOfFirstInstructionOrThrow { - opcode == Opcode.INVOKE_STATIC - && getReference()?.name == "isEmpty" - } - val insertRegister = getInstruction(insertIndex).registerC + context.findMethodOrThrow(onClickListenerClass) { + name == "onClick" + }.apply { + val insertIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_STATIC + && getReference()?.name == "isEmpty" + } + val insertRegister = getInstruction(insertIndex).registerC - addInstructions( - insertIndex, """ - invoke-static {v$insertRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->inAppPlaylistDownloadButtonOnClick(Ljava/lang/String;)Ljava/lang/String; - move-result-object v$insertRegister - """ - ) - } ?: throw PatchException("Could not find class $onClickListenerClass") + addInstructions( + insertIndex, """ + invoke-static {v$insertRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->inAppPlaylistDownloadButtonOnClick(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$insertRegister + """ + ) + } OfflinePlaylistEndpointFingerprint.resultOrThrow().mutableMethod.apply { val playlistIdParameter = parameterTypes.indexOf("Ljava/lang/String;") + 1 @@ -176,7 +176,7 @@ object DownloadActionsPatch : BaseBytecodePatch( SettingsPatch.addPreference( arrayOf( "PREFERENCE_SCREEN: GENERAL", - "PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS", + "SETTINGS: HOOK_BUTTONS", "SETTINGS: HOOK_DOWNLOAD_ACTIONS" ) ) diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/layoutswitch/LayoutSwitchPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/layoutswitch/LayoutSwitchPatch.kt index 9b38b5f049..f4c8c61390 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/layoutswitch/LayoutSwitchPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/layoutswitch/LayoutSwitchPatch.kt @@ -10,8 +10,7 @@ import app.revanced.patches.youtube.general.layoutswitch.fingerprints.LayoutSwit import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -34,7 +33,7 @@ object LayoutSwitchPatch : BaseBytecodePatch( GetFormFactorFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val jumpIndex = getTargetIndexReversedOrThrow(Opcode.SGET_OBJECT) + val jumpIndex = indexOfFirstInstructionReversedOrThrow(Opcode.SGET_OBJECT) addInstructionsWithLabels( 0, """ @@ -56,7 +55,7 @@ object LayoutSwitchPatch : BaseBytecodePatch( LayoutSwitchFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = getTargetIndexOrThrow(Opcode.IF_NEZ) + val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.IF_NEZ) val insertRegister = getInstruction(insertIndex).registerA addInstructions( diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/loadingscreen/GradientLoadingScreenPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/loadingscreen/GradientLoadingScreenPatch.kt index b6d844a468..2bb8182cb6 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/loadingscreen/GradientLoadingScreenPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/loadingscreen/GradientLoadingScreenPatch.kt @@ -6,7 +6,7 @@ import app.revanced.patches.youtube.general.loadingscreen.fingerprints.GradientL import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.literalInstructionBooleanHook +import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.patch.BaseBytecodePatch @Suppress("unused") @@ -29,7 +29,7 @@ object GradientLoadingScreenPatch : BaseBytecodePatch( GradientLoadingScreenPrimaryFingerprint to 45412406, GradientLoadingScreenSecondaryFingerprint to 45418917 ).forEach { (fingerprint, literal) -> - fingerprint.literalInstructionBooleanHook( + fingerprint.injectLiteralInstructionBooleanCall( literal, "$GENERAL_CLASS_DESCRIPTOR->enableGradientLoadingScreen()Z" ) diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch.kt index b5a2529db7..8482e06f72 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch.kt @@ -44,8 +44,8 @@ import app.revanced.util.fingerprint.LiteralValueFingerprint import app.revanced.util.getReference import app.revanced.util.getWalkerMethod import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.indexOfWideLiteralInstructionOrThrow -import app.revanced.util.literalInstructionBooleanHook +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.AccessFlags @@ -170,7 +170,7 @@ object MiniplayerPatch : BaseBytecodePatch( } if (SettingsPatch.upward1925) { - MiniplayerModernEnabledFingerprint.literalInstructionBooleanHook( + MiniplayerModernEnabledFingerprint.injectLiteralInstructionBooleanCall( 45622882, "$INTEGRATIONS_CLASS_DESCRIPTOR->getModernMiniplayerOverride(Z)Z" ) @@ -181,11 +181,11 @@ object MiniplayerPatch : BaseBytecodePatch( // region Enable double tap action. if (SettingsPatch.upward1925) { - MiniplayerModernConstructorFingerprint.literalInstructionBooleanHook( + MiniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( 45628823, "$INTEGRATIONS_CLASS_DESCRIPTOR->enableMiniplayerDoubleTapAction()Z" ) - MiniplayerModernConstructorFingerprint.literalInstructionBooleanHook( + MiniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( 45630429, "$INTEGRATIONS_CLASS_DESCRIPTOR->getModernMiniplayerOverride(Z)Z" ) @@ -211,7 +211,8 @@ object MiniplayerPatch : BaseBytecodePatch( YtOutlinePictureInPictureWhite to YtOutlineXWhite, YtOutlineXWhite to YtOutlinePictureInPictureWhite, ).forEach { (originalResource, replacementResource) -> - val imageResourceIndex = indexOfWideLiteralInstructionOrThrow(originalResource) + val imageResourceIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(originalResource) val register = getInstruction(imageResourceIndex).registerA @@ -321,7 +322,7 @@ object MiniplayerPatch : BaseBytecodePatch( // region Enable drag and drop. if (SettingsPatch.upward1923) { - MiniplayerModernDragAndDropFingerprint.literalInstructionBooleanHook( + MiniplayerModernDragAndDropFingerprint.injectLiteralInstructionBooleanCall( 45628752, "$INTEGRATIONS_CLASS_DESCRIPTOR->enableMiniplayerDragAndDrop()Z" ) @@ -388,7 +389,7 @@ object MiniplayerPatch : BaseBytecodePatch( ) { resultOrThrow().mutableMethod.apply { val imageViewIndex = indexOfFirstInstructionOrThrow( - indexOfWideLiteralInstructionOrThrow(literalValue) + indexOfFirstWideLiteralInstructionValueOrThrow(literalValue) ) { opcode == Opcode.CHECK_CAST && getReference()?.type == hookedClassType } diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/fingerprints/MiniplayerModernConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/fingerprints/MiniplayerModernConstructorFingerprint.kt index 47b8480c31..eba01fd68e 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/fingerprints/MiniplayerModernConstructorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/fingerprints/MiniplayerModernConstructorFingerprint.kt @@ -4,7 +4,7 @@ import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.youtube.general.miniplayer.fingerprints.MiniplayerModernConstructorFingerprint.constructorMethodCount import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.util.MethodUtil @@ -13,7 +13,7 @@ internal object MiniplayerModernConstructorFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, parameters = listOf("L"), customFingerprint = custom@{ methodDef, classDef -> - if (!methodDef.containsWideLiteralInstructionIndex(45623000)) // Magic number found in the constructor. + if (!methodDef.containsWideLiteralInstructionValue(45623000)) // Magic number found in the constructor. return@custom false classDef.methods.forEach { @@ -24,8 +24,8 @@ internal object MiniplayerModernConstructorFingerprint : MethodFingerprint( return@custom true // Double tap action (Used in YouTube 19.25.39+). - methodDef.containsWideLiteralInstructionIndex(45628823) - && methodDef.containsWideLiteralInstructionIndex(45630429) + methodDef.containsWideLiteralInstructionValue(45628823) + && methodDef.containsWideLiteralInstructionValue(45630429) } ) { private var constructorMethodCount = 0 diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/music/YouTubeMusicActionsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/music/YouTubeMusicActionsPatch.kt new file mode 100644 index 0000000000..9b49e86034 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/general/music/YouTubeMusicActionsPatch.kt @@ -0,0 +1,109 @@ +package app.revanced.patches.youtube.general.music + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction +import app.revanced.patches.youtube.general.music.fingerprints.AppDeepLinkFingerprint +import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.gms.GmsCoreSupportResourcePatch.PackageNameYouTubeMusic +import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_PATH +import app.revanced.patches.youtube.utils.settings.ResourceUtils.addEntryValues +import app.revanced.patches.youtube.utils.settings.SettingsBytecodePatch +import app.revanced.patches.youtube.utils.settings.SettingsPatch +import app.revanced.util.findMethodOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.patch.BaseBytecodePatch +import app.revanced.util.resultOrThrow +import app.revanced.util.valueOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import java.io.Closeable + +@Suppress("unused") +object YouTubeMusicActionsPatch : BaseBytecodePatch( + name = "Hook YouTube Music actions", + description = "Adds support for opening music in RVX Music using the in-app YouTube Music button.", + dependencies = setOf(SettingsPatch::class), + compatiblePackages = COMPATIBLE_PACKAGE, + fingerprints = setOf(AppDeepLinkFingerprint) +), Closeable { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = + "$GENERAL_PATH/YouTubeMusicActionsPatch;" + + override fun execute(context: BytecodeContext) { + + AppDeepLinkFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + val packageNameIndex = it.scanResult.patternScanResult!!.startIndex + val packageNameField = + getInstruction(packageNameIndex).reference.toString() + + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + instruction.opcode == Opcode.IGET_OBJECT && + instruction.getReference() + ?.toString() == packageNameField + } + .map { (index, _) -> index } + .reversed() + .forEach { index -> + val register = getInstruction(index).registerA + + addInstructions( + index + 1, """ + invoke-static {v$register}, $INTEGRATIONS_CLASS_DESCRIPTOR->overridePackageName(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$register + """ + ) + } + } + } + + /** + * Add settings + */ + SettingsPatch.addPreference( + arrayOf( + "PREFERENCE_SCREEN: GENERAL", + "SETTINGS: HOOK_BUTTONS", + "SETTINGS: HOOK_YOUTUBE_MUSIC_ACTIONS" + ) + ) + + SettingsPatch.updatePatchStatus(this) + } + + override fun close() { + if (SettingsPatch.containsPatch("GmsCore support")) { + val musicPackageName = PackageNameYouTubeMusic.valueOrThrow() + SettingsPatch.contexts.addEntryValues( + "revanced_third_party_youtube_music_label", + "RVX Music" + ) + SettingsPatch.contexts.addEntryValues( + "revanced_third_party_youtube_music_package_name", + musicPackageName + ) + + SettingsBytecodePatch.contexts.findMethodOrThrow(INTEGRATIONS_CLASS_DESCRIPTOR) { + name == "getRVXMusicPackageName" + }.apply { + val replaceIndex = indexOfFirstInstructionOrThrow(Opcode.CONST_STRING) + val replaceRegister = + getInstruction(replaceIndex).registerA + + replaceInstruction( + replaceIndex, + "const-string v$replaceRegister, \"$musicPackageName\"" + ) + } + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/music/fingerprints/AppDeepLinkFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/music/fingerprints/AppDeepLinkFingerprint.kt new file mode 100644 index 0000000000..d4d7d2330b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/general/music/fingerprints/AppDeepLinkFingerprint.kt @@ -0,0 +1,27 @@ +package app.revanced.patches.youtube.general.music.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.reference.FieldReference + +internal object AppDeepLinkFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("L", "Ljava/util/Map;"), + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.CONST_STRING, + ), + strings = listOf("android.intent.action.VIEW"), + customFingerprint = { methodDef, _ -> + methodDef.indexOfFirstInstruction { + getReference()?.name == "appDeepLinkEndpoint" + } >= 0 + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/navigation/NavigationBarComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/navigation/NavigationBarComponentsPatch.kt index 51d27430cb..df78e197ba 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/navigation/NavigationBarComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/navigation/NavigationBarComponentsPatch.kt @@ -12,14 +12,18 @@ import app.revanced.patches.youtube.general.navigation.fingerprints.TranslucentN import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.navigation.NavigationBarHookPatch +import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.literalInstructionBooleanHook +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstStringInstructionOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Suppress("unused") object NavigationBarComponentsPatch : BaseBytecodePatch( @@ -27,6 +31,7 @@ object NavigationBarComponentsPatch : BaseBytecodePatch( description = "Adds options to hide or change components related to the navigation bar.", dependencies = setOf( SettingsPatch::class, + SharedResourceIdPatch::class, NavigationBarHookPatch::class ), compatiblePackages = COMPATIBLE_PACKAGE, @@ -48,7 +53,7 @@ object NavigationBarComponentsPatch : BaseBytecodePatch( // region patch for enable translucent navigation bar if (SettingsPatch.upward1923) { - TranslucentNavigationBarFingerprint.literalInstructionBooleanHook( + TranslucentNavigationBarFingerprint.injectLiteralInstructionBooleanCall( 45630927, "$GENERAL_CLASS_DESCRIPTOR->enableTranslucentNavigationBar()Z" ) @@ -81,17 +86,23 @@ object NavigationBarComponentsPatch : BaseBytecodePatch( // endregion + // region patch for hide navigation bar + + NavigationBarHookPatch.addBottomBarContainerHook("$GENERAL_CLASS_DESCRIPTOR->hideNavigationBar(Landroid/view/View;)V") + + // endregion + // region patch for hide navigation buttons AutoMotiveFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = getStringInstructionIndex("Android Automotive") - 1 - val register = getInstruction(insertIndex).registerA + val insertIndex = indexOfFirstStringInstructionOrThrow("Android Automotive") - 1 + val insertRegister = getInstruction(insertIndex).registerA addInstructions( insertIndex, """ - invoke-static {v$register}, $GENERAL_CLASS_DESCRIPTOR->switchCreateWithNotificationButton(Z)Z - move-result v$register + invoke-static {v$insertRegister}, $GENERAL_CLASS_DESCRIPTOR->switchCreateWithNotificationButton(Z)Z + move-result v$insertRegister """ ) } @@ -103,7 +114,10 @@ object NavigationBarComponentsPatch : BaseBytecodePatch( PivotBarSetTextFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = getTargetIndexWithMethodReferenceNameOrThrow("setText") + val targetIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setText" + } val targetRegister = getInstruction(targetIndex).registerC addInstruction( diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/splashanimation/fingerprints/SplashAnimationFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/splashanimation/fingerprints/SplashAnimationFingerprint.kt index 59d0ec2461..bc421d1c60 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/splashanimation/fingerprints/SplashAnimationFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/splashanimation/fingerprints/SplashAnimationFingerprint.kt @@ -2,13 +2,13 @@ package app.revanced.patches.youtube.general.splashanimation.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.DarkSplashAnimation -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue internal object SplashAnimationFingerprint : MethodFingerprint( returnType = "V", parameters = listOf("Landroid/os/Bundle;"), customFingerprint = { methodDef, _ -> methodDef.name == "onCreate" - && methodDef.containsWideLiteralInstructionIndex(DarkSplashAnimation) + && methodDef.containsWideLiteralInstructionValue(DarkSplashAnimation) } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/splashanimation/fingerprints/StartUpResourceIdFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/splashanimation/fingerprints/StartUpResourceIdFingerprint.kt index c986eb741d..339a27ecf3 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/splashanimation/fingerprints/StartUpResourceIdFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/splashanimation/fingerprints/StartUpResourceIdFingerprint.kt @@ -2,7 +2,7 @@ package app.revanced.patches.youtube.general.splashanimation.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object StartUpResourceIdFingerprint : MethodFingerprint( @@ -10,7 +10,7 @@ internal object StartUpResourceIdFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, parameters = listOf("I"), customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(3) - && methodDef.containsWideLiteralInstructionIndex(4) + methodDef.containsWideLiteralInstructionValue(3) + && methodDef.containsWideLiteralInstructionValue(4) } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt index 5c53e03c11..54a4a0b0a7 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt @@ -2,11 +2,11 @@ package app.revanced.patches.youtube.general.spoofappversion import app.revanced.patcher.data.ResourceContext import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.settings.ResourceUtils.addEntryValues import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.util.patch.BaseResourcePatch -import org.w3c.dom.Element -@Suppress("DEPRECATION", "unused") +@Suppress("unused") object SpoofAppVersionPatch : BaseResourcePatch( name = "Spoof app version", description = "Adds options to spoof the YouTube client version. " + @@ -17,31 +17,20 @@ object SpoofAppVersionPatch : BaseResourcePatch( ), compatiblePackages = COMPATIBLE_PACKAGE ) { + private const val ATTRIBUTE_NAME_ENTRIES = + "revanced_spoof_app_version_target_entries" + + private const val ATTRIBUTE_NAME_ENTRY_VALUE = + "revanced_spoof_app_version_target_entry_values" + override fun execute(context: ResourceContext) { if (SettingsPatch.upward1834) { - context.appendChild( - arrayOf( - "revanced_spoof_app_version_target_entries" to "@string/revanced_spoof_app_version_target_entry_18_33_40", - "revanced_spoof_app_version_target_entry_values" to "18.33.40", - ) - ) - + context.appendAppVersion("18.33.40") if (SettingsPatch.upward1839) { - context.appendChild( - arrayOf( - "revanced_spoof_app_version_target_entries" to "@string/revanced_spoof_app_version_target_entry_18_38_45", - "revanced_spoof_app_version_target_entry_values" to "18.38.45" - ) - ) - + context.appendAppVersion("18.38.45") if (SettingsPatch.upward1849) { - context.appendChild( - arrayOf( - "revanced_spoof_app_version_target_entries" to "@string/revanced_spoof_app_version_target_entry_18_48_39", - "revanced_spoof_app_version_target_entry_values" to "18.48.39" - ) - ) + context.appendAppVersion("18.48.39") } } } @@ -60,25 +49,16 @@ object SpoofAppVersionPatch : BaseResourcePatch( SettingsPatch.updatePatchStatus(this) } - private fun ResourceContext.appendChild(entryArray: Array>) { - entryArray.map { (attributeName, attributeValue) -> - this.xmlEditor["res/values/arrays.xml"].use { editor -> - editor.file.apply { - val resourcesNode = getElementsByTagName("resources").item(0) as Element - - val newElement: Element = createElement("item") - for (i in 0 until resourcesNode.childNodes.length) { - val node = resourcesNode.childNodes.item(i) as? Element ?: continue - - if (node.getAttribute("name") == attributeName) { - newElement.appendChild(createTextNode(attributeValue)) - val firstChild = node.firstChild - - node.insertBefore(newElement, firstChild) - } - } - } - } - } + private fun ResourceContext.appendAppVersion(appVersion: String) { + addEntryValues( + ATTRIBUTE_NAME_ENTRIES, + "@string/revanced_spoof_app_version_target_entry_" + appVersion.replace(".", "_"), + prepend = false + ) + addEntryValues( + ATTRIBUTE_NAME_ENTRY_VALUE, + appVersion, + prepend = false + ) } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/startpage/ChangeStartPagePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/startpage/ChangeStartPagePatch.kt index 30a871646f..7412b16bd2 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/startpage/ChangeStartPagePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/startpage/ChangeStartPagePatch.kt @@ -2,12 +2,20 @@ package app.revanced.patches.youtube.general.startpage import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patches.youtube.general.startpage.fingerprints.StartActivityFingerprint +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patches.youtube.general.startpage.fingerprints.BrowseIdFingerprint +import app.revanced.patches.youtube.general.startpage.fingerprints.IntentActionFingerprint import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE -import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR +import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_PATH import app.revanced.patches.youtube.utils.settings.SettingsPatch +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.StringReference @Suppress("unused") object ChangeStartPagePatch : BaseBytecodePatch( @@ -15,18 +23,38 @@ object ChangeStartPagePatch : BaseBytecodePatch( description = "Adds an option to set which page the app opens in instead of the homepage.", dependencies = setOf(SettingsPatch::class), compatiblePackages = COMPATIBLE_PACKAGE, - fingerprints = setOf(StartActivityFingerprint) + fingerprints = setOf( + BrowseIdFingerprint, + IntentActionFingerprint, + ) ) { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = "$GENERAL_PATH/ChangeStartPagePatch;" + override fun execute(context: BytecodeContext) { - StartActivityFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - addInstruction( - 0, - "invoke-static { p1 }, $GENERAL_CLASS_DESCRIPTOR->changeStartPage(Landroid/content/Intent;)V" - ) + + // Hook browseId. + BrowseIdFingerprint.resultOrThrow().mutableMethod.apply { + val browseIdIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.CONST_STRING && + getReference()?.string == "FEwhat_to_watch" } + val browseIdRegister = getInstruction(browseIdIndex).registerA + + addInstructions( + browseIdIndex + 1, """ + invoke-static { v$browseIdRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->overrideBrowseId(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$browseIdRegister + """ + ) } + // There is no browseId assigned to Shorts and Search. + // Just hook the Intent action. + IntentActionFingerprint.resultOrThrow().mutableMethod.addInstruction( + 0, + "invoke-static { p1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->overrideIntentAction(Landroid/content/Intent;)V" + ) + /** * Add settings */ diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/startpage/fingerprints/BrowseIdFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/startpage/fingerprints/BrowseIdFingerprint.kt new file mode 100644 index 0000000000..f53490ac40 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/general/startpage/fingerprints/BrowseIdFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.youtube.general.startpage.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.Opcode + +internal object BrowseIdFingerprint : MethodFingerprint( + returnType = "Lcom/google/android/apps/youtube/app/common/ui/navigation/PaneDescriptor;", + parameters = emptyList(), + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.RETURN_OBJECT, + ), + strings = listOf("FEwhat_to_watch"), +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/startpage/fingerprints/IntentActionFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/startpage/fingerprints/IntentActionFingerprint.kt new file mode 100644 index 0000000000..8d6292c7de --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/general/startpage/fingerprints/IntentActionFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.youtube.general.startpage.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint + +internal object IntentActionFingerprint : MethodFingerprint( + parameters = listOf("Landroid/content/Intent;"), + strings = listOf("has_handled_intent"), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/startpage/fingerprints/StartActivityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/startpage/fingerprints/StartActivityFingerprint.kt deleted file mode 100644 index 735185042c..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/general/startpage/fingerprints/StartActivityFingerprint.kt +++ /dev/null @@ -1,11 +0,0 @@ -package app.revanced.patches.youtube.general.startpage.fingerprints - -import app.revanced.patcher.fingerprint.MethodFingerprint - -internal object StartActivityFingerprint : MethodFingerprint( - parameters = listOf("Landroid/content/Intent;"), - customFingerprint = { methodDef, classDef -> - methodDef.name == "startActivity" - && classDef.type.endsWith("/Shell_HomeActivity;") - } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt index 8e0456a21f..911ecb9883 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt @@ -11,6 +11,7 @@ import app.revanced.patcher.patch.PatchException import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.youtube.general.toolbar.fingerprints.ActionBarRingoBackgroundFingerprint +import app.revanced.patches.youtube.general.toolbar.fingerprints.ActionBarRingoConstructorFingerprint import app.revanced.patches.youtube.general.toolbar.fingerprints.ActionBarRingoTextFingerprint import app.revanced.patches.youtube.general.toolbar.fingerprints.AttributeResolverFingerprint import app.revanced.patches.youtube.general.toolbar.fingerprints.CreateButtonDrawableFingerprint @@ -38,16 +39,17 @@ import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch.contexts import app.revanced.patches.youtube.utils.toolbar.ToolBarHookPatch import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT +import app.revanced.util.alsoResolve import app.revanced.util.doRecursively -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.getTargetIndexWithReferenceOrThrow -import app.revanced.util.getTargetIndexWithReferenceReversedOrThrow +import app.revanced.util.findMethodOrThrow +import app.revanced.util.getReference import app.revanced.util.getWalkerMethod -import app.revanced.util.getWideLiteralInstructionIndex -import app.revanced.util.literalInstructionBooleanHook -import app.revanced.util.literalInstructionHook +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.patch.BaseBytecodePatch +import app.revanced.util.replaceLiteralInstructionCall import app.revanced.util.resultOrThrow import app.revanced.util.updatePatchStatus import com.android.tools.smali.dexlib2.Opcode @@ -72,6 +74,7 @@ object ToolBarComponentsPatch : BaseBytecodePatch( compatiblePackages = COMPATIBLE_PACKAGE, fingerprints = setOf( ActionBarRingoBackgroundFingerprint, + ActionBarRingoConstructorFingerprint, AttributeResolverFingerprint, CreateButtonDrawableFingerprint, CreateSearchSuggestionsFingerprint, @@ -104,7 +107,7 @@ object ToolBarComponentsPatch : BaseBytecodePatch( YtPremiumWordMarkHeader, YtWordMarkHeader ).forEach { literal -> - context.literalInstructionHook(literal, smaliInstruction) + context.replaceLiteralInstructionCall(literal, smaliInstruction) } // YouTube's headers have the form of AttributeSet, which is decoded from YouTube's built-in classes. @@ -112,8 +115,8 @@ object ToolBarComponentsPatch : BaseBytecodePatch( val attributeResolverMethodCall = attributeResolverMethod.definingClass + "->" + attributeResolverMethod.name + "(Landroid/content/Context;I)Landroid/graphics/drawable/Drawable;" - context.findClass(GENERAL_CLASS_DESCRIPTOR)!!.mutableClass.methods.single { method -> - method.name == "getHeaderDrawable" + context.findMethodOrThrow(GENERAL_CLASS_DESCRIPTOR) { + name == "getHeaderDrawable" }.addInstructions( 0, """ invoke-static {p0, p1}, $attributeResolverMethodCall @@ -123,13 +126,11 @@ object ToolBarComponentsPatch : BaseBytecodePatch( ) // The sidebar's header is lithoView. Add a listener to change it. - DrawerContentViewFingerprint.resolve( - context, - DrawerContentViewConstructorFingerprint.resultOrThrow().classDef - ) - DrawerContentViewFingerprint.resultOrThrow().let { + DrawerContentViewFingerprint.alsoResolve( + context, DrawerContentViewConstructorFingerprint + ).let { it.mutableMethod.apply { - val insertIndex = getTargetIndexWithMethodReferenceNameOrThrow("addView") + val insertIndex = DrawerContentViewFingerprint.indexOfAddViewInstruction(this) val insertRegister = getInstruction(insertIndex).registerD addInstruction( @@ -145,7 +146,7 @@ object ToolBarComponentsPatch : BaseBytecodePatch( setActionBarRingoMutableClass.methods.first { method -> MethodUtil.isConstructor(method) }.apply { - val insertIndex = getTargetIndexOrThrow(Opcode.IPUT_BOOLEAN) + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.IPUT_BOOLEAN) val insertRegister = getInstruction(insertIndex).registerA addInstruction( @@ -170,7 +171,8 @@ object ToolBarComponentsPatch : BaseBytecodePatch( ActionBarRingoBackgroundFingerprint.resultOrThrow().let { ActionBarRingoTextFingerprint.resolve(context, it.classDef) it.mutableMethod.apply { - val viewIndex = getWideLiteralInstructionIndex(ActionBarRingoBackground) + 2 + val viewIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(ActionBarRingoBackground) + 2 val viewRegister = getInstruction(viewIndex).registerA addInstructions( @@ -178,7 +180,8 @@ object ToolBarComponentsPatch : BaseBytecodePatch( "invoke-static {v$viewRegister}, $GENERAL_CLASS_DESCRIPTOR->setWideSearchBarLayout(Landroid/view/View;)V" ) - val targetIndex = it.scanResult.patternScanResult!!.endIndex + 1 + val targetIndex = + ActionBarRingoBackgroundFingerprint.indexOfStaticInstruction(this) + 1 val targetRegister = getInstruction(targetIndex).registerA injectSearchBarHook( @@ -187,39 +190,11 @@ object ToolBarComponentsPatch : BaseBytecodePatch( "enableWideSearchBarWithHeaderInverse" ) } - - it.mutableClass.methods.first { method -> MethodUtil.isConstructor(method) } - .apply { - val staticCalls = implementation!!.instructions.withIndex() - .filter { instruction -> - val methodReference = - ((instruction.value as? ReferenceInstruction)?.reference as? MethodReference) - methodReference?.parameterTypes?.size == 1 && - methodReference.returnType == "Z" - } - - if (staticCalls.size != 2) - throw PatchException("Size of staticCalls does not match: ${staticCalls.size}") - - mapOf( - staticCalls.elementAt(0).index to "enableWideSearchBar", - staticCalls.elementAt(1).index to "enableWideSearchBarWithHeader" - ).forEach { (index, descriptor) -> - val walkerMethod = getWalkerMethod(context, index) - - walkerMethod.apply { - injectSearchBarHook( - implementation!!.instructions.size - 1, - descriptor - ) - } - } - } } ActionBarRingoTextFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = it.scanResult.patternScanResult!!.endIndex + 1 + val targetIndex = ActionBarRingoTextFingerprint.indexOfStaticInstruction(this) + 1 val targetRegister = getInstruction(targetIndex).registerA injectSearchBarHook( @@ -230,6 +205,35 @@ object ToolBarComponentsPatch : BaseBytecodePatch( } } + ActionBarRingoConstructorFingerprint.resultOrThrow().mutableMethod.apply { + val staticCalls = implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + val methodReference = (instruction as? ReferenceInstruction)?.reference + instruction.opcode == Opcode.INVOKE_STATIC && + methodReference is MethodReference && + methodReference.parameterTypes.size == 1 && + methodReference.returnType == "Z" + } + + if (staticCalls.size != 2) + throw PatchException("Size of staticCalls does not match: ${staticCalls.size}") + + mapOf( + staticCalls.elementAt(0).index to "enableWideSearchBar", + staticCalls.elementAt(1).index to "enableWideSearchBarWithHeader" + ).forEach { (index, descriptor) -> + val walkerMethod = getWalkerMethod(context, index) + + walkerMethod.apply { + injectSearchBarHook( + implementation!!.instructions.lastIndex, + descriptor + ) + } + } + } + YouActionBarFingerprint.resolve(context, setActionBarRingoMutableClass) YouActionBarFingerprint.resultOrThrow().let { it.mutableMethod.apply { @@ -277,16 +281,16 @@ object ToolBarComponentsPatch : BaseBytecodePatch( CreateSearchSuggestionsFingerprint.resultOrThrow().let { result -> result.mutableMethod.apply { - val relativeIndex = getWideLiteralInstructionIndex(40) - val replaceIndex = getTargetIndexWithReferenceReversedOrThrow( - relativeIndex, - "Landroid/widget/ImageView;->setVisibility(I)V" - ) - 1 + val relativeIndex = indexOfFirstWideLiteralInstructionValueOrThrow(40) + val replaceIndex = indexOfFirstInstructionReversedOrThrow(relativeIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.toString() == "Landroid/widget/ImageView;->setVisibility(I)V" + } - 1 - val jumpIndex = getTargetIndexWithReferenceOrThrow( - relativeIndex, - "Landroid/net/Uri;->parse(Ljava/lang/String;)Landroid/net/Uri;" - ) + 4 + val jumpIndex = indexOfFirstInstructionOrThrow(relativeIndex) { + opcode == Opcode.INVOKE_STATIC && + getReference()?.toString() == "Landroid/net/Uri;->parse(Ljava/lang/String;)Landroid/net/Uri;" + } + 4 val replaceIndexInstruction = getInstruction(replaceIndex) val replaceIndexReference = @@ -309,7 +313,7 @@ object ToolBarComponentsPatch : BaseBytecodePatch( // region patch for hide voice search button if (SettingsPatch.upward1928) { - ImageSearchButtonConfigFingerprint.literalInstructionBooleanHook( + ImageSearchButtonConfigFingerprint.injectLiteralInstructionBooleanCall( 45617544, "$GENERAL_CLASS_DESCRIPTOR->hideImageSearchButton(Z)Z" ) @@ -323,15 +327,15 @@ object ToolBarComponentsPatch : BaseBytecodePatch( // region patch for hide voice search button - SearchBarFingerprint.resolve( - context, - SearchBarParentFingerprint.resultOrThrow().classDef - ) - SearchBarFingerprint.resultOrThrow().let { + SearchBarFingerprint.alsoResolve( + context, SearchBarParentFingerprint + ).let { it.mutableMethod.apply { val startIndex = it.scanResult.patternScanResult!!.startIndex - val setVisibilityIndex = - getTargetIndexWithMethodReferenceNameOrThrow(startIndex, "setVisibility") + val setVisibilityIndex = indexOfFirstInstructionOrThrow(startIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setVisibility" + } val setVisibilityInstruction = getInstruction(setVisibilityIndex) @@ -345,12 +349,11 @@ object ToolBarComponentsPatch : BaseBytecodePatch( SearchResultFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val startIndex = getWideLiteralInstructionIndex(VoiceSearch) - val setOnClickListenerIndex = - getTargetIndexWithMethodReferenceNameOrThrow( - startIndex, - "setOnClickListener" - ) + val startIndex = indexOfFirstWideLiteralInstructionValueOrThrow(VoiceSearch) + val setOnClickListenerIndex = indexOfFirstInstructionOrThrow(startIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setOnClickListener" + } val viewRegister = getInstruction(setOnClickListenerIndex).registerC @@ -366,7 +369,7 @@ object ToolBarComponentsPatch : BaseBytecodePatch( // region patch for replace create button CreateButtonDrawableFingerprint.resultOrThrow().mutableMethod.apply { - val index = getWideLiteralInstructionIndex(YtOutlineVideoCamera) + val index = indexOfFirstWideLiteralInstructionValueOrThrow(YtOutlineVideoCamera) val register = getInstruction(index).registerA addInstructions( @@ -379,15 +382,14 @@ object ToolBarComponentsPatch : BaseBytecodePatch( ToolBarHookPatch.hook("$GENERAL_CLASS_DESCRIPTOR->replaceCreateButton") - val settingsClass = context.findClass("Shell_SettingsActivity") - ?: throw PatchException("Shell_SettingsActivity class not found.") - - settingsClass.mutableClass.methods.find { it.name == "onCreate" }?.apply { - addInstruction( - 0, - "invoke-static {p0}, $GENERAL_CLASS_DESCRIPTOR->setShellActivityTheme(Landroid/app/Activity;)V" - ) - } ?: throw PatchException("onCreate method not found.") + context.findMethodOrThrow( + "Lcom/google/android/apps/youtube/app/application/Shell_SettingsActivity;" + ) { + name == "onCreate" + }.addInstruction( + 0, + "invoke-static {p0}, $GENERAL_CLASS_DESCRIPTOR->setShellActivityTheme(Landroid/app/Activity;)V" + ) // endregion diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/ActionBarRingoBackgroundFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/ActionBarRingoBackgroundFingerprint.kt index e3e4867cc7..fd04fadfbb 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/ActionBarRingoBackgroundFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/ActionBarRingoBackgroundFingerprint.kt @@ -1,15 +1,28 @@ package app.revanced.patches.youtube.general.toolbar.fingerprints +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.general.toolbar.fingerprints.ActionBarRingoBackgroundFingerprint.indexOfStaticInstruction import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ActionBarRingoBackground -import app.revanced.util.fingerprint.LiteralValueFingerprint +import app.revanced.util.containsWideLiteralInstructionValue +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.reference.MethodReference -internal object ActionBarRingoBackgroundFingerprint : LiteralValueFingerprint( +internal object ActionBarRingoBackgroundFingerprint : MethodFingerprint( returnType = "Landroid/view/View;", - parameters = listOf("Landroid/view/LayoutInflater;"), - opcodes = listOf( - Opcode.IGET_OBJECT, - Opcode.INVOKE_STATIC - ), - literalSupplier = { ActionBarRingoBackground } -) \ No newline at end of file + customFingerprint = { methodDef, _ -> + methodDef.containsWideLiteralInstructionValue(ActionBarRingoBackground) && + indexOfStaticInstruction(methodDef) >= 0 + } +) { + fun indexOfStaticInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + val reference = getReference() + opcode == Opcode.INVOKE_STATIC && + reference?.parameterTypes?.size == 1 && + reference.parameterTypes.firstOrNull() == "Landroid/content/Context;" && + reference.returnType == "Z" + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/ActionBarRingoConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/ActionBarRingoConstructorFingerprint.kt new file mode 100644 index 0000000000..3ce7ec5291 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/ActionBarRingoConstructorFingerprint.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.youtube.general.toolbar.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.util.MethodUtil + +internal object ActionBarRingoConstructorFingerprint : MethodFingerprint( + returnType = "V", + strings = listOf("default"), + customFingerprint = custom@{ methodDef, _ -> + if (!MethodUtil.isConstructor(methodDef)) { + return@custom false + } + + val parameterTypes = methodDef.parameterTypes + parameterTypes.size >= 5 && parameterTypes[0] == "Landroid/content/Context;" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/ActionBarRingoTextFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/ActionBarRingoTextFingerprint.kt index 4fc5d8a54c..8e0e7fdcc0 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/ActionBarRingoTextFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/ActionBarRingoTextFingerprint.kt @@ -1,16 +1,36 @@ package app.revanced.patches.youtube.general.toolbar.fingerprints import app.revanced.patcher.extensions.or -import app.revanced.util.fingerprint.MethodReferenceNameFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.general.toolbar.fingerprints.ActionBarRingoTextFingerprint.indexOfStartDelayInstruction +import app.revanced.patches.youtube.general.toolbar.fingerprints.ActionBarRingoTextFingerprint.indexOfStaticInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +import app.revanced.util.indexOfFirstInstructionReversed 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 object ActionBarRingoTextFingerprint : MethodReferenceNameFingerprint( - returnType = "V", +internal object ActionBarRingoTextFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - opcodes = listOf( - Opcode.IGET_OBJECT, - Opcode.INVOKE_STATIC - ), - reference = { "setStartDelay" } -) + customFingerprint = { methodDef, _ -> + indexOfStartDelayInstruction(methodDef) >= 0 && + indexOfStaticInstruction(methodDef) >= 0 + } +) { + fun indexOfStartDelayInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setStartDelay" + } + + fun indexOfStaticInstruction(methodDef: Method) = + methodDef.indexOfFirstInstructionReversed(indexOfStartDelayInstruction(methodDef)) { + val reference = getReference() + opcode == Opcode.INVOKE_STATIC && + reference?.parameterTypes?.size == 1 && + reference.parameterTypes.firstOrNull() == "Landroid/content/Context;" && + reference.returnType == "Z" + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/DrawerContentViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/DrawerContentViewFingerprint.kt index 789f2b1974..afda22c8d5 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/DrawerContentViewFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/DrawerContentViewFingerprint.kt @@ -1,9 +1,14 @@ package app.revanced.patches.youtube.general.toolbar.fingerprints -import app.revanced.util.fingerprint.MethodReferenceNameFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.general.toolbar.fingerprints.DrawerContentViewFingerprint.indexOfAddViewInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversed 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 object DrawerContentViewFingerprint : MethodReferenceNameFingerprint( +internal object DrawerContentViewFingerprint : MethodFingerprint( returnType = "V", parameters = listOf("L"), opcodes = listOf( @@ -12,5 +17,13 @@ internal object DrawerContentViewFingerprint : MethodReferenceNameFingerprint( Opcode.NEW_INSTANCE, Opcode.INVOKE_DIRECT, ), - reference = { "addView" } -) + customFingerprint = { methodDef, _ -> + indexOfAddViewInstruction(methodDef) >= 0 + } +) { + fun indexOfAddViewInstruction(methodDef: Method) = + methodDef.indexOfFirstInstructionReversed { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "addView" + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/SearchBarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/SearchBarFingerprint.kt index fd8eed5bb0..facbc289bb 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/SearchBarFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/fingerprints/SearchBarFingerprint.kt @@ -1,9 +1,12 @@ package app.revanced.patches.youtube.general.toolbar.fingerprints -import app.revanced.util.fingerprint.MethodReferenceNameFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversed import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.reference.MethodReference -object SearchBarFingerprint : MethodReferenceNameFingerprint( +object SearchBarFingerprint : MethodFingerprint( returnType = "V", parameters = listOf("Ljava/lang/String;"), opcodes = listOf( @@ -12,5 +15,9 @@ object SearchBarFingerprint : MethodReferenceNameFingerprint( Opcode.IGET_BOOLEAN, Opcode.IF_EQZ ), - reference = { "isEmpty" } + customFingerprint = { methodDef, _ -> + methodDef.indexOfFirstInstructionReversed { + getReference()?.name == "isEmpty" + } >= 0 + } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt index 956b6f3203..2bd701575e 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt @@ -30,7 +30,8 @@ object CustomBrandingIconPatch : BaseResourcePatch( "MMT" to "mmt", "Revancify Blue" to DEFAULT_ICON, "Revancify Red" to "revancify_red", - "YouTube" to "youtube" + "YouTube" to "youtube", + "YouTube (Minimal header)" to "youtube_minimal_header" ) private val sizeArray = arrayOf( diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/doubletaplength/DoubleTapLengthPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/doubletaplength/DoubleTapLengthPatch.kt index 65bc0860ec..ad981948c8 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/doubletaplength/DoubleTapLengthPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/doubletaplength/DoubleTapLengthPatch.kt @@ -56,8 +56,16 @@ object DoubleTapLengthPatch : BaseResourcePatch( ) for (index in 0 until splits.count()) { - context.addEntryValues(arrayPath, lengthElements[index], entryValueName) - context.addEntryValues(arrayPath, lengthElements[index], entriesName) + context.addEntryValues( + entryValueName, + lengthElements[index], + path = arrayPath + ) + context.addEntryValues( + entriesName, + lengthElements[index], + path = arrayPath + ) } SettingsPatch.updatePatchStatus(this) diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/header/ChangeHeaderPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/header/ChangeHeaderPatch.kt index b1b967798e..57b2c90dce 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/header/ChangeHeaderPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/header/ChangeHeaderPatch.kt @@ -3,13 +3,16 @@ package app.revanced.patches.youtube.layout.header import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption import app.revanced.patches.youtube.utils.compatibility.Constants +import app.revanced.patches.youtube.utils.integrations.Constants.PATCH_STATUS_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.settings.ResourceUtils +import app.revanced.patches.youtube.utils.settings.SettingsBytecodePatch import app.revanced.util.ResourceGroup import app.revanced.util.Utils.trimIndentMultiline import app.revanced.util.copyFile import app.revanced.util.copyResources import app.revanced.util.patch.BaseResourcePatch import app.revanced.util.underBarOrThrow +import app.revanced.util.updatePatchStatus import java.io.File import java.nio.file.Files import kotlin.io.path.copyTo @@ -136,6 +139,13 @@ object ChangeHeaderPatch : BaseResourcePatch( context.copyResources("youtube/branding/$customBrandingIconType/header", it) } } + + if (customBrandingIconType == "youtube_minimal_header") { + SettingsBytecodePatch.contexts.updatePatchStatus( + PATCH_STATUS_CLASS_DESCRIPTOR, + "MinimalHeader" + ) + } } else { println(warnings) return diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/shortcut/ShortcutPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/shortcut/ShortcutPatch.kt new file mode 100644 index 0000000000..85c977cc07 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/shortcut/ShortcutPatch.kt @@ -0,0 +1,69 @@ +package app.revanced.patches.youtube.layout.shortcut + +import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption +import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.settings.SettingsPatch +import app.revanced.util.findElementByAttributeValueOrThrow +import app.revanced.util.patch.BaseResourcePatch +import org.w3c.dom.Element + +@Suppress("DEPRECATION", "unused") +object ShortcutPatch : BaseResourcePatch( + name = "Hide shortcuts", + description = "Remove, at compile time, the app shortcuts that appears when app icon is long pressed.", + dependencies = setOf(SettingsPatch::class), + compatiblePackages = COMPATIBLE_PACKAGE, + use = false +) { + private val Explore by booleanPatchOption( + key = "Explore", + default = false, + title = "Hide Explore", + description = "Hide Explore from shortcuts.", + required = true + ) + + private val Subscriptions by booleanPatchOption( + key = "Subscriptions", + default = false, + title = "Hide Subscriptions", + description = "Hide Subscriptions from shortcuts.", + required = true + ) + + private val Search by booleanPatchOption( + key = "Search", + default = false, + title = "Hide Search", + description = "Hide Search from shortcuts.", + required = true + ) + + private val Shorts by booleanPatchOption( + key = "Shorts", + default = true, + title = "Hide Shorts", + description = "Hide Shorts from shortcuts.", + required = true + ) + + override fun execute(context: ResourceContext) { + + this.options.values.forEach { options -> + if (options.value == true) { + context.xmlEditor["res/xml/main_shortcuts.xml"].use { editor -> + val shortcuts = editor.file.getElementsByTagName("shortcuts").item(0) as Element + val shortsItem = shortcuts.getElementsByTagName("shortcut") + .findElementByAttributeValueOrThrow( + "android:shortcutId", + "${options.key.lowercase()}-shortcut" + ) + shortsItem.parentNode.removeChild(shortsItem) + } + } + } + + SettingsPatch.updatePatchStatus(this) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/BaseThemePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/BaseThemePatch.kt index 303150dd34..d0e95dc1ac 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/BaseThemePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/BaseThemePatch.kt @@ -5,7 +5,6 @@ import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.shared.drawable.DrawableColorPatch import app.revanced.patches.youtube.utils.integrations.Constants.UTILS_PATH -import app.revanced.util.doRecursively import org.w3c.dom.Element @Patch(dependencies = [DrawableColorPatch::class]) @@ -88,24 +87,6 @@ object BaseThemePatch : ResourcePatch() { } } - /** - * Since YouTube 19.20.35, the visibility of the background color of the `More comments` icon in live chat has worsened. - * See ReVanced_Extended#2197 - * - * As a temporary workaround, revert to the colors of YouTube 19.19.39. - */ - context.xmlEditor["res/drawable/live_chat_more_comments_selector.xml"].use { editor -> - editor.file.doRecursively loop@{ node -> - if (node !is Element) return@loop - - node.getAttributeNode("android:color")?.let { attribute -> - if (attribute.textContent == "?ytInvertedBackground") { - attribute.textContent = "?ytThemedBlue" - } - } - } - } - } internal var isMonetPatchIncluded: Boolean = false diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/translations/TranslationsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/translations/TranslationsPatch.kt index a3234c46d6..3eda299e5e 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/translations/TranslationsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/translations/TranslationsPatch.kt @@ -17,8 +17,24 @@ object TranslationsPatch : BaseResourcePatch( ) { // Array of supported translations, each represented by its language code. private val TRANSLATIONS = arrayOf( - "ar", "bg-rBG", "de-rDE", "el-rGR", "es-rES", "fr-rFR", "hu-rHU", "it-rIT", "ja-rJP", "ko-rKR", - "pl-rPL", "pt-rBR", "ru-rRU", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW" + "ar", + "bg-rBG", + "de-rDE", + "el-rGR", + "es-rES", + "fr-rFR", + "hu-rHU", + "it-rIT", + "ja-rJP", + "ko-rKR", + "pl-rPL", + "pt-rBR", + "ru-rRU", + "tr-rTR", + "uk-rUA", + "vi-rVN", + "zh-rCN", + "zh-rTW" ) private var CustomTranslations by stringPatchOption( diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/visual/VisualPreferencesIconsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/visual/VisualPreferencesIconsPatch.kt index 78c677b0c4..7929eb953b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/visual/VisualPreferencesIconsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/visual/VisualPreferencesIconsPatch.kt @@ -1,26 +1,29 @@ package app.revanced.patches.youtube.layout.visual import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption import app.revanced.patches.youtube.layout.branding.icon.CustomBrandingIconPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.util.ResourceGroup +import app.revanced.util.Utils.trimIndentMultiline import app.revanced.util.copyResources import app.revanced.util.doRecursively import app.revanced.util.patch.BaseResourcePatch import app.revanced.util.underBarOrThrow import org.w3c.dom.Element +import java.io.Closeable @Suppress("DEPRECATION", "unused") object VisualPreferencesIconsPatch : BaseResourcePatch( - name = "Visual preferences icons", + name = "Visual preferences icons for YouTube", description = "Adds icons to specific preferences in the settings.", dependencies = setOf(SettingsPatch::class), - compatiblePackages = COMPATIBLE_PACKAGE, - use = false -) { + compatiblePackages = COMPATIBLE_PACKAGE +), Closeable { private const val DEFAULT_ICON = "extension" + private const val EMPTY_ICON = "empty_icon" private val RVXSettingsMenuIcon = stringPatchOption( key = "RVXSettingsMenuIcon", @@ -29,6 +32,7 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "Custom branding icon" to "custom_branding_icon", "Extension" to DEFAULT_ICON, "Gear" to "gear", + "YT alt" to "yt_alt", "ReVanced" to "revanced", "ReVanced Colored" to "revanced_colored", ), @@ -37,7 +41,24 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( required = true ) + private val ApplyToAll by booleanPatchOption( + key = "ApplyToAll", + default = false, + title = "Apply to all settings menu", + description = """ + Whether to apply Visual preferences icons to all settings menus. + + If true: icons are applied to the parent PreferenceScreen of YouTube settings, the parent PreferenceScreen of RVX settings and the RVX sub-settings (if supports). + + If false: icons are applied only to the parent PreferenceScreen of YouTube settings and RVX settings. + """.trimIndentMultiline(), + required = true + ) + + private lateinit var context: ResourceContext + override fun execute(context: ResourceContext) { + this.context = context // Check patch options first. val selectedIconType = RVXSettingsMenuIcon @@ -46,6 +67,12 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( val customBrandingIconType = CustomBrandingIconPatch.AppIcon .underBarOrThrow() + if (ApplyToAll == true) { + preferenceKey += rvxPreferenceKey + } + + preferenceIcon = preferenceKey.setPreferenceIcon() + // region copy shared resources. arrayOf( @@ -55,7 +82,7 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( ), ResourceGroup( "drawable-xxhdpi", - "$emptyIcon.png" + "$EMPTY_ICON.png" ), ).forEach { resourceGroup -> context.copyResources("youtube/visual/shared", resourceGroup) @@ -90,6 +117,11 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( // endregion. + SettingsPatch.updatePatchStatus(this) + } + + override fun close() { + // region set visual preferences icon. arrayOf( @@ -109,9 +141,13 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( // Add custom RVX settings menu icon in intentKey -> intentIcon[title] - in emptyTitles -> emptyIcon + in emptyTitles -> EMPTY_ICON else -> null } + if (drawableName == EMPTY_ICON && + ApplyToAll == false + ) return@loop + drawableName?.let { node.setAttribute("android:icon", "@drawable/$it") } @@ -122,35 +158,34 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( // endregion. - SettingsPatch.updatePatchStatus(this) } // region preference key and icon. - private val preferenceKey = setOf( - // Main settings (sorted as displayed in the settings) - "parent_tools_key", - "general_key", + private var preferenceKey = setOf( + // YouTube settings. + "about_key", + "accessibility_settings_key", "account_switcher_key", - "data_saving_settings_key", "auto_play_key", - "video_quality_settings_key", + "billing_and_payment_key", + "captions_key", + "connected_accounts_browse_page_key", + "data_saving_settings_key", + "general_key", + "history_key", + "live_chat_key", + "notification_key", "offline_key", "pair_with_tv_key", - "history_key", - "your_data_key", - "privacy_key", + "parent_tools_key", "premium_early_access_browse_page_key", + "privacy_key", "subscription_product_setting_key", - "billing_and_payment_key", - "notification_key", - "connected_accounts_browse_page_key", - "live_chat_key", - "captions_key", - "accessibility_settings_key", - "about_key", + "video_quality_settings_key", + "your_data_key", - // Main RVX settings (sorted as displayed in the settings) + // RVX settings. "revanced_preference_screen_ads", "revanced_preference_screen_alt_thumbnails", "revanced_preference_screen_feed", @@ -162,7 +197,9 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_preference_screen_ryd", "revanced_preference_screen_sb", "revanced_preference_screen_misc", + ) + private var rvxPreferenceKey = setOf( // Internal RVX settings (items without prefix are listed first, others are sorted alphabetically) "gms_core_settings", "sb_enable_create_segment", @@ -173,11 +210,13 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_alt_thumbnail_player", "revanced_alt_thumbnail_search", "revanced_alt_thumbnail_subscriptions", + "revanced_change_share_sheet", "revanced_change_shorts_repeat_state", "revanced_custom_player_overlay_opacity", "revanced_default_app_settings", "revanced_default_playback_speed", "revanced_default_video_quality_wifi", + "revanced_disable_default_playback_speed_music", "revanced_disable_hdr_auto_brightness", "revanced_disable_hdr_video", "revanced_disable_quic_protocol", @@ -186,10 +225,13 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_enable_external_browser", "revanced_enable_old_quality_layout", "revanced_enable_open_links_directly", + "revanced_enable_opus_codec", + "revanced_enable_save_and_restore_brightness", "revanced_enable_swipe_brightness", "revanced_enable_swipe_haptic_feedback", "revanced_enable_swipe_lowest_value_auto_brightness", "revanced_enable_swipe_press_to_engage", + "revanced_enable_swipe_to_switch_video", "revanced_enable_swipe_volume", "revanced_enable_watch_panel_gestures", "revanced_hide_clip_button", @@ -213,11 +255,14 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_hide_player_flyout_menu_audio_track", "revanced_hide_player_flyout_menu_captions", "revanced_hide_player_flyout_menu_help", + "revanced_hide_player_flyout_menu_listen_with_youtube_music", "revanced_hide_player_flyout_menu_lock_screen", "revanced_hide_player_flyout_menu_loop_video", "revanced_hide_player_flyout_menu_more_info", + "revanced_hide_player_flyout_menu_pip", + "revanced_hide_player_flyout_menu_premium_controls", "revanced_hide_player_flyout_menu_playback_speed", - "revanced_hide_player_flyout_menu_quality_footer", + "revanced_hide_player_flyout_menu_quality_header", "revanced_hide_player_flyout_menu_report", "revanced_hide_player_flyout_menu_stable_volume", "revanced_hide_player_flyout_menu_stats_for_nerds", @@ -235,7 +280,9 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_hide_quick_actions_share_button", "revanced_hide_remix_button", "revanced_hide_report_button", + "revanced_hide_rewards_button", "revanced_hide_share_button", + "revanced_hide_shop_button", "revanced_hide_shorts_comments_button", "revanced_hide_shorts_dislike_button", "revanced_hide_shorts_like_button", @@ -247,6 +294,10 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_hide_shorts_shelf_search", "revanced_hide_shorts_shelf_subscriptions", "revanced_hide_shorts_toolbar", + "revanced_hide_thanks_button", + "revanced_hide_toolbar_cast_button", + "revanced_hide_toolbar_create_button", + "revanced_hide_toolbar_notification_button", "revanced_overlay_button_always_repeat", "revanced_overlay_button_copy_video_url", "revanced_overlay_button_copy_video_url_timestamp", @@ -267,9 +318,10 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_preference_screen_feed_flyout_menu", "revanced_preference_screen_fullscreen", "revanced_preference_screen_haptic_feedback", + "revanced_preference_screen_hook_buttons", "revanced_preference_screen_import_export", "revanced_preference_screen_miniplayer", - "revanced_preference_screen_navigation_buttons", + "revanced_preference_screen_navigation_bar", "revanced_preference_screen_patch_information", "revanced_preference_screen_player_buttons", "revanced_preference_screen_player_flyout_menu", @@ -280,6 +332,7 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_preference_screen_toolbar", "revanced_preference_screen_video_description", "revanced_preference_screen_video_filter", + "revanced_preference_screen_watch_history", "revanced_sanitize_sharing_links", "revanced_swipe_gestures_lock_mode", "revanced_swipe_magnitude_threshold", @@ -299,13 +352,16 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_custom_playback_speeds", "revanced_custom_playback_speed_menu_type", "revanced_default_video_quality_mobile", + "revanced_disable_like_dislike_glow", "revanced_disable_default_playback_speed_live", "revanced_enable_custom_playback_speed", - "revanced_external_downloader_package_name", "revanced_hide_shorts_comments_disabled_button", "revanced_hide_player_flyout_menu_captions_footer", + "revanced_hide_player_flyout_menu_quality_footer", "revanced_remember_playback_speed_last_selected", + "revanced_remember_playback_speed_last_selected_toast", "revanced_remember_video_quality_last_selected", + "revanced_remember_video_quality_last_selected_toast", "revanced_restore_old_video_quality_menu", "revanced_enable_debug_buffer_logging", "revanced_whitelist_settings", @@ -314,7 +370,7 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( // A lot of mappings here. // The performance impact should be negligible in this context, // as the operations involved are not computationally intensive. - private val preferenceIcon = preferenceKey.associateWith { title -> + private fun Set.setPreferenceIcon() = associateWith { title -> when (title) { // Main RVX settings "revanced_preference_screen_general" -> "general_key_icon" @@ -326,6 +382,7 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_alt_thumbnail_player" -> "revanced_preference_screen_player_icon" "revanced_alt_thumbnail_search" -> "revanced_hide_shorts_shelf_search_icon" "revanced_alt_thumbnail_subscriptions" -> "revanced_hide_navigation_subscriptions_button_icon" + "revanced_change_share_sheet" -> "revanced_hide_shorts_share_button_icon" "revanced_custom_player_overlay_opacity" -> "revanced_swipe_overlay_background_alpha_icon" "revanced_default_app_settings" -> "revanced_preference_screen_settings_menu_icon" "revanced_default_playback_speed" -> "revanced_overlay_button_speed_dialog_icon" @@ -344,9 +401,12 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_hide_player_captions_button" -> "captions_key_icon" "revanced_hide_player_flyout_menu_ambient_mode" -> "revanced_preference_screen_ambient_mode_icon" "revanced_hide_player_flyout_menu_captions" -> "captions_key_icon" + "revanced_hide_player_flyout_menu_listen_with_youtube_music" -> "revanced_hide_player_youtube_music_button_icon" "revanced_hide_player_flyout_menu_loop_video" -> "revanced_overlay_button_always_repeat_icon" "revanced_hide_player_flyout_menu_more_info" -> "about_key_icon" - "revanced_hide_player_flyout_menu_quality_footer" -> "revanced_default_video_quality_wifi_icon" + "revanced_hide_player_flyout_menu_pip" -> "offline_key_icon" + "revanced_hide_player_flyout_menu_premium_controls" -> "premium_early_access_browse_page_key_icon" + "revanced_hide_player_flyout_menu_quality_header" -> "revanced_default_video_quality_wifi_icon" "revanced_hide_player_flyout_menu_report" -> "revanced_hide_report_button_icon" "revanced_hide_player_fullscreen_button" -> "revanced_preference_screen_fullscreen_icon" "revanced_hide_quick_actions_dislike_button" -> "revanced_preference_screen_ryd_icon" @@ -358,27 +418,33 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_hide_shorts_comments_button" -> "revanced_hide_quick_actions_comment_button_icon" "revanced_hide_shorts_dislike_button" -> "revanced_preference_screen_ryd_icon" "revanced_hide_shorts_like_button" -> "revanced_hide_quick_actions_like_button_icon" - "revanced_hide_shorts_navigation_bar" -> "revanced_preference_screen_navigation_buttons_icon" + "revanced_hide_shorts_navigation_bar" -> "revanced_preference_screen_navigation_bar_icon" "revanced_hide_shorts_shelf_home_related_videos" -> "revanced_hide_navigation_home_button_icon" "revanced_hide_shorts_shelf_subscriptions" -> "revanced_hide_navigation_subscriptions_button_icon" "revanced_hide_shorts_toolbar" -> "revanced_preference_screen_toolbar_icon" + "revanced_hide_toolbar_cast_button" -> "revanced_hide_player_cast_button_icon" + "revanced_hide_toolbar_create_button" -> "revanced_hide_navigation_create_button_icon" + "revanced_hide_toolbar_notification_button" -> "notification_key_icon" "revanced_preference_screen_account_menu" -> "account_switcher_key_icon" "revanced_preference_screen_channel_bar" -> "account_switcher_key_icon" "revanced_preference_screen_channel_profile" -> "account_switcher_key_icon" "revanced_preference_screen_comments" -> "revanced_hide_quick_actions_comment_button_icon" "revanced_preference_screen_feed_flyout_menu" -> "revanced_preference_screen_player_flyout_menu_icon" "revanced_preference_screen_haptic_feedback" -> "revanced_enable_swipe_haptic_feedback_icon" + "revanced_preference_screen_hook_buttons" -> "revanced_preference_screen_import_export_icon" "revanced_preference_screen_miniplayer" -> "offline_key_icon" "revanced_preference_screen_patch_information" -> "about_key_icon" "revanced_preference_screen_shorts_player" -> "revanced_preference_screen_shorts_icon" "revanced_preference_screen_video_filter" -> "revanced_preference_screen_video_icon" + "revanced_preference_screen_watch_history" -> "history_key_icon" "revanced_swipe_gestures_lock_mode" -> "revanced_hide_player_flyout_menu_lock_screen_icon" "revanced_disable_hdr_auto_brightness" -> "revanced_disable_hdr_video_icon" else -> "${title}_icon" } } + + private lateinit var preferenceIcon: Map private val intentIcon = intentKey.associateWith { "${it}_icon" } - private const val emptyIcon = "empty_icon" // endregion. diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt index 54e3870a9c..827458cb78 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt @@ -4,6 +4,7 @@ import app.revanced.patcher.data.BytecodeContext 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.replaceInstruction import app.revanced.patches.youtube.misc.backgroundplayback.fingerprints.BackgroundPlaybackManagerFingerprint import app.revanced.patches.youtube.misc.backgroundplayback.fingerprints.BackgroundPlaybackSettingsFingerprint import app.revanced.patches.youtube.misc.backgroundplayback.fingerprints.KidsBackgroundPlaybackPolicyControllerFingerprint @@ -13,10 +14,12 @@ import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PAC import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.patches.youtube.video.information.VideoInformationPatch +import app.revanced.util.findOpcodeIndicesReversed import app.revanced.util.getWalkerMethod import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.MethodReference @@ -27,7 +30,6 @@ object BackgroundPlaybackPatch : BaseBytecodePatch( description = "Removes restrictions on background playback, including for music and kids videos.", dependencies = setOf( PlayerTypeHookPatch::class, - VideoInformationPatch::class, SettingsPatch::class ), compatiblePackages = COMPATIBLE_PACKAGE, @@ -40,13 +42,25 @@ object BackgroundPlaybackPatch : BaseBytecodePatch( ) { override fun execute(context: BytecodeContext) { - BackgroundPlaybackManagerFingerprint.resultOrThrow().mutableMethod.addInstructions( - 0, """ - invoke-static {}, $MISC_PATH/BackgroundPlaybackPatch;->playbackIsNotShort()Z - move-result v0 - return v0 - """ - ) + BackgroundPlaybackManagerFingerprint.resultOrThrow().mutableMethod.apply { + findOpcodeIndicesReversed(Opcode.RETURN).forEach { index -> + val register = getInstruction(index).registerA + + // Replace to preserve control flow label. + replaceInstruction( + index, + "invoke-static { v$register }, $MISC_PATH/BackgroundPlaybackPatch;->allowBackgroundPlayback(Z)Z" + ) + + addInstructions( + index + 1, + """ + move-result v$register + return v$register + """ + ) + } + } // Enable background playback option in YouTube settings BackgroundPlaybackSettingsFingerprint.resultOrThrow().let { diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/fingerprints/KidsBackgroundPlaybackPolicyControllerParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/fingerprints/KidsBackgroundPlaybackPolicyControllerParentFingerprint.kt index 9f793667a9..4089f04678 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/fingerprints/KidsBackgroundPlaybackPolicyControllerParentFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/fingerprints/KidsBackgroundPlaybackPolicyControllerParentFingerprint.kt @@ -2,6 +2,7 @@ package app.revanced.patches.youtube.misc.backgroundplayback.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.utils.PlayerResponseModelUtils.PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstruction import com.android.tools.smali.dexlib2.AccessFlags @@ -11,7 +12,7 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference internal object KidsBackgroundPlaybackPolicyControllerParentFingerprint : MethodFingerprint( returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, - parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"), + parameters = listOf(PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR), customFingerprint = { methodDef, _ -> methodDef.indexOfFirstInstruction { opcode == Opcode.SGET_OBJECT diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/OpenLinksDirectlyPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/OpenLinksDirectlyPatch.kt index 0846f89704..676f2caef7 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/OpenLinksDirectlyPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/OpenLinksDirectlyPatch.kt @@ -8,10 +8,13 @@ import app.revanced.patches.youtube.misc.openlinksdirectly.fingerprints.OpenLink import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Suppress("unused") object OpenLinksDirectlyPatch : BaseBytecodePatch( @@ -32,7 +35,10 @@ object OpenLinksDirectlyPatch : BaseBytecodePatch( ).forEach { fingerprint -> fingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = getTargetIndexWithMethodReferenceNameOrThrow("parse") + val insertIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_STATIC && + getReference()?.name == "parse" + } val insertRegister = getInstruction(insertIndex).registerC diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/share/ShareSheetPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/share/ShareSheetPatch.kt index 58ec1dfb5e..1e762dc4c4 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/share/ShareSheetPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/share/ShareSheetPatch.kt @@ -13,8 +13,8 @@ import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.BottomSheetRecyclerView import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -46,8 +46,8 @@ object ShareSheetPatch : BaseBytecodePatch( // Detects that the Share sheet panel has been invoked. BottomSheetRecyclerViewFingerprint.resultOrThrow().mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(BottomSheetRecyclerView) - val targetIndex = getTargetIndexOrThrow(constIndex, Opcode.CHECK_CAST) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(BottomSheetRecyclerView) + val targetIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST) val targetRegister = getInstruction(targetIndex).registerA addInstruction( diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/ambientmode/AmbientModeSwitchPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/ambientmode/AmbientModeSwitchPatch.kt index f0c43fd8dc..f5179bfe78 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/ambientmode/AmbientModeSwitchPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/ambientmode/AmbientModeSwitchPatch.kt @@ -3,23 +3,22 @@ package app.revanced.patches.youtube.player.ambientmode import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.patch.PatchException import app.revanced.patches.youtube.player.ambientmode.fingerprints.AmbientModeInFullscreenFingerprint import app.revanced.patches.youtube.player.ambientmode.fingerprints.PowerSaveModeBroadcastReceiverFingerprint import app.revanced.patches.youtube.player.ambientmode.fingerprints.PowerSaveModeSyntheticFingerprint import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.integrations.Constants.PLAYER_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.literalInstructionBooleanHook +import app.revanced.util.findMethodOrThrow +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstStringInstructionOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction -import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Suppress("unused") @@ -46,12 +45,12 @@ object AmbientModeSwitchPatch : BaseBytecodePatch( ).forEach { (fingerprint, reversed) -> fingerprint.resultOrThrow().mutableMethod.apply { val stringIndex = - getStringInstructionIndex("android.os.action.POWER_SAVE_MODE_CHANGED") + indexOfFirstStringInstructionOrThrow("android.os.action.POWER_SAVE_MODE_CHANGED") val targetIndex = if (reversed) - getTargetIndexReversedOrThrow(stringIndex, Opcode.INVOKE_DIRECT) + indexOfFirstInstructionReversedOrThrow(stringIndex, Opcode.INVOKE_DIRECT) else - getTargetIndexOrThrow(stringIndex, Opcode.INVOKE_DIRECT) + indexOfFirstInstructionOrThrow(stringIndex, Opcode.INVOKE_DIRECT) val targetClass = (getInstruction(targetIndex).reference as MethodReference).definingClass @@ -60,34 +59,37 @@ object AmbientModeSwitchPatch : BaseBytecodePatch( } syntheticClassList.distinct().forEach { className -> - context.findClass(className)?.mutableClass?.methods?.first { method -> - method.name == "accept" - }?.apply { - for (index in implementation!!.instructions.size - 1 downTo 0) { - val instruction = getInstruction(index) - if (instruction.opcode != Opcode.INVOKE_VIRTUAL) - continue - - if (((instruction as Instruction35c).reference as MethodReference).name != "isPowerSaveMode") - continue - - val register = getInstruction(index + 1).registerA - - addInstructions( - index + 2, """ - invoke-static {v$register}, $PLAYER_CLASS_DESCRIPTOR->bypassAmbientModeRestrictions(Z)Z - move-result v$register - """ - ) - } - } ?: throw PatchException("Could not find $className") + context.findMethodOrThrow(className) { + name == "accept" + }.apply { + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + val reference = (instruction as? ReferenceInstruction)?.reference + instruction.opcode == Opcode.INVOKE_VIRTUAL && + reference is MethodReference && + reference.name == "isPowerSaveMode" + } + .map { (index, _) -> index } + .reversed() + .forEach { index -> + val register = getInstruction(index + 1).registerA + + addInstructions( + index + 2, """ + invoke-static {v$register}, $PLAYER_CLASS_DESCRIPTOR->bypassAmbientModeRestrictions(Z)Z + move-result v$register + """ + ) + } + } } // endregion // region patch for disable ambient mode in fullscreen - AmbientModeInFullscreenFingerprint.literalInstructionBooleanHook( + AmbientModeInFullscreenFingerprint.injectLiteralInstructionBooleanCall( 45389368, "$PLAYER_CLASS_DESCRIPTOR->disableAmbientModeInFullscreen()Z" ) diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/buttons/PlayerButtonsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/buttons/PlayerButtonsPatch.kt index 81888b98b3..69e42d03fb 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/buttons/PlayerButtonsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/buttons/PlayerButtonsPatch.kt @@ -24,14 +24,14 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.FullS import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.PlayerCollapseButton import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.TitleAnchor import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstruction import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction -import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction3rc @Suppress("unused") object PlayerButtonsPatch : BaseBytecodePatch( @@ -63,9 +63,10 @@ object PlayerButtonsPatch : BaseBytecodePatch( LayoutConstructorFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(AutoNavToggle) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(AutoNavToggle) val constRegister = getInstruction(constIndex).registerA - val jumpIndex = getTargetIndexOrThrow(constIndex + 2, Opcode.INVOKE_VIRTUAL) + 1 + val jumpIndex = + indexOfFirstInstructionOrThrow(constIndex + 2, Opcode.INVOKE_VIRTUAL) + 1 addInstructionsWithLabels( constIndex, """ @@ -124,9 +125,9 @@ object PlayerButtonsPatch : BaseBytecodePatch( // region patch for hide collapse button TitleAnchorFingerprint.resultOrThrow().mutableMethod.apply { - val titleAnchorConstIndex = getWideLiteralInstructionIndex(TitleAnchor) + val titleAnchorConstIndex = indexOfFirstWideLiteralInstructionValueOrThrow(TitleAnchor) val titleAnchorIndex = - getTargetIndexOrThrow(titleAnchorConstIndex, Opcode.MOVE_RESULT_OBJECT) + indexOfFirstInstructionOrThrow(titleAnchorConstIndex, Opcode.MOVE_RESULT_OBJECT) val titleAnchorRegister = getInstruction(titleAnchorIndex).registerA @@ -136,9 +137,9 @@ object PlayerButtonsPatch : BaseBytecodePatch( ) val playerCollapseButtonConstIndex = - getWideLiteralInstructionIndex(PlayerCollapseButton) + indexOfFirstWideLiteralInstructionValueOrThrow(PlayerCollapseButton) val playerCollapseButtonIndex = - getTargetIndexOrThrow(playerCollapseButtonConstIndex, Opcode.CHECK_CAST) + indexOfFirstInstructionOrThrow(playerCollapseButtonConstIndex, Opcode.CHECK_CAST) val playerCollapseButtonRegister = getInstruction(playerCollapseButtonIndex).registerA @@ -159,7 +160,7 @@ object PlayerButtonsPatch : BaseBytecodePatch( (instruction.value as? WideLiteralInstruction)?.wideLiteral == FullScreenButton } val constIndex = buttonCalls.elementAt(buttonCalls.size - 1).index - val castIndex = getTargetIndexOrThrow(constIndex, Opcode.CHECK_CAST) + val castIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST) val insertIndex = castIndex + 1 val insertRegister = getInstruction(castIndex).registerA @@ -180,8 +181,8 @@ object PlayerButtonsPatch : BaseBytecodePatch( PlayerControlsVisibilityModelFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val callIndex = getTargetIndexOrThrow(Opcode.INVOKE_DIRECT_RANGE) - val callInstruction = getInstruction(callIndex) + val callIndex = indexOfFirstInstructionOrThrow(Opcode.INVOKE_DIRECT_RANGE) + val callInstruction = getInstruction(callIndex) val hasNextParameterRegister = callInstruction.startRegister + HAS_NEXT val hasPreviousParameterRegister = callInstruction.startRegister + HAS_PREVIOUS diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/buttons/fingerprints/FullScreenButtonFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/buttons/fingerprints/FullScreenButtonFingerprint.kt index 42acc32f13..09e41630f4 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/buttons/fingerprints/FullScreenButtonFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/buttons/fingerprints/FullScreenButtonFingerprint.kt @@ -5,7 +5,7 @@ import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.CfFullscreenButton import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.FadeDurationFast import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.FullScreenButton -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object FullScreenButtonFingerprint : MethodFingerprint( @@ -13,10 +13,10 @@ internal object FullScreenButtonFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("Landroid/view/View;"), customFingerprint = handler@{ methodDef, _ -> - if (!methodDef.containsWideLiteralInstructionIndex(FullScreenButton)) + if (!methodDef.containsWideLiteralInstructionValue(FullScreenButton)) return@handler false - methodDef.containsWideLiteralInstructionIndex(FadeDurationFast) // YouTube 18.29.38 ~ YouTube 19.18.41 - || methodDef.containsWideLiteralInstructionIndex(CfFullscreenButton) // YouTube 19.19.39 ~ + methodDef.containsWideLiteralInstructionValue(FadeDurationFast) // YouTube 18.29.38 ~ YouTube 19.18.41 + || methodDef.containsWideLiteralInstructionValue(CfFullscreenButton) // YouTube 19.19.39 ~ }, ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/buttons/fingerprints/TitleAnchorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/buttons/fingerprints/TitleAnchorFingerprint.kt index 263e4264dc..a6c5b2eaa6 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/buttons/fingerprints/TitleAnchorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/buttons/fingerprints/TitleAnchorFingerprint.kt @@ -3,12 +3,12 @@ package app.revanced.patches.youtube.player.buttons.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.PlayerCollapseButton import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.TitleAnchor -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue internal object TitleAnchorFingerprint : MethodFingerprint( returnType = "V", customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(PlayerCollapseButton) - && methodDef.containsWideLiteralInstructionIndex(TitleAnchor) + methodDef.containsWideLiteralInstructionValue(PlayerCollapseButton) + && methodDef.containsWideLiteralInstructionValue(TitleAnchor) } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/comments/CommentsComponentPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/comments/CommentsComponentPatch.kt index 1320632503..8add5c0f8a 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/comments/CommentsComponentPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/comments/CommentsComponentPatch.kt @@ -12,9 +12,9 @@ import app.revanced.patches.youtube.utils.integrations.Constants.COMPONENTS_PATH import app.revanced.patches.youtube.utils.integrations.Constants.PLAYER_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexOrThrow import app.revanced.util.getWalkerMethod -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -56,14 +56,15 @@ object CommentsComponentPatch : BaseBytecodePatch( ShortsLiveStreamEmojiPickerOnClickListenerFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val emojiPickerEndpointIndex = getWideLiteralInstructionIndex(126326492) + val emojiPickerEndpointIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(126326492) val emojiPickerOnClickListenerIndex = - getTargetIndexOrThrow(emojiPickerEndpointIndex, Opcode.INVOKE_DIRECT) + indexOfFirstInstructionOrThrow(emojiPickerEndpointIndex, Opcode.INVOKE_DIRECT) val emojiPickerOnClickListenerMethod = getWalkerMethod(context, emojiPickerOnClickListenerIndex) emojiPickerOnClickListenerMethod.apply { - val insertIndex = getTargetIndexOrThrow(Opcode.IF_EQZ) + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.IF_EQZ) val insertRegister = getInstruction(insertIndex).registerA diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt index c5ecd85d51..eca7754ce9 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt @@ -9,9 +9,9 @@ import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.patch.PatchException import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.shared.fingerprints.StartVideoInformerFingerprint import app.revanced.patches.shared.litho.LithoFilterPatch import app.revanced.patches.youtube.player.components.fingerprints.CrowdfundingBoxFingerprint -import app.revanced.patches.youtube.player.components.fingerprints.EngagementPanelControllerFingerprint import app.revanced.patches.youtube.player.components.fingerprints.FilmStripOverlayConfigFingerprint import app.revanced.patches.youtube.player.components.fingerprints.FilmStripOverlayInteractionFingerprint import app.revanced.patches.youtube.player.components.fingerprints.FilmStripOverlayParentFingerprint @@ -33,7 +33,7 @@ import app.revanced.patches.youtube.player.components.fingerprints.WatermarkPare import app.revanced.patches.youtube.player.speedoverlay.SpeedOverlayPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.controlsoverlay.ControlsOverlayConfigPatch -import app.revanced.patches.youtube.utils.fingerprints.StartVideoInformerFingerprint +import app.revanced.patches.youtube.utils.fingerprints.EngagementPanelBuilderFingerprint import app.revanced.patches.youtube.utils.fingerprints.YouTubeControlsOverlayFingerprint import app.revanced.patches.youtube.utils.fix.suggestedvideoendscreen.SuggestedVideoEndScreenPatch import app.revanced.patches.youtube.utils.integrations.Constants.COMPONENTS_PATH @@ -48,11 +48,12 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.TapBl import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.video.information.VideoInformationPatch import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.getWideLiteralInstructionIndex -import app.revanced.util.literalInstructionViewHook +import app.revanced.util.findMethodOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.injectLiteralInstructionViewCall import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -61,6 +62,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Suppress("unused") object PlayerComponentsPatch : BaseBytecodePatch( @@ -79,7 +81,7 @@ object PlayerComponentsPatch : BaseBytecodePatch( compatiblePackages = COMPATIBLE_PACKAGE, fingerprints = setOf( CrowdfundingBoxFingerprint, - EngagementPanelControllerFingerprint, + EngagementPanelBuilderFingerprint, FilmStripOverlayParentFingerprint, InfoCardsIncognitoFingerprint, LayoutCircleFingerprint, @@ -107,8 +109,8 @@ object PlayerComponentsPatch : BaseBytecodePatch( YouTubeControlsOverlayFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(ScrimOverlay) - val targetIndex = getTargetIndexOrThrow(constIndex, Opcode.CHECK_CAST) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(ScrimOverlay) + val targetIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST) val targetParameter = getInstruction(targetIndex).reference val targetRegister = getInstruction(targetIndex).registerA @@ -144,20 +146,19 @@ object PlayerComponentsPatch : BaseBytecodePatch( if (fingerprint == StartVideoInformerFingerprint) { hookInitVideoPanel(1) } else { - val syntheticIndex = getTargetIndexOrThrow(Opcode.NEW_INSTANCE) + val syntheticIndex = + indexOfFirstInstructionOrThrow(Opcode.NEW_INSTANCE) val syntheticReference = getInstruction(syntheticIndex).reference.toString() - val syntheticClass = - context.findClass(syntheticReference)!!.mutableClass - syntheticClass.methods.find { method -> method.name == "onClick" } - ?.hookInitVideoPanel(0) - ?: throw PatchException("Could not find onClick method in $syntheticReference") + context.findMethodOrThrow(syntheticReference) { + name == "onClick" + }.hookInitVideoPanel(0) } } } - EngagementPanelControllerFingerprint.resultOrThrow().let { + EngagementPanelBuilderFingerprint.resultOrThrow().let { it.mutableMethod.apply { addInstructionsWithLabels( 0, """ @@ -228,7 +229,7 @@ object PlayerComponentsPatch : BaseBytecodePatch( DarkBackground, TapBloomView ).forEach { literal -> - QuickSeekOverlayFingerprint.literalInstructionViewHook( + QuickSeekOverlayFingerprint.injectLiteralInstructionViewCall( literal, smaliInstruction ) @@ -273,10 +274,10 @@ object PlayerComponentsPatch : BaseBytecodePatch( YouTubeControlsOverlayFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(FadeDurationFast) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(FadeDurationFast) val constRegister = getInstruction(constIndex).registerA val insertIndex = - getTargetIndexReversedOrThrow(constIndex, Opcode.INVOKE_VIRTUAL) + 1 + indexOfFirstInstructionReversedOrThrow(constIndex, Opcode.INVOKE_VIRTUAL) + 1 val jumpIndex = implementation!!.instructions.let { instruction -> insertIndex + instruction.subList(insertIndex, instruction.size - 1) .indexOfFirst { instructions -> @@ -341,11 +342,14 @@ object PlayerComponentsPatch : BaseBytecodePatch( YouTubeControlsOverlayFingerprint.resultOrThrow().let { result -> result.mutableMethod.apply { - val insertIndex = getWideLiteralInstructionIndex(SeekUndoEduOverlayStub) + val insertIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(SeekUndoEduOverlayStub) val insertRegister = getInstruction(insertIndex).registerA - val onClickListenerIndex = - getTargetIndexWithMethodReferenceNameOrThrow(insertIndex, "setOnClickListener") + val onClickListenerIndex = indexOfFirstInstructionOrThrow(insertIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setOnClickListener" + } val constComponent = getConstComponent(insertIndex, onClickListenerIndex - 1) if (constComponent.isNotEmpty()) { @@ -390,8 +394,10 @@ object PlayerComponentsPatch : BaseBytecodePatch( it.mutableClass.methods.find { method -> method.parameters == listOf("Landroid/view/View${'$'}OnClickListener;") }?.apply { - val setOnClickListenerIndex = - getTargetIndexWithMethodReferenceNameOrThrow("setOnClickListener") + val setOnClickListenerIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setOnClickListener" + } val setOnClickListenerRegister = getInstruction(setOnClickListenerIndex).registerC diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/components/fingerprints/QuickSeekOverlayFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/components/fingerprints/QuickSeekOverlayFingerprint.kt index b07040639f..1b7cb1ffa4 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/components/fingerprints/QuickSeekOverlayFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/components/fingerprints/QuickSeekOverlayFingerprint.kt @@ -3,13 +3,13 @@ package app.revanced.patches.youtube.player.components.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.DarkBackground import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.TapBloomView -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue internal object QuickSeekOverlayFingerprint : MethodFingerprint( returnType = "V", parameters = emptyList(), customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(DarkBackground) - && methodDef.containsWideLiteralInstructionIndex(TapBloomView) + methodDef.containsWideLiteralInstructionValue(DarkBackground) + && methodDef.containsWideLiteralInstructionValue(TapBloomView) }, ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/DescriptionComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/DescriptionComponentsPatch.kt index 42935cd344..c16e31639c 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/DescriptionComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/DescriptionComponentsPatch.kt @@ -10,6 +10,7 @@ import app.revanced.patches.shared.litho.LithoFilterPatch import app.revanced.patches.youtube.player.descriptions.fingerprints.EngagementPanelTitleFingerprint import app.revanced.patches.youtube.player.descriptions.fingerprints.EngagementPanelTitleParentFingerprint import app.revanced.patches.youtube.player.descriptions.fingerprints.TextViewComponentFingerprint +import app.revanced.patches.youtube.player.descriptions.fingerprints.TextViewComponentFingerprint.indexOfTextIsSelectableInstruction import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.fingerprints.RollingNumberTextViewAnimationUpdateFingerprint import app.revanced.patches.youtube.utils.fingerprints.RollingNumberTextViewFingerprint @@ -19,10 +20,14 @@ import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch import app.revanced.patches.youtube.utils.recyclerview.BottomSheetRecyclerViewPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.alsoResolve +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Suppress("unused") object DescriptionComponentsPatch : BaseBytecodePatch( @@ -53,23 +58,23 @@ object DescriptionComponentsPatch : BaseBytecodePatch( // In order to maintain compatibility with YouTube v18.48.39 or previous versions, // This patch is applied only to the version after YouTube v18.49.37. if (SettingsPatch.upward1849) { - RollingNumberTextViewAnimationUpdateFingerprint.resolve( - context, - RollingNumberTextViewFingerprint.resultOrThrow().classDef - ) - RollingNumberTextViewAnimationUpdateFingerprint.resultOrThrow().let { + RollingNumberTextViewAnimationUpdateFingerprint.alsoResolve( + context, RollingNumberTextViewFingerprint + ).let { it.mutableMethod.apply { val freeRegister = implementation!!.registerCount - parameters.size - 2 val imageSpanIndex = it.scanResult.patternScanResult!!.startIndex - val setTextIndex = getTargetIndexWithMethodReferenceNameOrThrow("setText") - + val setTextIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setText" + } addInstruction(setTextIndex, "nop") addInstructionsWithLabels( imageSpanIndex, """ - invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->disableRollingNumberAnimations()Z - move-result v$freeRegister - if-nez v$freeRegister, :disable_animations - """, ExternalLabel("disable_animations", getInstruction(setTextIndex)) + invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->disableRollingNumberAnimations()Z + move-result v$freeRegister + if-nez v$freeRegister, :disable_animations + """, ExternalLabel("disable_animations", getInstruction(setTextIndex)) ) } } @@ -94,8 +99,7 @@ object DescriptionComponentsPatch : BaseBytecodePatch( if (SettingsPatch.upward1902) { TextViewComponentFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = - getTargetIndexWithMethodReferenceNameOrThrow("setTextIsSelectable") + val insertIndex = indexOfTextIsSelectableInstruction(this) val insertInstruction = getInstruction(insertIndex) replaceInstruction( @@ -106,13 +110,11 @@ object DescriptionComponentsPatch : BaseBytecodePatch( } } - EngagementPanelTitleFingerprint.resolve( - context, - EngagementPanelTitleParentFingerprint.resultOrThrow().classDef - ) - EngagementPanelTitleFingerprint.resultOrThrow().mutableMethod.apply { - val contentDescriptionIndex = - getTargetIndexWithMethodReferenceNameOrThrow("setContentDescription") + EngagementPanelTitleFingerprint.alsoResolve( + context, EngagementPanelTitleParentFingerprint + ).mutableMethod.apply { + val contentDescriptionIndex = EngagementPanelTitleFingerprint + .indexOfContentDescriptionInstruction(this) val contentDescriptionRegister = getInstruction(contentDescriptionIndex).registerD diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/fingerprints/EngagementPanelTitleFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/fingerprints/EngagementPanelTitleFingerprint.kt index 723f7803ed..4fee2997d2 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/fingerprints/EngagementPanelTitleFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/fingerprints/EngagementPanelTitleFingerprint.kt @@ -1,8 +1,22 @@ package app.revanced.patches.youtube.player.descriptions.fingerprints -import app.revanced.util.fingerprint.MethodReferenceNameFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.player.descriptions.fingerprints.EngagementPanelTitleFingerprint.indexOfContentDescriptionInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversed +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 object EngagementPanelTitleFingerprint : MethodReferenceNameFingerprint( +internal object EngagementPanelTitleFingerprint : MethodFingerprint( strings = listOf(". "), - reference = { "setContentDescription" } -) \ No newline at end of file + customFingerprint = { methodDef, _ -> + indexOfContentDescriptionInstruction(methodDef) >= 0 + } +) { + fun indexOfContentDescriptionInstruction(methodDef: Method) = + methodDef.indexOfFirstInstructionReversed { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setContentDescription" + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/fingerprints/TextViewComponentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/fingerprints/TextViewComponentFingerprint.kt index a791b12b79..cb6634fe75 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/fingerprints/TextViewComponentFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/fingerprints/TextViewComponentFingerprint.kt @@ -1,14 +1,30 @@ package app.revanced.patches.youtube.player.descriptions.fingerprints -import app.revanced.util.fingerprint.MethodReferenceNameFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.player.descriptions.fingerprints.TextViewComponentFingerprint.indexOfTextIsSelectableInstruction +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.reference.MethodReference /** * This fingerprint is compatible with YouTube v18.35.xx~ * Nonetheless, the patch works in YouTube v19.02.xx~ */ -internal object TextViewComponentFingerprint : MethodReferenceNameFingerprint( +internal object TextViewComponentFingerprint : MethodFingerprint( returnType = "V", opcodes = listOf(Opcode.CMPL_FLOAT), - reference = { "setBreakStrategy" } -) + customFingerprint = { methodDef, _ -> + methodDef.implementation != null && + indexOfTextIsSelectableInstruction(methodDef) >= 0 + }, +) { + fun indexOfTextIsSelectableInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL && + reference?.name == "setTextIsSelectable" && + reference.definingClass != "Landroid/widget/TextView;" + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/PlayerFlyoutMenuPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/PlayerFlyoutMenuPatch.kt index 6688bbcae5..5b48b26061 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/PlayerFlyoutMenuPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/PlayerFlyoutMenuPatch.kt @@ -16,12 +16,15 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.BottomSheetFooterText import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.literalInstructionBooleanHook -import app.revanced.util.literalInstructionViewHook +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall +import app.revanced.util.injectLiteralInstructionViewCall import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Suppress("unused") object PlayerFlyoutMenuPatch : BaseBytecodePatch( @@ -56,7 +59,7 @@ object PlayerFlyoutMenuPatch : BaseBytecodePatch( val smaliInstruction = """ invoke-static {v$REGISTER_TEMPLATE_REPLACEMENT}, $PLAYER_CLASS_DESCRIPTOR->$name(Landroid/view/View;)V """ - fingerprint.literalInstructionViewHook(BottomSheetFooterText, smaliInstruction) + fingerprint.injectLiteralInstructionViewCall(BottomSheetFooterText, smaliInstruction) } arrayOf( @@ -64,15 +67,17 @@ object PlayerFlyoutMenuPatch : BaseBytecodePatch( QualityMenuViewInflateFingerprint ).forEach { fingerprint -> fingerprint.resultOrThrow().mutableMethod.apply { - val insertIndex = getTargetIndexWithMethodReferenceNameOrThrow("addHeaderView") + val insertIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "addHeaderView" + } val insertRegister = getInstruction(insertIndex).registerD addInstructions( - insertIndex, - """ - invoke-static {v$insertRegister}, $PLAYER_CLASS_DESCRIPTOR->hidePlayerFlyoutMenuQualityHeader(Landroid/view/View;)Landroid/view/View; - move-result-object v$insertRegister - """ + insertIndex, """ + invoke-static {v$insertRegister}, $PLAYER_CLASS_DESCRIPTOR->hidePlayerFlyoutMenuQualityHeader(Landroid/view/View;)Landroid/view/View; + move-result-object v$insertRegister + """ ) } } @@ -95,7 +100,7 @@ object PlayerFlyoutMenuPatch : BaseBytecodePatch( SettingsPatch.updatePatchStatus(this) if (SettingsPatch.upward1839) { - PiPModeConfigFingerprint.literalInstructionBooleanHook( + PiPModeConfigFingerprint.injectLiteralInstructionBooleanCall( 45427407, "$PLAYER_CLASS_DESCRIPTOR->hidePiPModeMenu(Z)Z" ) diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/fingerprints/CaptionsBottomSheetFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/fingerprints/CaptionsBottomSheetFingerprint.kt index 5a1cbf5c53..4c086f4a20 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/fingerprints/CaptionsBottomSheetFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/fingerprints/CaptionsBottomSheetFingerprint.kt @@ -4,13 +4,13 @@ import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.BottomSheetFooterText import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.SubtitleMenuSettingsFooterInfo -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object CaptionsBottomSheetFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(BottomSheetFooterText) - && methodDef.containsWideLiteralInstructionIndex(SubtitleMenuSettingsFooterInfo) + methodDef.containsWideLiteralInstructionValue(BottomSheetFooterText) + && methodDef.containsWideLiteralInstructionValue(SubtitleMenuSettingsFooterInfo) } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/toggle/ChangeTogglePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/toggle/ChangeTogglePatch.kt index 9140067b64..3a2d60ae2b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/toggle/ChangeTogglePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/toggle/ChangeTogglePatch.kt @@ -17,11 +17,10 @@ import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PAC import app.revanced.patches.youtube.utils.integrations.Constants.PLAYER_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.util.getReference -import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow import app.revanced.util.indexOfFirstInstruction import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstStringInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -139,13 +138,15 @@ object ChangeTogglePatch : BaseBytecodePatch( } val classRegister = getInstruction(iGetIndex).registerB - val stringIndex = getStringInstructionIndex("menu_item_cinematic_lighting") + val stringIndex = + indexOfFirstStringInstructionOrThrow("menu_item_cinematic_lighting") - val checkCastIndex = getTargetIndexReversedOrThrow(stringIndex, Opcode.CHECK_CAST) + val checkCastIndex = + indexOfFirstInstructionReversedOrThrow(stringIndex, Opcode.CHECK_CAST) val iGetObjectPrimaryIndex = - getTargetIndexReversedOrThrow(checkCastIndex, Opcode.IGET_OBJECT) + indexOfFirstInstructionReversedOrThrow(checkCastIndex, Opcode.IGET_OBJECT) val iGetObjectSecondaryIndex = - getTargetIndexOrThrow(checkCastIndex, Opcode.IGET_OBJECT) + indexOfFirstInstructionOrThrow(checkCastIndex, Opcode.IGET_OBJECT) val checkCastReference = getInstruction(checkCastIndex).reference @@ -154,14 +155,15 @@ object ChangeTogglePatch : BaseBytecodePatch( val iGetObjectSecondaryReference = getInstruction(iGetObjectSecondaryIndex).reference - val invokeVirtualIndex = getTargetIndexOrThrow(stringIndex, Opcode.INVOKE_VIRTUAL) + val invokeVirtualIndex = + indexOfFirstInstructionOrThrow(stringIndex, Opcode.INVOKE_VIRTUAL) val invokeVirtualInstruction = getInstruction(invokeVirtualIndex) val freeRegisterC = invokeVirtualInstruction.registerC val freeRegisterD = invokeVirtualInstruction.registerD val freeRegisterE = invokeVirtualInstruction.registerE - val insertIndex = getTargetIndexOrThrow(stringIndex, Opcode.RETURN_VOID) + val insertIndex = indexOfFirstInstructionOrThrow(stringIndex, Opcode.RETURN_VOID) addInstructionsWithLabels( insertIndex, """ diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/FullscreenComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/FullscreenComponentsPatch.kt index e90ec3c648..4846391cc0 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/FullscreenComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/FullscreenComponentsPatch.kt @@ -29,13 +29,12 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.AutoN import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.FullScreenEngagementPanel import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.QuickActionsElementContainer import app.revanced.patches.youtube.utils.settings.SettingsPatch +import app.revanced.util.findMethodOrThrow import app.revanced.util.getReference -import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow import app.revanced.util.getWalkerMethod -import app.revanced.util.getWideLiteralInstructionIndex import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstStringInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import app.revanced.util.updatePatchStatus @@ -80,8 +79,9 @@ object FullscreenComponentsPatch : BaseBytecodePatch( EngagementPanelFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val literalIndex = getWideLiteralInstructionIndex(FullScreenEngagementPanel) - val targetIndex = getTargetIndexOrThrow(literalIndex, Opcode.CHECK_CAST) + val literalIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(FullScreenEngagementPanel) + val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.CHECK_CAST) val targetRegister = getInstruction(targetIndex).registerA addInstruction( @@ -94,7 +94,10 @@ object FullscreenComponentsPatch : BaseBytecodePatch( PlayerTitleViewFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = getTargetIndexWithMethodReferenceNameOrThrow("addView") + val insertIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "addView" + } val insertReference = getInstruction(insertIndex).reference.toString() if (!insertReference.startsWith("Landroid/widget/FrameLayout;")) @@ -115,9 +118,10 @@ object FullscreenComponentsPatch : BaseBytecodePatch( LayoutConstructorFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(AutoNavPreviewStub) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(AutoNavPreviewStub) val constRegister = getInstruction(constIndex).registerA - val jumpIndex = getTargetIndexOrThrow(constIndex + 2, Opcode.INVOKE_VIRTUAL) + 1 + val jumpIndex = + indexOfFirstInstructionOrThrow(constIndex + 2, Opcode.INVOKE_VIRTUAL) + 1 addInstructionsWithLabels( constIndex, """ @@ -159,13 +163,13 @@ object FullscreenComponentsPatch : BaseBytecodePatch( } val constIndex = containerCalls.elementAt(containerCalls.size - 1).index - val checkCastIndex = getTargetIndexOrThrow(constIndex, Opcode.CHECK_CAST) + val checkCastIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST) val insertRegister = getInstruction(checkCastIndex).registerA addInstruction( checkCastIndex + 1, - "invoke-static {v$insertRegister}, $PLAYER_CLASS_DESCRIPTOR->setQuickActionMargin(Landroid/widget/FrameLayout;)V" + "invoke-static {v$insertRegister}, $PLAYER_CLASS_DESCRIPTOR->setQuickActionMargin(Landroid/view/View;)V" ) addInstruction( @@ -183,9 +187,11 @@ object FullscreenComponentsPatch : BaseBytecodePatch( YouTubeControlsOverlayFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = - getTargetIndexWithMethodReferenceNameOrThrow("setFocusableInTouchMode") - val walkerIndex = getTargetIndexOrThrow(targetIndex, Opcode.INVOKE_STATIC) + val targetIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setFocusableInTouchMode" + } + val walkerIndex = indexOfFirstInstructionOrThrow(targetIndex, Opcode.INVOKE_STATIC) val walkerMethod = getWalkerMethod(context, walkerIndex) walkerMethod.apply { @@ -209,14 +215,14 @@ object FullscreenComponentsPatch : BaseBytecodePatch( ClientSettingEndpointFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val getActivityIndex = getStringInstructionIndex("watch") + 2 + val getActivityIndex = indexOfFirstStringInstructionOrThrow("watch") + 2 val getActivityReference = getInstruction(getActivityIndex).reference val classRegister = getInstruction(getActivityIndex).registerB val watchDescriptorMethodIndex = - getStringInstructionIndex("start_watch_minimized") - 1 + indexOfFirstStringInstructionOrThrow("start_watch_minimized") - 1 val watchDescriptorRegister = getInstruction(watchDescriptorMethodIndex).registerD @@ -228,7 +234,7 @@ object FullscreenComponentsPatch : BaseBytecodePatch( ) // hooks Activity. - val insertIndex = getStringInstructionIndex("force_fullscreen") + val insertIndex = indexOfFirstStringInstructionOrThrow("force_fullscreen") val freeRegister = getInstruction(insertIndex).registerA addInstructions( @@ -244,21 +250,20 @@ object FullscreenComponentsPatch : BaseBytecodePatch( VideoPortraitParentFingerprint.resultOrThrow().let { it.mutableMethod.apply { val stringIndex = - getStringInstructionIndex("Acquiring NetLatencyActionLogger failed. taskId=") - val invokeIndex = getTargetIndexOrThrow(stringIndex, Opcode.INVOKE_INTERFACE) - val targetIndex = getTargetIndexOrThrow(invokeIndex, Opcode.CHECK_CAST) - val targetClass = context - .findClass(getInstruction(targetIndex).reference.toString())!! - .mutableClass + indexOfFirstStringInstructionOrThrow("Acquiring NetLatencyActionLogger failed. taskId=") + val invokeIndex = + indexOfFirstInstructionOrThrow(stringIndex, Opcode.INVOKE_INTERFACE) + val targetIndex = indexOfFirstInstructionOrThrow(invokeIndex, Opcode.CHECK_CAST) + val targetClass = + getInstruction(targetIndex).reference.toString() // add an instruction to check the vertical video - targetClass.methods.find { method -> method.parameters == listOf("I", "I", "Z") } - ?.apply { - addInstruction( - 1, - "invoke-static {p1, p2}, $PLAYER_CLASS_DESCRIPTOR->setVideoPortrait(II)V" - ) - } ?: throw PatchException("Could not find targetMethod") + context.findMethodOrThrow(targetClass) { + parameters == listOf("I", "I", "Z") + }.addInstruction( + 1, + "invoke-static {p1, p2}, $PLAYER_CLASS_DESCRIPTOR->setVideoPortrait(II)V" + ) } } @@ -275,14 +280,11 @@ object FullscreenComponentsPatch : BaseBytecodePatch( } val walkerMethod = getWalkerMethod(context, walkerIndex) - val targetClass = - context.findClass(walkerMethod.definingClass)!!.mutableClass - val constructorMethod = targetClass - .methods - .find { method -> - method.name == "" - && method.parameterTypes == listOf("Landroid/app/Activity;") - } ?: throw PatchException("Constructor method not found!") + val constructorMethod = + context.findMethodOrThrow(walkerMethod.definingClass) { + name == "" && + parameterTypes == listOf("Landroid/app/Activity;") + } arrayOf( walkerMethod, @@ -328,8 +330,9 @@ object FullscreenComponentsPatch : BaseBytecodePatch( BroadcastReceiverFingerprint.resultOrThrow().let { result -> result.mutableMethod.apply { val stringIndex = - getStringInstructionIndex("android.intent.action.SCREEN_ON") - val insertIndex = getTargetIndexOrThrow(stringIndex, Opcode.IF_EQZ) + 1 + indexOfFirstStringInstructionOrThrow("android.intent.action.SCREEN_ON") + val insertIndex = + indexOfFirstInstructionOrThrow(stringIndex, Opcode.IF_EQZ) + 1 addInstruction( insertIndex, diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/fingerprints/PlayerTitleViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/fingerprints/PlayerTitleViewFingerprint.kt index 9529c1de94..1f8cc8b053 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/fingerprints/PlayerTitleViewFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/fingerprints/PlayerTitleViewFingerprint.kt @@ -1,12 +1,9 @@ package app.revanced.patches.youtube.player.fullscreen.fingerprints -import app.revanced.patcher.extensions.or import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.PlayerVideoTitleView import app.revanced.util.fingerprint.LiteralValueFingerprint -import com.android.tools.smali.dexlib2.AccessFlags internal object PlayerTitleViewFingerprint : LiteralValueFingerprint( - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, returnType = "V", literalSupplier = { PlayerVideoTitleView } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/hapticfeedback/HapticFeedBackPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/hapticfeedback/HapticFeedBackPatch.kt index c1568aef0e..2712908f86 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/hapticfeedback/HapticFeedBackPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/hapticfeedback/HapticFeedBackPatch.kt @@ -13,7 +13,7 @@ import app.revanced.patches.youtube.player.hapticfeedback.fingerprints.ZoomHapti import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.integrations.Constants.PLAYER_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexOrThrow +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -65,7 +65,7 @@ object HapticFeedBackPatch : BaseBytecodePatch( var register = 0 if (name == "run") { - index = getTargetIndexOrThrow(Opcode.SGET) + index = indexOfFirstInstructionOrThrow(Opcode.SGET) register = getInstruction(index).registerA } diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt index 17265a15df..6d93672b79 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt @@ -30,20 +30,20 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ReelT import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch.contexts import app.revanced.patches.youtube.video.information.VideoInformationPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.findMethodsOrThrow +import app.revanced.util.getReference import app.revanced.util.getWalkerMethod -import app.revanced.util.getWideLiteralInstructionIndex -import app.revanced.util.literalInstructionBooleanHook +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c -import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedMethodReference import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference import org.w3c.dom.Element @Suppress("DEPRECATION", "unused") @@ -83,14 +83,15 @@ object SeekbarComponentsPatch : BaseBytecodePatch( SeekbarTappingFingerprint.resultOrThrow().let { it.mutableMethod.apply { val tapSeekIndex = it.scanResult.patternScanResult!!.startIndex + 1 - val tapSeekReference = getInstruction(tapSeekIndex).reference - val tapSeekClass = - context - .findClass((tapSeekReference as DexBackedMethodReference).definingClass)!! - .mutableClass - val tapSeekMethods = mutableMapOf() - - for (method in tapSeekClass.methods) { + val tapSeekClass = getInstruction(tapSeekIndex) + .getReference()!! + .definingClass + + val tapSeekMethods = context.findMethodsOrThrow(tapSeekClass) + var pMethodCall = "" + var oMethodCall = "" + + for (method in tapSeekMethods) { if (method.implementation == null) continue @@ -109,15 +110,17 @@ object SeekbarComponentsPatch : BaseBytecodePatch( // method founds if (literal == 1) - tapSeekMethods["P"] = method + pMethodCall = "${method.definingClass}->${method.name}(I)V" else if (literal == 2) - tapSeekMethods["O"] = method + oMethodCall = "${method.definingClass}->${method.name}(I)V" } - val pMethod = tapSeekMethods["P"] - ?: throw PatchException("pMethod not found") - val oMethod = tapSeekMethods["O"] - ?: throw PatchException("oMethod not found") + if (pMethodCall.isEmpty()) { + throw PatchException("pMethod not found") + } + if (oMethodCall.isEmpty()) { + throw PatchException("oMethod not found") + } val insertIndex = it.scanResult.patternScanResult!!.startIndex + 2 @@ -126,8 +129,8 @@ object SeekbarComponentsPatch : BaseBytecodePatch( invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->enableSeekbarTapping()Z move-result v0 if-eqz v0, :disabled - invoke-virtual { p0, v2 }, ${oMethod.definingClass}->${oMethod.name}(I)V - invoke-virtual { p0, v2 }, ${pMethod.definingClass}->${pMethod.name}(I)V + invoke-virtual { p0, v2 }, $pMethodCall + invoke-virtual { p0, v2 }, $oMethodCall """, ExternalLabel("disabled", getInstruction(insertIndex)) ) } @@ -139,11 +142,14 @@ object SeekbarComponentsPatch : BaseBytecodePatch( TotalTimeFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val charSequenceIndex = - getTargetIndexWithMethodReferenceNameOrThrow("getString") + 1 + val charSequenceIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "getString" + } + 1 val charSequenceRegister = getInstruction(charSequenceIndex).registerA - val textViewIndex = getTargetIndexWithMethodReferenceNameOrThrow("getText") + val textViewIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "getText" + } val textViewRegister = getInstruction(textViewIndex).registerC @@ -162,12 +168,12 @@ object SeekbarComponentsPatch : BaseBytecodePatch( // region patch for seekbar color PlayerSeekbarColorFingerprint.resultOrThrow().mutableMethod.apply { - hook(getWideLiteralInstructionIndex(InlineTimeBarColorizedBarPlayedColorDark) + 2) - hook(getWideLiteralInstructionIndex(InlineTimeBarPlayedNotHighlightedColor) + 2) + hook(InlineTimeBarColorizedBarPlayedColorDark) + hook(InlineTimeBarPlayedNotHighlightedColor) } ShortsSeekbarColorFingerprint.resultOrThrow().mutableMethod.apply { - hook(getWideLiteralInstructionIndex(ReelTimeBarPlayedColor) + 2) + hook(ReelTimeBarPlayedColor) } ControlsOverlayStyleFingerprint.resultOrThrow().let { @@ -212,7 +218,7 @@ object SeekbarComponentsPatch : BaseBytecodePatch( PlayerButtonsVisibilityFingerprint.resultOrThrow().let { it.mutableMethod.apply { val freeRegister = implementation!!.registerCount - parameters.size - 2 - val viewIndex = getTargetIndexOrThrow(Opcode.INVOKE_INTERFACE) + val viewIndex = indexOfFirstInstructionOrThrow(Opcode.INVOKE_INTERFACE) val viewRegister = getInstruction(viewIndex).registerD addInstructionsWithLabels( @@ -271,7 +277,7 @@ object SeekbarComponentsPatch : BaseBytecodePatch( // region patch for restore old seekbar thumbnails ThumbnailPreviewConfigFingerprint.result?.let { - ThumbnailPreviewConfigFingerprint.literalInstructionBooleanHook( + ThumbnailPreviewConfigFingerprint.injectLiteralInstructionBooleanCall( 45398577, "$PLAYER_CLASS_DESCRIPTOR->restoreOldSeekbarThumbnails()Z" ) @@ -285,7 +291,7 @@ object SeekbarComponentsPatch : BaseBytecodePatch( // region patch for enable cairo seekbar if (SettingsPatch.upward1923) { - CairoSeekbarConfigFingerprint.literalInstructionBooleanHook( + CairoSeekbarConfigFingerprint.injectLiteralInstructionBooleanCall( 45617850, "$PLAYER_CLASS_DESCRIPTOR->enableCairoSeekbar()Z" ) @@ -303,7 +309,8 @@ object SeekbarComponentsPatch : BaseBytecodePatch( SettingsPatch.updatePatchStatus(this) } - private fun MutableMethod.hook(insertIndex: Int) { + private fun MutableMethod.hook(literal: Long) { + val insertIndex = indexOfFirstWideLiteralInstructionValueOrThrow(literal) + 2 val insertRegister = getInstruction(insertIndex).registerA addInstructions( diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/SpeedOverlayPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/SpeedOverlayPatch.kt index 2c6f1ef1b7..ae20d453e5 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/SpeedOverlayPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/SpeedOverlayPatch.kt @@ -6,7 +6,6 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.smali.ExternalLabel @@ -15,18 +14,17 @@ import app.revanced.patches.youtube.player.speedoverlay.fingerprints.NextGenWatc import app.revanced.patches.youtube.player.speedoverlay.fingerprints.RestoreSlideToSeekBehaviorFingerprint import app.revanced.patches.youtube.player.speedoverlay.fingerprints.SlideToSeekMotionEventFingerprint import app.revanced.patches.youtube.player.speedoverlay.fingerprints.SpeedOverlayFingerprint +import app.revanced.patches.youtube.player.speedoverlay.fingerprints.SpeedOverlayFloatValueFingerprint import app.revanced.patches.youtube.player.speedoverlay.fingerprints.SpeedOverlayTextValueFingerprint -import app.revanced.patches.youtube.player.speedoverlay.fingerprints.SpeedOverlayValueFingerprint import app.revanced.patches.youtube.utils.integrations.Constants.PLAYER_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch +import app.revanced.util.alsoResolve +import app.revanced.util.findMethodOrThrow import app.revanced.util.getReference -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameReversedOrThrow import app.revanced.util.getWalkerMethod import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.literalInstructionBooleanHook +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction @@ -35,6 +33,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Patch(dependencies = [SharedResourceIdPatch::class]) object SpeedOverlayPatch : BytecodePatch( @@ -43,8 +42,8 @@ object SpeedOverlayPatch : BytecodePatch( NextGenWatchLayoutFingerprint, RestoreSlideToSeekBehaviorFingerprint, SpeedOverlayFingerprint, + SpeedOverlayFloatValueFingerprint, SpeedOverlayTextValueFingerprint, - SpeedOverlayValueFingerprint, ) ) { override fun execute(context: BytecodeContext) { @@ -52,36 +51,33 @@ object SpeedOverlayPatch : BytecodePatch( val restoreSlideToSeekBehaviorFingerprintResult = RestoreSlideToSeekBehaviorFingerprint.result val speedOverlayFingerprintResult = SpeedOverlayFingerprint.result - val speedOverlayValueFingerprintResult = SpeedOverlayValueFingerprint.result + val speedOverlayFloatValueFingerprintResult = SpeedOverlayFloatValueFingerprint.result val resolvable = restoreSlideToSeekBehaviorFingerprintResult != null && speedOverlayFingerprintResult != null - && speedOverlayValueFingerprintResult != null + && speedOverlayFloatValueFingerprintResult != null if (resolvable) { - // Legacy method. // Used on YouTube 18.29.38 ~ YouTube 19.17.41 - // region patch for disable speed overlay + // region patch for Disable speed overlay (Enable slide to seek) mapOf( RestoreSlideToSeekBehaviorFingerprint to 45411329, SpeedOverlayFingerprint to 45411330 ).forEach { (fingerprint, literal) -> - fingerprint.result!!.let { - fingerprint.literalInstructionBooleanHook( - literal, - "$PLAYER_CLASS_DESCRIPTOR->disableSpeedOverlay(Z)Z" - ) - } + fingerprint.injectLiteralInstructionBooleanCall( + literal, + "$PLAYER_CLASS_DESCRIPTOR->disableSpeedOverlay(Z)Z" + ) } // endregion - // region patch for custom speed overlay value + // region patch for Custom speed overlay float value - speedOverlayValueFingerprintResult!!.let { + speedOverlayFloatValueFingerprintResult!!.let { it.mutableMethod.apply { val index = it.scanResult.patternScanResult!!.startIndex val register = getInstruction(index).registerA @@ -98,13 +94,18 @@ object SpeedOverlayPatch : BytecodePatch( // endregion } else { - // New method. // Used on YouTube 19.18.41~ - NextGenWatchLayoutFingerprint.resultOrThrow().mutableMethod.apply { - val booleanValueIndex = getTargetIndexWithMethodReferenceNameOrThrow("booleanValue") + // region patch for Disable speed overlay (Enable slide to seek) - val insertIndex = findIGetIndex(booleanValueIndex - 10, booleanValueIndex) + NextGenWatchLayoutFingerprint.resultOrThrow().mutableMethod.apply { + val booleanValueIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "booleanValue" + } + val insertIndex = indexOfFirstInstructionOrThrow(booleanValueIndex - 10) { + opcode == Opcode.IGET_OBJECT + && getReference()?.definingClass == definingClass + } val insertInstruction = getInstruction(insertIndex) val insertReference = getInstruction(insertIndex).reference @@ -113,77 +114,100 @@ object SpeedOverlayPatch : BytecodePatch( "iget-object v${insertInstruction.registerA}, v${insertInstruction.registerB}, $insertReference" ) - val jumpIndex = findIGetIndex(booleanValueIndex, booleanValueIndex + 10) + val jumpIndex = indexOfFirstInstructionOrThrow(booleanValueIndex) { + opcode == Opcode.IGET_OBJECT + && getReference()?.definingClass == definingClass + } hook(insertIndex + 1, insertInstruction.registerA, jumpIndex) } - SlideToSeekMotionEventFingerprint.resolve( - context, - HorizontalTouchOffsetConstructorFingerprint.resultOrThrow().classDef - ) - SlideToSeekMotionEventFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val scanResult = it.scanResult.patternScanResult!! + val (slideToSeekBooleanMethod, slideToSeekSyntheticMethod) = + SlideToSeekMotionEventFingerprint.alsoResolve( + context, HorizontalTouchOffsetConstructorFingerprint + ).let { + with(it.mutableMethod) { + val scanResult = it.scanResult.patternScanResult!! + val jumpIndex = scanResult.endIndex + 1 + val insertIndex = scanResult.endIndex - 1 + val insertRegister = + getInstruction(insertIndex).registerA + + hook(insertIndex, insertRegister, jumpIndex) + + val slideToSeekBooleanMethod = + getWalkerMethod(context, scanResult.startIndex + 1) + + val slideToSeekConstructorMethod = + context.findMethodOrThrow(slideToSeekBooleanMethod.definingClass) - val slideToSeekBooleanIndex = scanResult.startIndex + 1 - slideToSeekBooleanMethod = getWalkerMethod(context, slideToSeekBooleanIndex) + val slideToSeekSyntheticIndex = slideToSeekConstructorMethod + .indexOfFirstInstructionReversedOrThrow { + opcode == Opcode.NEW_INSTANCE + } - val jumpIndex = scanResult.endIndex + 1 - val insertIndex = scanResult.endIndex - 1 - val insertRegister = - getInstruction(insertIndex).registerA + val slideToSeekSyntheticClass = slideToSeekConstructorMethod + .getInstruction(slideToSeekSyntheticIndex) + .reference + .toString() - hook(insertIndex, insertRegister, jumpIndex) + val slideToSeekSyntheticMethod = + context.findMethodOrThrow(slideToSeekSyntheticClass) { + name == "run" + } + + Pair(slideToSeekBooleanMethod, slideToSeekSyntheticMethod) + } } - } slideToSeekBooleanMethod.apply { - var insertIndex = getTargetIndexOrThrow(Opcode.IGET_OBJECT) - var insertRegister = getInstruction(insertIndex).registerA - var jumpIndex = getTargetIndexReversedOrThrow(Opcode.INVOKE_VIRTUAL) + val insertIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.IGET_OBJECT + } + val insertRegister = getInstruction(insertIndex).registerA + val jumpIndex = indexOfFirstInstructionReversedOrThrow { + opcode == Opcode.INVOKE_VIRTUAL + } hook(insertIndex, insertRegister, jumpIndex) + } - val constructorMethod = - context.findClass(definingClass)?.mutableClass - ?.methods?.find { method -> method.name == "" } - ?: throw PatchException("Could not find constructor method") - - constructorMethod.apply { - val syntheticIndex = getTargetIndexReversedOrThrow(Opcode.NEW_INSTANCE) - val syntheticClass = - getInstruction(syntheticIndex).reference.toString() - - val syntheticMethod = - context.findClass(syntheticClass)?.mutableClass - ?.methods?.find { method -> method.name == "run" } - ?: throw PatchException("Could not find synthetic method") - - syntheticMethod.apply { - val speedOverlayValueIndex = - indexOfFirstInstructionOrThrow { (this as? NarrowLiteralInstruction)?.narrowLiteral == 2.0f.toRawBits() } - val speedOverlayValueRegister = - getInstruction(speedOverlayValueIndex).registerA - - addInstructions( - speedOverlayValueIndex + 1, """ - invoke-static {v$speedOverlayValueRegister}, $PLAYER_CLASS_DESCRIPTOR->speedOverlayValue(F)F - move-result v$speedOverlayValueRegister - """ - ) - - insertIndex = getTargetIndexWithMethodReferenceNameReversedOrThrow( - speedOverlayValueIndex, - "removeCallbacks" - ) + 1 - insertRegister = - getInstruction(insertIndex - 1).registerC - jumpIndex = - getTargetIndexOrThrow(speedOverlayValueIndex, Opcode.RETURN_VOID) + 1 - hook(insertIndex, insertRegister, jumpIndex) - } + slideToSeekSyntheticMethod.apply { + val speedOverlayFloatValueIndex = indexOfFirstInstructionOrThrow { + (this as? NarrowLiteralInstruction)?.narrowLiteral == 2.0f.toRawBits() } + val insertIndex = + indexOfFirstInstructionReversedOrThrow(speedOverlayFloatValueIndex) { + getReference()?.name == "removeCallbacks" + } + 1 + val insertRegister = + getInstruction(insertIndex - 1).registerC + val jumpIndex = + indexOfFirstInstructionOrThrow( + speedOverlayFloatValueIndex, + Opcode.RETURN_VOID + ) + 1 + + hook(insertIndex, insertRegister, jumpIndex) + } + + // endregion + + // region patch for Custom speed overlay float value + + slideToSeekSyntheticMethod.apply { + val speedOverlayFloatValueIndex = indexOfFirstInstructionOrThrow { + (this as? NarrowLiteralInstruction)?.narrowLiteral == 2.0f.toRawBits() + } + val speedOverlayFloatValueRegister = + getInstruction(speedOverlayFloatValueIndex).registerA + + addInstructions( + speedOverlayFloatValueIndex + 1, """ + invoke-static {v$speedOverlayFloatValueRegister}, $PLAYER_CLASS_DESCRIPTOR->speedOverlayValue(F)F + move-result v$speedOverlayFloatValueRegister + """ + ) } SpeedOverlayTextValueFingerprint.resultOrThrow().let { @@ -200,11 +224,12 @@ object SpeedOverlayPatch : BytecodePatch( ) } } + + // endregion + } } - private lateinit var slideToSeekBooleanMethod: MutableMethod - // restore slide to seek private fun MutableMethod.hook( insertIndex: Int, @@ -220,14 +245,4 @@ object SpeedOverlayPatch : BytecodePatch( """, ExternalLabel("disable", getInstruction(jumpIndex)) ) } - - private fun MutableMethod.findIGetIndex( - startIndex: Int, - endIndex: Int - ): Int = implementation!!.instructions.let { instruction -> - startIndex + instruction.subList(startIndex, endIndex).indexOfFirst { - it.opcode == Opcode.IGET_OBJECT - && it.getReference()?.definingClass == definingClass - } - } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/NextGenWatchLayoutFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/NextGenWatchLayoutFingerprint.kt index f5ef60b0c9..4e54b4a7ed 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/NextGenWatchLayoutFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/NextGenWatchLayoutFingerprint.kt @@ -2,8 +2,10 @@ package app.revanced.patches.youtube.player.speedoverlay.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.util.containsMethodReferenceNameInstructionIndex +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.iface.reference.MethodReference internal object NextGenWatchLayoutFingerprint : MethodFingerprint( returnType = "Z", @@ -13,6 +15,8 @@ internal object NextGenWatchLayoutFingerprint : MethodFingerprint( if (methodDef.definingClass != "Lcom/google/android/apps/youtube/app/watch/nextgenwatch/ui/NextGenWatchLayout;") return@handler false - methodDef.containsMethodReferenceNameInstructionIndex("booleanValue") + methodDef.indexOfFirstInstruction { + getReference()?.name == "booleanValue" + } >= 0 } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/RestoreSlideToSeekBehaviorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/RestoreSlideToSeekBehaviorFingerprint.kt index e27dac2f08..a6904c6705 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/RestoreSlideToSeekBehaviorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/RestoreSlideToSeekBehaviorFingerprint.kt @@ -5,6 +5,7 @@ import com.android.tools.smali.dexlib2.Opcode /** * This value restores the 'Slide to seek' behavior. + * Deprecated in YouTube v19.18.41+. */ internal object RestoreSlideToSeekBehaviorFingerprint : LiteralValueFingerprint( returnType = "Z", diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayFingerprint.kt index dd1fc9e7ec..ad71e2e0d6 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayFingerprint.kt @@ -5,6 +5,7 @@ import com.android.tools.smali.dexlib2.Opcode /** * This value disables 'Playing at 2x speed' while holding down. + * Deprecated in YouTube v19.18.41+. */ internal object SpeedOverlayFingerprint : LiteralValueFingerprint( returnType = "Z", diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayValueFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayFloatValueFingerprint.kt similarity index 81% rename from src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayValueFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayFloatValueFingerprint.kt index c60dc03f8f..d5557db6b3 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayValueFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayFloatValueFingerprint.kt @@ -7,8 +7,9 @@ import com.android.tools.smali.dexlib2.Opcode /** * This value is the key for the playback speed overlay value. + * Deprecated in YouTube v19.18.41+. */ -internal object SpeedOverlayValueFingerprint : LiteralValueFingerprint( +internal object SpeedOverlayFloatValueFingerprint : LiteralValueFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, opcodes = listOf(Opcode.DOUBLE_TO_FLOAT), diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayTextValueFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayTextValueFingerprint.kt index 434305547e..53de379db5 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayTextValueFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/player/speedoverlay/fingerprints/SpeedOverlayTextValueFingerprint.kt @@ -1,14 +1,21 @@ package app.revanced.patches.youtube.player.speedoverlay.fingerprints import app.revanced.patcher.extensions.or -import app.revanced.util.fingerprint.ReferenceFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.reference.MethodReference -internal object SpeedOverlayTextValueFingerprint : ReferenceFingerprint( +internal object SpeedOverlayTextValueFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, parameters = emptyList(), opcodes = listOf(Opcode.CONST_WIDE_HIGH16), - reference = { "Ljava/math/BigDecimal;->signum()I" } + customFingerprint = { methodDef, _ -> + methodDef.indexOfFirstInstruction { + getReference()?.toString() == "Ljava/math/BigDecimal;->signum()I" + } >= 0 + } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsAnimationPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsAnimationPatch.kt index 153517b645..118a6eac0d 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsAnimationPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsAnimationPatch.kt @@ -15,8 +15,8 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ReelF import app.revanced.patches.youtube.utils.settings.SettingsPatch.contexts import app.revanced.util.ResourceGroup import app.revanced.util.copyResources -import app.revanced.util.getWideLiteralInstructionIndex import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @@ -38,7 +38,7 @@ object ShortsAnimationPatch : BytecodePatch( ReelFeedbackPause to "setShortsPauseFeedback", ReelFeedbackPlay to "setShortsPlayFeedback", ).forEach { (literal, methodName) -> - val literalIndex = getWideLiteralInstructionIndex(literal) + val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow(literal) val viewIndex = indexOfFirstInstructionOrThrow(literalIndex) { opcode == Opcode.CHECK_CAST && (this as? ReferenceInstruction)?.reference?.toString() == LOTTIE_ANIMATION_VIEW_CLASS_DESCRIPTOR diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt index 7bd645ca94..e30dac5c82 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt @@ -35,13 +35,12 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.Right import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.video.information.VideoInformationPatch import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.getTargetIndexWithReferenceOrThrow -import app.revanced.util.getWalkerMethod -import app.revanced.util.getWideLiteralInstructionIndex -import app.revanced.util.literalInstructionHook +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.patch.BaseBytecodePatch +import app.revanced.util.replaceLiteralInstructionCall import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction @@ -49,6 +48,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Suppress("unused") object ShortsComponentPatch : BaseBytecodePatch( @@ -93,7 +93,7 @@ object ShortsComponentPatch : BaseBytecodePatch( "SETTINGS: SHORTS_COMPONENTS" ) - if (SettingsPatch.upward1925) { + if (SettingsPatch.upward1925 && !SettingsPatch.upward1928) { settingArray += "SETTINGS: SHORTS_TIME_STAMP" } @@ -107,10 +107,11 @@ object ShortsComponentPatch : BaseBytecodePatch( ShortsButtonFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(ReelRightDislikeIcon) + val constIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(ReelRightDislikeIcon) val constRegister = getInstruction(constIndex).registerA - val jumpIndex = getTargetIndexOrThrow(constIndex, Opcode.CONST_CLASS) + 2 + val jumpIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CONST_CLASS) + 2 addInstructionsWithLabels( constIndex + 1, """ @@ -129,9 +130,9 @@ object ShortsComponentPatch : BaseBytecodePatch( ShortsButtonFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = getWideLiteralInstructionIndex(ReelRightLikeIcon) + val insertIndex = indexOfFirstWideLiteralInstructionValueOrThrow(ReelRightLikeIcon) val insertRegister = getInstruction(insertIndex).registerA - val jumpIndex = getTargetIndexOrThrow(insertIndex, Opcode.CONST_CLASS) + 2 + val jumpIndex = indexOfFirstInstructionOrThrow(insertIndex, Opcode.CONST_CLASS) + 2 addInstructionsWithLabels( insertIndex + 1, """ @@ -153,11 +154,12 @@ object ShortsComponentPatch : BaseBytecodePatch( if (shortsPivotLegacyFingerprintResult != null) { // Legacy method. shortsPivotLegacyFingerprintResult.mutableMethod.apply { - val targetIndex = getWideLiteralInstructionIndex(ReelForcedMuteButton) + val targetIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(ReelForcedMuteButton) val targetRegister = getInstruction(targetIndex).registerA - val insertIndex = getTargetIndexReversedOrThrow(targetIndex, Opcode.IF_EQZ) - val jumpIndex = getTargetIndexOrThrow(targetIndex, Opcode.GOTO) + val insertIndex = indexOfFirstInstructionReversedOrThrow(targetIndex, Opcode.IF_EQZ) + val jumpIndex = indexOfFirstInstructionOrThrow(targetIndex, Opcode.GOTO) addInstructionsWithLabels( insertIndex, """ @@ -174,7 +176,7 @@ object ShortsComponentPatch : BaseBytecodePatch( move-result v$REGISTER_TEMPLATE_REPLACEMENT """ - context.literalInstructionHook( + context.replaceLiteralInstructionCall( ReelPlayerRightPivotV2Size, smaliInstruction ) @@ -243,7 +245,8 @@ object ShortsComponentPatch : BaseBytecodePatch( lateinit var subscriptionFieldReference: FieldReference parentResult.mutableMethod.apply { - val targetIndex = getWideLiteralInstructionIndex(ReelPlayerFooter) - 1 + val targetIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(ReelPlayerFooter) - 1 subscriptionFieldReference = (getInstruction(targetIndex)).reference as FieldReference } @@ -279,18 +282,15 @@ object ShortsComponentPatch : BaseBytecodePatch( // region patch for hide paused header ShortsPausedHeaderFingerprint.resultOrThrow().let { - val targetMethod = - it.getWalkerMethod(context, it.scanResult.patternScanResult!!.endIndex) + it.mutableMethod.apply { + val insertIndex = it.scanResult.patternScanResult!!.startIndex + val insertRegister = getInstruction(insertIndex).registerA - targetMethod.apply { - addInstructionsWithLabels( - 0, - """ - invoke-static {}, $SHORTS_CLASS_DESCRIPTOR->hideShortsPausedHeader()Z - move-result v0 - if-nez v0, :hide - """, - ExternalLabel("hide", getInstruction(implementation!!.instructions.lastIndex)) + addInstructions( + insertIndex, """ + invoke-static {v$insertRegister}, $SHORTS_CLASS_DESCRIPTOR->hideShortsPausedHeader(Z)Z + move-result v$insertRegister + """ ) } } @@ -301,9 +301,9 @@ object ShortsComponentPatch : BaseBytecodePatch( TextComponentSpecFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = - getTargetIndexWithReferenceOrThrow("Landroid/text/SpannableString;->valueOf(Ljava/lang/CharSequence;)Landroid/text/SpannableString;") - + val insertIndex = indexOfFirstInstructionOrThrow { + getReference()?.toString() == "Landroid/text/SpannableString;->valueOf(Ljava/lang/CharSequence;)Landroid/text/SpannableString;" + } val charSequenceRegister = getInstruction(insertIndex).registerC val conversionContextRegister = @@ -346,11 +346,11 @@ object ShortsComponentPatch : BaseBytecodePatch( ) { resultOrThrow().let { it.mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(id) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(id) val insertIndex = if (reversed) - getTargetIndexReversedOrThrow(constIndex, Opcode.CHECK_CAST) + indexOfFirstInstructionReversedOrThrow(constIndex, Opcode.CHECK_CAST) else - getTargetIndexOrThrow(constIndex, Opcode.CHECK_CAST) + indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST) val insertRegister = getInstruction(insertIndex).registerA addInstruction( @@ -367,8 +367,8 @@ object ShortsComponentPatch : BaseBytecodePatch( ) { resultOrThrow().let { it.mutableMethod.apply { - val constIndex = getWideLiteralInstructionIndex(id) - val insertIndex = getTargetIndexOrThrow(constIndex, Opcode.CHECK_CAST) + val constIndex = indexOfFirstWideLiteralInstructionValueOrThrow(id) + val insertIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST) hideButtons(insertIndex, descriptor) } diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsNavigationBarPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsNavigationBarPatch.kt index 1bc60c84a7..337a239fb7 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsNavigationBarPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsNavigationBarPatch.kt @@ -1,67 +1,67 @@ package app.revanced.patches.youtube.shorts.components import app.revanced.patcher.data.BytecodeContext -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.BytecodePatch -import app.revanced.patches.youtube.shorts.components.fingerprints.BottomNavigationBarFingerprint -import app.revanced.patches.youtube.shorts.components.fingerprints.RenderBottomNavigationBarFingerprint -import app.revanced.patches.youtube.shorts.components.fingerprints.SetPivotBarFingerprint -import app.revanced.patches.youtube.utils.fingerprints.InitializeButtonsFingerprint +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patches.youtube.shorts.components.fingerprints.BottomBarContainerHeightFingerprint import app.revanced.patches.youtube.utils.integrations.Constants.SHORTS_CLASS_DESCRIPTOR -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.getWalkerMethod +import app.revanced.patches.youtube.utils.navigation.NavigationBarHookPatch +import app.revanced.patches.youtube.utils.playertype.fingerprint.ReelWatchPagerFingerprint +import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.BottomBarContainer +import app.revanced.util.fingerprint.MultiMethodFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValue +import app.revanced.util.patch.MultiMethodBytecodePatch import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference -object ShortsNavigationBarPatch : BytecodePatch( - setOf( - BottomNavigationBarFingerprint, - InitializeButtonsFingerprint, - RenderBottomNavigationBarFingerprint - ) +/** + * Up to YouTube 19.28.42, there are two Methods with almost the same pattern. + * + * In certain YouTube versions, the hook should be done not on the first matching Method, but also on the last matching Method. + * + * 'Multiple fingerprint search' feature is not yet implemented in ReVanced Patcher, + * So I just implement it via [MultiMethodFingerprint]. + * + * Related Issues: + * https://github.com/ReVanced/revanced-patcher/issues/74 + * https://github.com/ReVanced/revanced-patcher/issues/308 + */ +@Patch(dependencies = [NavigationBarHookPatch::class]) +object ShortsNavigationBarPatch : MultiMethodBytecodePatch( + fingerprints = setOf(ReelWatchPagerFingerprint), + multiFingerprints = setOf(BottomBarContainerHeightFingerprint) ) { override fun execute(context: BytecodeContext) { + super.execute(context) - InitializeButtonsFingerprint.resultOrThrow().let { parentResult -> - SetPivotBarFingerprint.also { it.resolve(context, parentResult.classDef) } - .resultOrThrow().let { - it.mutableMethod.apply { - val startIndex = it.scanResult.patternScanResult!!.startIndex - val register = getInstruction(startIndex).registerA + // region patch for set navigation bar height. - addInstruction( - startIndex + 1, - "invoke-static {v$register}, $SHORTS_CLASS_DESCRIPTOR->setNavigationBar(Ljava/lang/Object;)V" - ) - } - } - } + BottomBarContainerHeightFingerprint.resultOrThrow().forEach { + it.mutableMethod.apply { + val constIndex = indexOfFirstWideLiteralInstructionValue(BottomBarContainer) - RenderBottomNavigationBarFingerprint.resultOrThrow().let { - val walkerMethod = - it.getWalkerMethod(context, it.scanResult.patternScanResult!!.endIndex) + val targetIndex = indexOfFirstInstructionOrThrow(constIndex) { + getReference()?.name == "getHeight" + } + 1 - walkerMethod.addInstruction( - 0, - "invoke-static {}, $SHORTS_CLASS_DESCRIPTOR->hideShortsNavigationBar()V" - ) - } - - BottomNavigationBarFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val targetIndex = getTargetIndexWithMethodReferenceNameOrThrow("findViewById") + 1 - val insertRegister = getInstruction(targetIndex).registerA + val heightRegister = getInstruction(targetIndex).registerA addInstructions( targetIndex + 1, """ - invoke-static {v$insertRegister}, $SHORTS_CLASS_DESCRIPTOR->hideShortsNavigationBar(Landroid/view/View;)Landroid/view/View; - move-result-object v$insertRegister + invoke-static {v$heightRegister}, $SHORTS_CLASS_DESCRIPTOR->setNavigationBarHeight(I)I + move-result v$heightRegister """ ) } } + NavigationBarHookPatch.addBottomBarContainerHook("$SHORTS_CLASS_DESCRIPTOR->setNavigationBar(Landroid/view/View;)V") + + // endregion. + } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsRepeatPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsRepeatPatch.kt index b7484a31cd..8c16daabbb 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsRepeatPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsRepeatPatch.kt @@ -9,14 +9,18 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patches.youtube.shorts.components.fingerprints.ReelEnumConstructorFingerprint import app.revanced.patches.youtube.shorts.components.fingerprints.ReelEnumStaticFingerprint import app.revanced.patches.youtube.utils.integrations.Constants.SHORTS_CLASS_DESCRIPTOR -import app.revanced.util.containsReferenceInstructionIndex import app.revanced.util.findMutableMethodOf -import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndexOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstStringInstructionOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.util.MethodUtil object ShortsRepeatPatch : BytecodePatch( setOf(ReelEnumConstructorFingerprint) @@ -36,16 +40,15 @@ object ShortsRepeatPatch : BytecodePatch( } val endScreenStringIndex = - getStringInstructionIndex("REEL_LOOP_BEHAVIOR_END_SCREEN") + indexOfFirstStringInstructionOrThrow("REEL_LOOP_BEHAVIOR_END_SCREEN") val endScreenReferenceIndex = - getTargetIndexOrThrow(endScreenStringIndex, Opcode.SPUT_OBJECT) + indexOfFirstInstructionOrThrow(endScreenStringIndex, Opcode.SPUT_OBJECT) val endScreenReference = getInstruction(endScreenReferenceIndex).reference.toString() - val enumMethodName = ReelEnumStaticFingerprint.resultOrThrow().mutableMethod.name - val enumMethodCall = "$definingClass->$enumMethodName(I)$definingClass" + val enumMethod = ReelEnumStaticFingerprint.resultOrThrow().mutableMethod - context.injectHook(endScreenReference, enumMethodCall) + context.injectHook(endScreenReference, enumMethod) } } } @@ -54,8 +57,8 @@ object ShortsRepeatPatch : BytecodePatch( enumName: String, fieldName: String ) { - val stringIndex = getStringInstructionIndex(enumName) - val insertIndex = getTargetIndexOrThrow(stringIndex, Opcode.SPUT_OBJECT) + val stringIndex = indexOfFirstStringInstructionOrThrow(enumName) + val insertIndex = indexOfFirstInstructionOrThrow(stringIndex, Opcode.SPUT_OBJECT) val insertRegister = getInstruction(insertIndex).registerA addInstruction( @@ -66,35 +69,41 @@ object ShortsRepeatPatch : BytecodePatch( private fun BytecodeContext.injectHook( endScreenReference: String, - enumMethodCall: String + enumMethod: MutableMethod ) { classes.forEach { classDef -> classDef.methods.filter { method -> method.parameters.size == 1 && method.parameters[0].startsWith("L") && method.returnType == "V" - && method.containsReferenceInstructionIndex(endScreenReference) + && method.indexOfFirstInstruction { + getReference()?.toString() == endScreenReference + } >= 0 }.forEach { targetMethod -> proxy(classDef) .mutableClass .findMutableMethodOf(targetMethod) .apply { - for ((index, instruction) in implementation!!.instructions.withIndex()) { - if (instruction.opcode != Opcode.INVOKE_STATIC) - continue - if ((instruction as ReferenceInstruction).reference.toString() != enumMethodCall) - continue + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + val reference = (instruction as? ReferenceInstruction)?.reference + reference is MethodReference && + MethodUtil.methodSignaturesMatch(enumMethod, reference) + } + .map { (index, _) -> index } + .reversed() + .forEach { index -> + val register = + getInstruction(index + 1).registerA - val register = - getInstruction(index + 1).registerA - - addInstructions( - index + 2, """ - invoke-static {v$register}, $SHORTS_CLASS_DESCRIPTOR->changeShortsRepeatState(Ljava/lang/Enum;)Ljava/lang/Enum; - move-result-object v$register - """ - ) - } + addInstructions( + index + 2, """ + invoke-static {v$register}, $SHORTS_CLASS_DESCRIPTOR->changeShortsRepeatState(Ljava/lang/Enum;)Ljava/lang/Enum; + move-result-object v$register + """ + ) + } } } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsTimeStampPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsTimeStampPatch.kt index 4d9c30852b..dfdc55a3f3 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsTimeStampPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsTimeStampPatch.kt @@ -13,9 +13,9 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.MetaP import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ReelVodTimeStampsContainer import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT -import app.revanced.util.getWideLiteralInstructionIndex -import app.revanced.util.literalInstructionBooleanHook -import app.revanced.util.literalInstructionViewHook +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall +import app.revanced.util.injectLiteralInstructionViewCall import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @@ -29,7 +29,7 @@ object ShortsTimeStampPatch : BytecodePatch( ) { override fun execute(context: BytecodeContext) { - if (!SettingsPatch.upward1925) return + if (!SettingsPatch.upward1925 || SettingsPatch.upward1928) return // region patch for enable time stamp @@ -38,14 +38,14 @@ object ShortsTimeStampPatch : BytecodePatch( ShortsTimeStampPrimaryFingerprint to 45638282, ShortsTimeStampSecondaryFingerprint to 45638187 ).forEach { (fingerprint, literal) -> - fingerprint.literalInstructionBooleanHook( + fingerprint.injectLiteralInstructionBooleanCall( literal, "$SHORTS_CLASS_DESCRIPTOR->enableShortsTimeStamp(Z)Z" ) } ShortsTimeStampPrimaryFingerprint.resultOrThrow().mutableMethod.apply { - val literalIndex = getWideLiteralInstructionIndex(10002) + val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow(10002) val literalRegister = getInstruction(literalIndex).registerA addInstructions( @@ -81,7 +81,7 @@ object ShortsTimeStampPatch : BytecodePatch( invoke-static {v$REGISTER_TEMPLATE_REPLACEMENT}, $SHORTS_CLASS_DESCRIPTOR->$methodName(Landroid/view/View;)V """ - fingerprint.literalInstructionViewHook(literalValue, smaliInstruction) + fingerprint.injectLiteralInstructionViewCall(literalValue, smaliInstruction) } // endregion diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/BottomBarContainerHeightFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/BottomBarContainerHeightFingerprint.kt new file mode 100644 index 0000000000..776252b56b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/BottomBarContainerHeightFingerprint.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.youtube.shorts.components.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.BottomBarContainer +import app.revanced.util.containsWideLiteralInstructionValue +import app.revanced.util.fingerprint.MultiMethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object BottomBarContainerHeightFingerprint : MultiMethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("Landroid/view/View;", "Landroid/os/Bundle;"), + strings = listOf("r_pfvc"), + customFingerprint = { methodDef, _ -> + methodDef.containsWideLiteralInstructionValue(BottomBarContainer) + }, +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/BottomNavigationBarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/BottomNavigationBarFingerprint.kt deleted file mode 100644 index c126985dc5..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/BottomNavigationBarFingerprint.kt +++ /dev/null @@ -1,12 +0,0 @@ -package app.revanced.patches.youtube.shorts.components.fingerprints - -import app.revanced.patcher.extensions.or -import app.revanced.patcher.fingerprint.MethodFingerprint -import com.android.tools.smali.dexlib2.AccessFlags - -internal object BottomNavigationBarFingerprint : MethodFingerprint( - returnType = "V", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("Landroid/view/View;", "Landroid/os/Bundle;"), - strings = listOf("r_pfvc", "ReelWatchPaneFragmentViewModelKey") -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ReelFeedbackFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ReelFeedbackFingerprint.kt index 901af28bdd..5081b1ff5d 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ReelFeedbackFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ReelFeedbackFingerprint.kt @@ -4,13 +4,13 @@ import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ReelFeedbackLike import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ReelFeedbackPause import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ReelFeedbackPlay -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue internal object ReelFeedbackFingerprint : MethodFingerprint( returnType = "V", customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(ReelFeedbackLike) - && methodDef.containsWideLiteralInstructionIndex(ReelFeedbackPause) - && methodDef.containsWideLiteralInstructionIndex(ReelFeedbackPlay) + methodDef.containsWideLiteralInstructionValue(ReelFeedbackLike) + && methodDef.containsWideLiteralInstructionValue(ReelFeedbackPause) + && methodDef.containsWideLiteralInstructionValue(ReelFeedbackPlay) }, ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/RenderBottomNavigationBarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/RenderBottomNavigationBarFingerprint.kt deleted file mode 100644 index 9dc16d62a9..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/RenderBottomNavigationBarFingerprint.kt +++ /dev/null @@ -1,13 +0,0 @@ -package app.revanced.patches.youtube.shorts.components.fingerprints - -import app.revanced.patcher.fingerprint.MethodFingerprint -import com.android.tools.smali.dexlib2.Opcode - -internal object RenderBottomNavigationBarFingerprint : MethodFingerprint( - returnType = "Landroid/view/View;", - opcodes = listOf( - Opcode.CONST_STRING, - Opcode.INVOKE_VIRTUAL - ), - strings = listOf("r_pfcv") -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/SetPivotBarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/SetPivotBarFingerprint.kt deleted file mode 100644 index ac218b1551..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/SetPivotBarFingerprint.kt +++ /dev/null @@ -1,16 +0,0 @@ -package app.revanced.patches.youtube.shorts.components.fingerprints - -import app.revanced.patcher.extensions.or -import app.revanced.patcher.fingerprint.MethodFingerprint -import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode - -internal object SetPivotBarFingerprint : MethodFingerprint( - returnType = "V", - accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, - parameters = listOf("Z"), - opcodes = listOf( - Opcode.CHECK_CAST, - Opcode.IF_EQZ - ) -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsButtonFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsButtonFingerprint.kt index 6f7d94c0a3..12e5e844c3 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsButtonFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsButtonFingerprint.kt @@ -6,15 +6,15 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ReelD import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ReelRightDislikeIcon import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ReelRightLikeIcon import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.RightComment -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue internal object ShortsButtonFingerprint : MethodFingerprint( returnType = "V", customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(ReelDynRemix) - && methodDef.containsWideLiteralInstructionIndex(ReelDynShare) - && methodDef.containsWideLiteralInstructionIndex(ReelRightDislikeIcon) - && methodDef.containsWideLiteralInstructionIndex(ReelRightLikeIcon) - && methodDef.containsWideLiteralInstructionIndex(RightComment) + methodDef.containsWideLiteralInstructionValue(ReelDynRemix) + && methodDef.containsWideLiteralInstructionValue(ReelDynShare) + && methodDef.containsWideLiteralInstructionValue(ReelRightDislikeIcon) + && methodDef.containsWideLiteralInstructionValue(ReelRightLikeIcon) + && methodDef.containsWideLiteralInstructionValue(RightComment) }, ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsPausedHeaderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsPausedHeaderFingerprint.kt index b94276a10f..456975694a 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsPausedHeaderFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsPausedHeaderFingerprint.kt @@ -10,8 +10,7 @@ internal object ShortsPausedHeaderFingerprint : MethodFingerprint( Opcode.IGET_OBJECT, Opcode.CONST, Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.INVOKE_VIRTUAL + Opcode.MOVE_RESULT_OBJECT ), strings = listOf("r_pfcv") ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsTimeStampPrimaryFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsTimeStampPrimaryFingerprint.kt index 9e34f01192..d2a2fdfa0b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsTimeStampPrimaryFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsTimeStampPrimaryFingerprint.kt @@ -2,7 +2,7 @@ package app.revanced.patches.youtube.shorts.components.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object ShortsTimeStampPrimaryFingerprint : MethodFingerprint( @@ -10,8 +10,8 @@ internal object ShortsTimeStampPrimaryFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("I"), customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(45627350) - && methodDef.containsWideLiteralInstructionIndex(45638282) - && methodDef.containsWideLiteralInstructionIndex(10002) + methodDef.containsWideLiteralInstructionValue(45627350) + && methodDef.containsWideLiteralInstructionValue(45638282) + && methodDef.containsWideLiteralInstructionValue(10002) }, ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsToolBarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsToolBarFingerprint.kt index f0de713d81..2cae4bf8de 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsToolBarFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/shorts/components/fingerprints/ShortsToolBarFingerprint.kt @@ -8,7 +8,9 @@ import com.android.tools.smali.dexlib2.Opcode internal object ShortsToolBarFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, - parameters = listOf("Z", "L", "L"), opcodes = listOf(Opcode.IPUT_BOOLEAN), - strings = listOf("Null topBarButtons") + strings = listOf("Null topBarButtons"), + customFingerprint = { methodDef, _ -> + methodDef.parameterTypes.firstOrNull() == "Z" + } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt index 686c02dfc1..1fcad075e9 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt @@ -23,8 +23,8 @@ import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch.contexts import app.revanced.util.ResourceGroup import app.revanced.util.copyResources -import app.revanced.util.getWideLiteralInstructionIndex -import app.revanced.util.literalInstructionBooleanHook +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import app.revanced.util.transformMethods @@ -90,7 +90,8 @@ object SwipeControlsPatch : BaseBytecodePatch( FullScreenEngagementOverlayFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val viewIndex = getWideLiteralInstructionIndex(FullScreenEngagementOverlay) + 3 + val viewIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(FullScreenEngagementOverlay) + 3 val viewRegister = getInstruction(viewIndex).registerA addInstruction( @@ -131,7 +132,7 @@ object SwipeControlsPatch : BaseBytecodePatch( // Since it does not support all versions, // add settings only if the patch is successful. SwipeToSwitchVideoFingerprint.result?.let { - SwipeToSwitchVideoFingerprint.literalInstructionBooleanHook( + SwipeToSwitchVideoFingerprint.injectLiteralInstructionBooleanCall( 45631116, "$INTEGRATIONS_SWIPE_CONTROLS_PATCH_CLASS_DESCRIPTOR->enableSwipeToSwitchVideo()Z" ) @@ -147,7 +148,7 @@ object SwipeControlsPatch : BaseBytecodePatch( // Since it does not support all versions, // add settings only if the patch is successful. WatchPanelGesturesFingerprint.result?.let { - WatchPanelGesturesFingerprint.literalInstructionBooleanHook( + WatchPanelGesturesFingerprint.injectLiteralInstructionBooleanCall( 45372793, "$INTEGRATIONS_SWIPE_CONTROLS_PATCH_CLASS_DESCRIPTOR->enableWatchPanelGestures()Z" ) diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/PlayerResponseModelUtils.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/PlayerResponseModelUtils.kt new file mode 100644 index 0000000000..0d1d44b016 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/PlayerResponseModelUtils.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.youtube.utils + +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.reference.MethodReference + +internal object PlayerResponseModelUtils { + const val PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR = + "Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;" + + fun indexOfPlayerResponseModelInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + opcode == Opcode.INVOKE_INTERFACE && + getReference()?.definingClass == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/bottomsheet/BottomSheetHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/bottomsheet/BottomSheetHookPatch.kt new file mode 100644 index 0000000000..52205105d5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/bottomsheet/BottomSheetHookPatch.kt @@ -0,0 +1,37 @@ +package app.revanced.patches.youtube.utils.bottomsheet + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patches.youtube.utils.bottomsheet.fingerprint.BottomSheetBehaviorFingerprint +import app.revanced.patches.youtube.utils.integrations.Constants.UTILS_PATH +import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch +import app.revanced.util.findMethodOrThrow +import app.revanced.util.resultOrThrow + +@Patch(dependencies = [SharedResourceIdPatch::class]) +object BottomSheetHookPatch : BytecodePatch( + setOf(BottomSheetBehaviorFingerprint) +) { + private const val INTEGRATIONS_BOTTOM_SHEET_HOOK_CLASS_DESCRIPTOR = + "$UTILS_PATH/BottomSheetHookPatch;" + + override fun execute(context: BytecodeContext) { + + val bottomSheetClass = + BottomSheetBehaviorFingerprint.resultOrThrow().mutableMethod.definingClass + + arrayOf( + "onAttachedToWindow", + "onDetachedFromWindow" + ).forEach { methodName -> + context.findMethodOrThrow(bottomSheetClass) { + name == methodName + }.addInstruction( + 1, + "invoke-static {}, $INTEGRATIONS_BOTTOM_SHEET_HOOK_CLASS_DESCRIPTOR->$methodName()V" + ) + } + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/bottomsheet/fingerprint/BottomSheetBehaviorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/bottomsheet/fingerprint/BottomSheetBehaviorFingerprint.kt new file mode 100644 index 0000000000..73d6aa2cb0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/bottomsheet/fingerprint/BottomSheetBehaviorFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.utils.bottomsheet.fingerprint + +import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.DesignBottomSheet +import app.revanced.util.fingerprint.LiteralValueFingerprint + +internal object BottomSheetBehaviorFingerprint : LiteralValueFingerprint( + returnType = "V", + literalSupplier = { DesignBottomSheet } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/castbutton/CastButtonPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/castbutton/CastButtonPatch.kt index 6db2ab6bba..01193f893c 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/castbutton/CastButtonPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/castbutton/CastButtonPatch.kt @@ -6,7 +6,6 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patches.youtube.utils.castbutton.fingerprints.MenuItemInitializeFingerprint @@ -17,11 +16,15 @@ import app.revanced.patches.youtube.utils.integrations.Constants.PATCH_STATUS_CL import app.revanced.patches.youtube.utils.integrations.Constants.PLAYER_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.integrations.Constants.UTILS_PATH import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.alsoResolve +import app.revanced.util.findMethodOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow import app.revanced.util.updatePatchStatus import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Patch(dependencies = [SharedResourceIdPatch::class]) object CastButtonPatch : BytecodePatch( @@ -39,32 +42,30 @@ object CastButtonPatch : BytecodePatch( override fun execute(context: BytecodeContext) { - val toolbarMenuItemInitializeResult = MenuItemInitializeFingerprint.resultOrThrow() - MenuItemVisibilityFingerprint.resolve(context, toolbarMenuItemInitializeResult.classDef) - - toolbarMenuItemInitializeMethod = toolbarMenuItemInitializeResult.mutableMethod - toolbarMenuItemVisibilityMethod = - MenuItemVisibilityFingerprint.resultOrThrow().mutableMethod + toolbarMenuItemInitializeMethod = MenuItemInitializeFingerprint.resultOrThrow() + .mutableMethod + toolbarMenuItemVisibilityMethod = MenuItemVisibilityFingerprint.alsoResolve( + context, MenuItemInitializeFingerprint + ).mutableMethod playerButtonMethod = PlayerButtonFingerprint.resultOrThrow().mutableMethod - val buttonClass = context.findClass("MediaRouteButton") - ?: throw PatchException("MediaRouteButton class not found.") - - buttonClass.mutableClass.methods.find { it.name == "setVisibility" }?.apply { - addInstructions( - 0, """ - invoke-static {p1}, $INTEGRATIONS_CLASS_DESCRIPTOR->hideCastButton(I)I - move-result p1 - """ - ) - } ?: throw PatchException("setVisibility method not found.") + context.findMethodOrThrow("Landroidx/mediarouter/app/MediaRouteButton;") { + name == "setVisibility" + }.addInstructions( + 0, """ + invoke-static {p1}, $INTEGRATIONS_CLASS_DESCRIPTOR->hideCastButton(I)I + move-result p1 + """ + ) } internal fun hookPlayerButton(context: BytecodeContext) { playerButtonMethod.apply { - val index = getTargetIndexWithMethodReferenceNameOrThrow("setVisibility") + val index = indexOfFirstInstructionOrThrow { + getReference()?.name == "setVisibility" + } val instruction = getInstruction(index) val viewRegister = instruction.registerC val visibilityRegister = instruction.registerD @@ -84,8 +85,9 @@ object CastButtonPatch : BytecodePatch( internal fun hookToolBarButton(context: BytecodeContext) { toolbarMenuItemInitializeMethod.apply { - val index = getTargetIndexWithMethodReferenceNameOrThrow("setShowAsAction") + 1 - + val index = indexOfFirstInstructionOrThrow { + getReference()?.name == "setShowAsAction" + } + 1 addInstruction( index, "invoke-static {p1}, $GENERAL_CLASS_DESCRIPTOR->hideCastButton(Landroid/view/MenuItem;)V" diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/castbutton/fingerprints/MenuItemVisibilityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/castbutton/fingerprints/MenuItemVisibilityFingerprint.kt index 18e37f62cd..a52f69071e 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/castbutton/fingerprints/MenuItemVisibilityFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/castbutton/fingerprints/MenuItemVisibilityFingerprint.kt @@ -1,12 +1,19 @@ package app.revanced.patches.youtube.utils.castbutton.fingerprints import app.revanced.patcher.extensions.or -import app.revanced.util.fingerprint.MethodReferenceNameFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.iface.reference.MethodReference -internal object MenuItemVisibilityFingerprint : MethodReferenceNameFingerprint( +internal object MenuItemVisibilityFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("Z"), - reference = { "setVisible" } + customFingerprint = { methodDef, _ -> + methodDef.indexOfFirstInstruction { + getReference()?.name == "setVisible" + } >= 0 + } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/player/components/fingerprints/EngagementPanelControllerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/EngagementPanelBuilderFingerprint.kt similarity index 79% rename from src/main/kotlin/app/revanced/patches/youtube/player/components/fingerprints/EngagementPanelControllerFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/EngagementPanelBuilderFingerprint.kt index ffc7e9c979..45002b73e9 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/player/components/fingerprints/EngagementPanelControllerFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/EngagementPanelBuilderFingerprint.kt @@ -1,10 +1,10 @@ -package app.revanced.patches.youtube.player.components.fingerprints +package app.revanced.patches.youtube.utils.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags -internal object EngagementPanelControllerFingerprint : MethodFingerprint( +internal object EngagementPanelBuilderFingerprint : MethodFingerprint( returnType = "L", accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, parameters = listOf("L", "L", "Z", "Z"), diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/PlayerSeekbarColorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/PlayerSeekbarColorFingerprint.kt index e14a7ad92e..20063ed0e5 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/PlayerSeekbarColorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/PlayerSeekbarColorFingerprint.kt @@ -4,15 +4,15 @@ import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.InlineTimeBarColorizedBarPlayedColorDark import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.InlineTimeBarPlayedNotHighlightedColor -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object PlayerSeekbarColorFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(InlineTimeBarColorizedBarPlayedColorDark) - && methodDef.containsWideLiteralInstructionIndex( + methodDef.containsWideLiteralInstructionValue(InlineTimeBarColorizedBarPlayedColorDark) + && methodDef.containsWideLiteralInstructionValue( InlineTimeBarPlayedNotHighlightedColor ) } diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/StartVideoInformerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/StartVideoInformerFingerprint.kt deleted file mode 100644 index 0609b6a3af..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/StartVideoInformerFingerprint.kt +++ /dev/null @@ -1,16 +0,0 @@ -package app.revanced.patches.youtube.utils.fingerprints - -import app.revanced.patcher.extensions.or -import app.revanced.patcher.fingerprint.MethodFingerprint -import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode - -internal object StartVideoInformerFingerprint : MethodFingerprint( - returnType = "V", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - opcodes = listOf( - Opcode.INVOKE_INTERFACE, - Opcode.RETURN_VOID - ), - strings = listOf("pc") -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/YouTubeControlsOverlayFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/YouTubeControlsOverlayFingerprint.kt index 727b29ced4..39a48fb614 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/YouTubeControlsOverlayFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/YouTubeControlsOverlayFingerprint.kt @@ -8,7 +8,7 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.Inset import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ScrimOverlay import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.SeekUndoEduOverlayStub import app.revanced.patches.youtube.utils.sponsorblock.SponsorBlockBytecodePatch -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -26,9 +26,9 @@ internal object YouTubeControlsOverlayFingerprint : MethodFingerprint( accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, parameters = emptyList(), customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(FadeDurationFast) - && methodDef.containsWideLiteralInstructionIndex(InsetOverlayViewLayout) - && methodDef.containsWideLiteralInstructionIndex(ScrimOverlay) - && methodDef.containsWideLiteralInstructionIndex(SeekUndoEduOverlayStub) + methodDef.containsWideLiteralInstructionValue(FadeDurationFast) + && methodDef.containsWideLiteralInstructionValue(InsetOverlayViewLayout) + && methodDef.containsWideLiteralInstructionValue(ScrimOverlay) + && methodDef.containsWideLiteralInstructionValue(SeekUndoEduOverlayStub) } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/bottomui/CfBottomUIPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/bottomui/CfBottomUIPatch.kt index 98c9114c18..66374cddfb 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/bottomui/CfBottomUIPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/bottomui/CfBottomUIPatch.kt @@ -1,26 +1,17 @@ package app.revanced.patches.youtube.utils.fix.bottomui import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.Patch -import app.revanced.patches.youtube.utils.fix.bottomui.fingerprints.BottomUIContainerBooleanFingerprint -import app.revanced.patches.youtube.utils.fix.bottomui.fingerprints.BottomUIContainerIntegerFingerprint import app.revanced.patches.youtube.utils.fix.bottomui.fingerprints.FullscreenButtonPositionFingerprint import app.revanced.patches.youtube.utils.fix.bottomui.fingerprints.FullscreenButtonViewStubFingerprint -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getWideLiteralInstructionIndex -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import app.revanced.util.injectLiteralInstructionBooleanCall @Patch( description = "Fixes an issue where overlay button patches were broken by the new layout." ) object CfBottomUIPatch : BytecodePatch( setOf( - BottomUIContainerBooleanFingerprint, - BottomUIContainerIntegerFingerprint, FullscreenButtonPositionFingerprint, FullscreenButtonViewStubFingerprint ) @@ -32,25 +23,14 @@ object CfBottomUIPatch : BytecodePatch( * Therefore, this patch only applies to versions that can resolve this fingerprint. */ mapOf( - BottomUIContainerBooleanFingerprint to 45637647, - BottomUIContainerIntegerFingerprint to 45637647, FullscreenButtonViewStubFingerprint to 45617294, FullscreenButtonPositionFingerprint to 45627640 ).forEach { (fingerprint, literalValue) -> fingerprint.result?.let { - it.mutableMethod.apply { - val targetIndex = getTargetIndexOrThrow( - getWideLiteralInstructionIndex(literalValue.toLong()), - Opcode.MOVE_RESULT - ) - val targetRegister = - getInstruction(targetIndex).registerA - - addInstruction( - targetIndex + 1, - "const/4 v$targetRegister, 0x0" - ) - } + fingerprint.injectLiteralInstructionBooleanCall( + literalValue, + "0x0" + ) } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/bottomui/fingerprints/BottomUIContainerBooleanFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/bottomui/fingerprints/BottomUIContainerBooleanFingerprint.kt deleted file mode 100644 index 5ca66ba1e0..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/bottomui/fingerprints/BottomUIContainerBooleanFingerprint.kt +++ /dev/null @@ -1,8 +0,0 @@ -package app.revanced.patches.youtube.utils.fix.bottomui.fingerprints - -import app.revanced.util.fingerprint.LiteralValueFingerprint - -internal object BottomUIContainerBooleanFingerprint : LiteralValueFingerprint( - returnType = "Z", - literalSupplier = { 45637647 } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/bottomui/fingerprints/BottomUIContainerIntegerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/bottomui/fingerprints/BottomUIContainerIntegerFingerprint.kt deleted file mode 100644 index 78c364ebf1..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/bottomui/fingerprints/BottomUIContainerIntegerFingerprint.kt +++ /dev/null @@ -1,8 +0,0 @@ -package app.revanced.patches.youtube.utils.fix.bottomui.fingerprints - -import app.revanced.util.fingerprint.LiteralValueFingerprint - -internal object BottomUIContainerIntegerFingerprint : LiteralValueFingerprint( - returnType = "V", - literalSupplier = { 45637647 } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/CairoSettingsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/CairoSettingsPatch.kt index 74b3ee2487..c20fd4a113 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/CairoSettingsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/CairoSettingsPatch.kt @@ -1,16 +1,11 @@ package app.revanced.patches.youtube.utils.fix.cairo import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.youtube.misc.backgroundplayback.BackgroundPlaybackPatch import app.revanced.patches.youtube.utils.fix.cairo.fingerprints.CarioFragmentConfigFingerprint -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getWideLiteralInstructionIndex -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import app.revanced.util.injectLiteralInstructionBooleanCall @Patch( description = "Fixes issues where Cairo Fragment is applied." @@ -31,19 +26,10 @@ object CairoSettingsPatch : BytecodePatch( * for screenshots of the Cairo Fragment. */ CarioFragmentConfigFingerprint.result?.let { - it.mutableMethod.apply { - val targetIndex = - getTargetIndexOrThrow( - getWideLiteralInstructionIndex(45532100), - Opcode.MOVE_RESULT - ) - val targetRegister = getInstruction(targetIndex).registerA - - addInstruction( - targetIndex + 1, - "const/4 v$targetRegister, 0x0" - ) - } + CarioFragmentConfigFingerprint.injectLiteralInstructionBooleanCall( + 45532100, + "0x0" + ) } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/litho/ConversionContextObfuscationPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/litho/ConversionContextObfuscationPatch.kt deleted file mode 100644 index 9e039fbb96..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/litho/ConversionContextObfuscationPatch.kt +++ /dev/null @@ -1,44 +0,0 @@ -package app.revanced.patches.youtube.utils.fix.litho - -import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.annotation.Patch -import app.revanced.patches.youtube.utils.fix.litho.fingerprints.ObfuscationConfigFingerprint -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getWideLiteralInstructionIndex -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction - -@Patch( - description = "Fix the issue where ConversionContext is obfuscating. " - + "When ConversionContext is obfuscated, most patches are broken because the litho components can no longer be identified." -) -object ConversionContextObfuscationPatch : BytecodePatch( - setOf(ObfuscationConfigFingerprint) -) { - override fun execute(context: BytecodeContext) { - - /** - * I found a boolean value on YouTube 19.19.39 that obfuscates ConversionContext, - * but I'm not sure if this is for testing purposes only. - */ - ObfuscationConfigFingerprint.result?.let { - it.mutableMethod.apply { - val targetIndex = - getTargetIndexOrThrow( - getWideLiteralInstructionIndex(45631264), - Opcode.MOVE_RESULT - ) - val targetRegister = getInstruction(targetIndex).registerA - - addInstruction( - targetIndex + 1, - "const/4 v$targetRegister, 0x0" - ) - } - } - - } -} diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/litho/fingerprints/ObfuscationConfigFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/litho/fingerprints/ObfuscationConfigFingerprint.kt deleted file mode 100644 index 532b694ac5..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/litho/fingerprints/ObfuscationConfigFingerprint.kt +++ /dev/null @@ -1,14 +0,0 @@ -package app.revanced.patches.youtube.utils.fix.litho.fingerprints - -import app.revanced.patcher.extensions.or -import app.revanced.util.fingerprint.LiteralValueFingerprint -import com.android.tools.smali.dexlib2.AccessFlags - -/** - * This fingerprint is compatible with YouTube v19.19.39+ - */ -internal object ObfuscationConfigFingerprint : LiteralValueFingerprint( - returnType = "Z", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - literalSupplier = { 45631264 } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/shortsplayback/ShortsPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/shortsplayback/ShortsPlaybackPatch.kt index 60da209b94..9788f3bec6 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/shortsplayback/ShortsPlaybackPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/shortsplayback/ShortsPlaybackPatch.kt @@ -1,15 +1,10 @@ package app.revanced.patches.youtube.utils.fix.shortsplayback import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.youtube.utils.fix.shortsplayback.fingerprints.ShortsPlaybackFingerprint -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getWideLiteralInstructionIndex -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import app.revanced.util.injectLiteralInstructionBooleanCall @Patch( description = "Fix issue with looping at the start of the video when applying default video quality to Shorts." @@ -26,19 +21,10 @@ object ShortsPlaybackPatch : BytecodePatch( * RVX applies default video quality to Shorts as well, so this patch is required. */ ShortsPlaybackFingerprint.result?.let { - it.mutableMethod.apply { - val targetIndex = - getTargetIndexOrThrow( - getWideLiteralInstructionIndex(45387052), - Opcode.MOVE_RESULT - ) - val targetRegister = getInstruction(targetIndex).registerA - - addInstruction( - targetIndex + 1, - "const/4 v$targetRegister, 0x0" - ) - } + ShortsPlaybackFingerprint.injectLiteralInstructionBooleanCall( + 45387052, + "0x0" + ) } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt index 3c032f5ba7..3f06ad0522 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt @@ -11,12 +11,13 @@ import app.revanced.patches.youtube.utils.compatibility.Constants import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildBrowseRequestFingerprint import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildInitPlaybackRequestFingerprint import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildMediaDataSourceFingerprint +import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildPlayerRequestURIFingerprint import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.CreateStreamingDataFingerprint import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.NerdsStatsVideoFormatBuilderFingerprint import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.ProtobufClassParseByteBufferFingerprint import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.patches.youtube.video.videoid.VideoIdPatch +import app.revanced.util.findOpcodeIndicesReversed import app.revanced.util.getReference import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow @@ -32,13 +33,13 @@ object SpoofStreamingDataPatch : BaseBytecodePatch( dependencies = setOf( SettingsPatch::class, SpoofUserAgentPatch::class, - VideoIdPatch::class, ), compatiblePackages = Constants.COMPATIBLE_PACKAGE, fingerprints = setOf( BuildBrowseRequestFingerprint, BuildInitPlaybackRequestFingerprint, BuildMediaDataSourceFingerprint, + BuildPlayerRequestURIFingerprint, CreateStreamingDataFingerprint, ProtobufClassParseByteBufferFingerprint, @@ -51,6 +52,27 @@ object SpoofStreamingDataPatch : BaseBytecodePatch( override fun execute(context: BytecodeContext) { + // region Block /get_watch requests to fall back to /player requests. + + BuildPlayerRequestURIFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + val invokeToStringIndex = + BuildPlayerRequestURIFingerprint.indexOfToStringInstruction(this) + val uriRegister = + getInstruction(invokeToStringIndex).registerC + + addInstructions( + invokeToStringIndex, + """ + invoke-static { v$uriRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri; + move-result-object v$uriRegister + """, + ) + } + } + + // endregion + // region Block /initplayback requests to fall back to /get_watch requests. BuildInitPlaybackRequestFingerprint.resultOrThrow().let { @@ -71,7 +93,7 @@ object SpoofStreamingDataPatch : BaseBytecodePatch( // endregion - // region Copy request headers for streaming data fetch. + // region Fetch replacement streams. BuildBrowseRequestFingerprint.resultOrThrow().let { result -> result.mutableMethod.apply { @@ -89,7 +111,7 @@ object SpoofStreamingDataPatch : BaseBytecodePatch( var smaliInstructions = "invoke-static { v$urlRegister, v$mapRegister }, " + "$INTEGRATIONS_CLASS_DESCRIPTOR->" + - "setFetchHeaders(Ljava/lang/String;Ljava/util/Map;)V" + "fetchStreams(Ljava/lang/String;Ljava/util/Map;)V" if (entrySetIndex < 0) smaliInstructions = """ move-object/from16 v$mapRegister, p1 @@ -160,6 +182,7 @@ object SpoofStreamingDataPatch : BaseBytecodePatch( iget-object v$freeRegister, v$freeRegister, $getStreamingDataField if-eqz v0, :disabled iput-object v$freeRegister, p0, $setStreamingDataField + """, ExternalLabel("disabled", getInstruction(insertIndex)) ) @@ -189,7 +212,7 @@ object SpoofStreamingDataPatch : BaseBytecodePatch( invoke-static { v1, v2, v3 }, $INTEGRATIONS_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B move-result-object v1 iput-object v1, v0, $definingClass->d:[B - """, + """, ) } } @@ -199,27 +222,20 @@ object SpoofStreamingDataPatch : BaseBytecodePatch( // region Append spoof info. NerdsStatsVideoFormatBuilderFingerprint.resultOrThrow().mutableMethod.apply { - for (index in implementation!!.instructions.size - 1 downTo 0) { - val instruction = getInstruction(index) - if (instruction.opcode != Opcode.RETURN_OBJECT) - continue - - val register = (instruction as OneRegisterInstruction).registerA + findOpcodeIndicesReversed(Opcode.RETURN_OBJECT).forEach { index -> + val register = getInstruction(index).registerA addInstructions( index, """ - invoke-static {v$register}, $INTEGRATIONS_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String; - move-result-object v$register - """ + invoke-static {v$register}, $INTEGRATIONS_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$register + """ ) } } // endregion - // Prefetch streaming data. - VideoIdPatch.hookPlayerResponseVideoId("$INTEGRATIONS_CLASS_DESCRIPTOR->fetchStreamingData(Ljava/lang/String;Z)V") - /** * Add settings */ diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/fingerprints/BuildPlayerRequestURIFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/fingerprints/BuildPlayerRequestURIFingerprint.kt new file mode 100644 index 0000000000..b2601154e4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/fingerprints/BuildPlayerRequestURIFingerprint.kt @@ -0,0 +1,26 @@ +package app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.utils.fix.streamingdata.fingerprints.BuildPlayerRequestURIFingerprint.indexOfToStringInstruction +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.reference.MethodReference + +internal object BuildPlayerRequestURIFingerprint : MethodFingerprint( + returnType = "Ljava/lang/String;", + strings = listOf( + "key", + "asig", + ), + customFingerprint = { methodDef, _ -> + indexOfToStringInstruction(methodDef) >= 0 + }, +) { + fun indexOfToStringInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference().toString() == "Landroid/net/Uri;->toString()Ljava/lang/String;" + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/suggestedvideoendscreen/SuggestedVideoEndScreenPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/suggestedvideoendscreen/SuggestedVideoEndScreenPatch.kt index 7021857799..a4b297c0bd 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/suggestedvideoendscreen/SuggestedVideoEndScreenPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/suggestedvideoendscreen/SuggestedVideoEndScreenPatch.kt @@ -8,9 +8,9 @@ import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.youtube.utils.fix.suggestedvideoendscreen.fingerprints.RemoveOnLayoutChangeListenerFingerprint import app.revanced.patches.youtube.utils.integrations.Constants.PLAYER_CLASS_DESCRIPTOR -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow import app.revanced.util.getWalkerMethod +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction @@ -36,9 +36,10 @@ object SuggestedVideoEndScreenPatch : BytecodePatch( it.getWalkerMethod(context, it.scanResult.patternScanResult!!.endIndex) walkerIndex.apply { - val invokeInterfaceIndex = getTargetIndexOrThrow(Opcode.INVOKE_INTERFACE) + val invokeInterfaceIndex = + indexOfFirstInstructionOrThrow(opcode = Opcode.INVOKE_INTERFACE) val iGetObjectIndex = - getTargetIndexReversedOrThrow(invokeInterfaceIndex, Opcode.IGET_OBJECT) + indexOfFirstInstructionReversedOrThrow(invokeInterfaceIndex, Opcode.IGET_OBJECT) val invokeInterfaceReference = getInstruction(invokeInterfaceIndex).reference diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/suggestedvideoendscreen/fingerprints/RemoveOnLayoutChangeListenerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/suggestedvideoendscreen/fingerprints/RemoveOnLayoutChangeListenerFingerprint.kt index 2c0f744e7b..2c923219bc 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fix/suggestedvideoendscreen/fingerprints/RemoveOnLayoutChangeListenerFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/fix/suggestedvideoendscreen/fingerprints/RemoveOnLayoutChangeListenerFingerprint.kt @@ -1,15 +1,18 @@ package app.revanced.patches.youtube.utils.fix.suggestedvideoendscreen.fingerprints import app.revanced.patcher.extensions.or -import app.revanced.util.fingerprint.ReferenceFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.reference.MethodReference /** * This fingerprint is also compatible with very old YouTube versions. - * Tested on YouTube v16.40.36, v18.29.38, v19.12.41. + * Tested on YouTube v16.40.36, v18.29.38, v19.16.39. */ -internal object RemoveOnLayoutChangeListenerFingerprint : ReferenceFingerprint( +internal object RemoveOnLayoutChangeListenerFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = emptyList(), @@ -18,5 +21,10 @@ internal object RemoveOnLayoutChangeListenerFingerprint : ReferenceFingerprint( Opcode.INVOKE_VIRTUAL ), // This is the only reference present in the entire smali. - reference = { "YouTubePlayerOverlaysLayout;->removeOnLayoutChangeListener(Landroid/view/View${'$'}OnLayoutChangeListener;)V" } + customFingerprint = { methodDef, _ -> + methodDef.indexOfFirstInstruction { + getReference()?.toString() + ?.endsWith("YouTubePlayerOverlaysLayout;->removeOnLayoutChangeListener(Landroid/view/View${'$'}OnLayoutChangeListener;)V") == true + } >= 0 + } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/flyoutmenu/FlyoutMenuHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/flyoutmenu/FlyoutMenuHookPatch.kt index 56270f1233..f6275602a3 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/flyoutmenu/FlyoutMenuHookPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/flyoutmenu/FlyoutMenuHookPatch.kt @@ -7,7 +7,7 @@ import app.revanced.patches.youtube.utils.fingerprints.PlaybackRateBottomSheetBu import app.revanced.patches.youtube.utils.flyoutmenu.fingerprints.VideoQualityBottomSheetClassFingerprint import app.revanced.patches.youtube.utils.integrations.Constants.INTEGRATIONS_PATH import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch -import app.revanced.util.addFieldAndInstructions +import app.revanced.util.addStaticFieldToIntegration import app.revanced.util.resultOrThrow @Patch( @@ -25,10 +25,6 @@ object FlyoutMenuHookPatch : BytecodePatch( override fun execute(context: BytecodeContext) { - val videoUtilsMutableClass = context.findClass( - INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR - )!!.mutableClass - PlaybackRateBottomSheetBuilderFingerprint.resultOrThrow().let { it.mutableMethod.apply { val smaliInstructions = @@ -39,13 +35,12 @@ object FlyoutMenuHookPatch : BytecodePatch( return-void """ - videoUtilsMutableClass.addFieldAndInstructions( - context, + context.addStaticFieldToIntegration( + INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR, "showPlaybackSpeedFlyoutMenu", "playbackRateBottomSheetClass", definingClass, - smaliInstructions, - true + smaliInstructions ) } } @@ -61,13 +56,12 @@ object FlyoutMenuHookPatch : BytecodePatch( return-void """ - videoUtilsMutableClass.addFieldAndInstructions( - context, + context.addStaticFieldToIntegration( + INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR, "showVideoQualityFlyoutMenu", "videoQualityBottomSheetClass", definingClass, - smaliInstructions, - true + smaliInstructions ) } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/integrations/fingerprints/StandalonePlayerActivityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/integrations/fingerprints/StandalonePlayerActivityFingerprint.kt index 2b643e64fa..d42bea6d97 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/integrations/fingerprints/StandalonePlayerActivityFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/integrations/fingerprints/StandalonePlayerActivityFingerprint.kt @@ -8,7 +8,10 @@ import app.revanced.patches.shared.integrations.BaseIntegrationsPatch.Integratio * In 2023 supported was ended and is no longer available, * but this may still be used by older apps: * https://developers.google.com/youtube/android/player + * + * Deprecated in YouTube v19.38.xx+. */ +@Deprecated("Fingerprint is obsolete and will be deleted soon") internal object StandalonePlayerActivityFingerprint : IntegrationsFingerprint( customFingerprint = { methodDef, _ -> methodDef.definingClass == "Lcom/google/android/youtube/api/StandalonePlayerActivity;" diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/lottie/LottieAnimationViewHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/lottie/LottieAnimationViewHookPatch.kt index 24e436f45e..1d3e106f48 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/lottie/LottieAnimationViewHookPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/lottie/LottieAnimationViewHookPatch.kt @@ -3,11 +3,11 @@ package app.revanced.patches.youtube.utils.lottie import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.youtube.utils.integrations.Constants.UTILS_PATH import app.revanced.patches.youtube.utils.lottie.fingerprints.SetAnimationFingerprint import app.revanced.patches.youtube.utils.lottie.fingerprints.SetAnimationFingerprint.LOTTIE_ANIMATION_VIEW_CLASS_DESCRIPTOR +import app.revanced.util.findMethodOrThrow import app.revanced.util.resultOrThrow @Patch( @@ -20,21 +20,15 @@ object LottieAnimationViewHookPatch : BytecodePatch( "$UTILS_PATH/LottieAnimationViewPatch;" override fun execute(context: BytecodeContext) { - - val setAnimationMethodName = SetAnimationFingerprint.resultOrThrow().mutableMethod.name - val setAnimationCall = "invoke-virtual {p0, p1}, " + - LOTTIE_ANIMATION_VIEW_CLASS_DESCRIPTOR + - "->" + - setAnimationMethodName + - "(I)V" - - context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR) - ?.mutableClass - ?.methods - ?.first { method -> method.name == "setAnimation" } - ?.addInstruction( - 0, - setAnimationCall - ) ?: throw PatchException("Could not find setAnimation method") + context.findMethodOrThrow(INTEGRATIONS_CLASS_DESCRIPTOR) { + name == "setAnimation" + }.addInstruction( + 0, + "invoke-virtual {p0, p1}, " + + LOTTIE_ANIMATION_VIEW_CLASS_DESCRIPTOR + + "->" + + SetAnimationFingerprint.resultOrThrow().mutableMethod.name + + "(I)V" + ) } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/NavigationBarHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/NavigationBarHookPatch.kt index 92b600ca57..65d8819e70 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/NavigationBarHookPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/NavigationBarHookPatch.kt @@ -2,25 +2,24 @@ package app.revanced.patches.youtube.utils.navigation import app.revanced.patcher.data.BytecodeContext 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.getInstructions import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod -import app.revanced.patches.youtube.utils.fingerprints.InitializeButtonsFingerprint import app.revanced.patches.youtube.utils.integrations.Constants.SHARED_PATH import app.revanced.patches.youtube.utils.mainactivity.MainActivityResolvePatch -import app.revanced.patches.youtube.utils.navigation.fingerprints.MobileTopBarButtonOnClickFingerprint +import app.revanced.patches.youtube.utils.navigation.fingerprints.InitializeBottomBarContainerFingerprint +import app.revanced.patches.youtube.utils.navigation.fingerprints.InitializeButtonsFingerprint import app.revanced.patches.youtube.utils.navigation.fingerprints.NavigationEnumFingerprint import app.revanced.patches.youtube.utils.navigation.fingerprints.PivotBarButtonsCreateDrawableViewFingerprint import app.revanced.patches.youtube.utils.navigation.fingerprints.PivotBarButtonsCreateResourceViewFingerprint import app.revanced.patches.youtube.utils.navigation.fingerprints.PivotBarButtonsViewSetSelectedFingerprint import app.revanced.patches.youtube.utils.navigation.fingerprints.PivotBarConstructorFingerprint -import app.revanced.patches.youtube.utils.navigation.fingerprints.SettingsActivityOnBackPressedFingerprint import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch +import app.revanced.util.findMethodOrThrow import app.revanced.util.getReference import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -41,13 +40,12 @@ import com.android.tools.smali.dexlib2.util.MethodUtil @Suppress("unused") object NavigationBarHookPatch : BytecodePatch( setOf( - MobileTopBarButtonOnClickFingerprint, + InitializeBottomBarContainerFingerprint, NavigationEnumFingerprint, PivotBarButtonsCreateDrawableViewFingerprint, PivotBarButtonsCreateResourceViewFingerprint, PivotBarButtonsViewSetSelectedFingerprint, PivotBarConstructorFingerprint, - SettingsActivityOnBackPressedFingerprint ), ) { private const val INTEGRATIONS_CLASS_DESCRIPTOR = @@ -57,6 +55,10 @@ object NavigationBarHookPatch : BytecodePatch( private lateinit var navigationTabCreatedCallback: MutableMethod + private lateinit var bottomBarContainerMethod: MutableMethod + private var bottomBarContainerIndex = 0 + private var bottomBarContainerRegister = 0 + override fun execute(context: BytecodeContext) { fun MutableMethod.addHook(hook: Hook, insertPredicate: Instruction.() -> Boolean) { val filtered = getInstructions().filter(insertPredicate) @@ -110,43 +112,31 @@ object NavigationBarHookPatch : BytecodePatch( val instruction = getInstruction(index) val viewRegister = instruction.registerC val isSelectedRegister = instruction.registerD - val freeRegister = implementation!!.registerCount - parameters.size - 2 addInstruction( index + 1, - "invoke-static { v$viewRegister, v$freeRegister, v$isSelectedRegister }, " + - "$INTEGRATIONS_CLASS_DESCRIPTOR->navigationTabSelected(Landroid/view/View;IZ)V", - ) - addInstruction( - 0, - "move/16 v$freeRegister, p1" + "invoke-static { v$viewRegister, v$isSelectedRegister }, " + + "$INTEGRATIONS_CLASS_DESCRIPTOR->navigationTabSelected(Landroid/view/View;Z)V", ) } } navigationTabCreatedCallback = - context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR)?.mutableClass?.methods?.first { method -> - method.name == "navigationTabCreatedCallback" - } ?: throw PatchException("Could not find navigationTabCreatedCallback method") + context.findMethodOrThrow(INTEGRATIONS_CLASS_DESCRIPTOR) { + name == "navigationTabCreatedCallback" + } MainActivityResolvePatch.injectOnBackPressedMethodCall( INTEGRATIONS_CLASS_DESCRIPTOR, "onBackPressed" ) - /** - * Since it is used only after opening the library tab, set index to 3. - */ - arrayOf( - MobileTopBarButtonOnClickFingerprint, - SettingsActivityOnBackPressedFingerprint - ).forEach { fingerprint -> - fingerprint.resultOrThrow().mutableMethod.addInstructions( - 0, """ - const/4 v0, 0x3 - invoke-static {v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->setNavigationTabIndex(I)V - """ - ) + InitializeBottomBarContainerFingerprint.resultOrThrow().mutableMethod.apply { + bottomBarContainerMethod = this + bottomBarContainerIndex = + InitializeBottomBarContainerFingerprint.indexOfLayoutChangeListenerInstruction(this) + bottomBarContainerRegister = + getInstruction(bottomBarContainerIndex).registerC } } @@ -162,6 +152,12 @@ object NavigationBarHookPatch : BytecodePatch( } } + fun addBottomBarContainerHook(descriptor: String) = + bottomBarContainerMethod.addInstruction( + bottomBarContainerIndex, + "invoke-static { v$bottomBarContainerRegister }, $descriptor" + ) + private enum class Hook(val methodName: String, val parameters: String) { SET_LAST_APP_NAVIGATION_ENUM("setLastAppNavigationEnum", "Ljava/lang/Enum;"), NAVIGATION_TAB_LOADED("navigationTabLoaded", "Landroid/view/View;"), diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/InitializeBottomBarContainerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/InitializeBottomBarContainerFingerprint.kt new file mode 100644 index 0000000000..9991cae7dd --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/InitializeBottomBarContainerFingerprint.kt @@ -0,0 +1,29 @@ +package app.revanced.patches.youtube.utils.navigation.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.utils.navigation.fingerprints.InitializeBottomBarContainerFingerprint.indexOfLayoutChangeListenerInstruction +import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.BottomBarContainer +import app.revanced.util.containsWideLiteralInstructionValue +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +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 object InitializeBottomBarContainerFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + customFingerprint = { methodDef, classDef -> + AccessFlags.SYNTHETIC.isSet(classDef.accessFlags) && + methodDef.containsWideLiteralInstructionValue(BottomBarContainer) && + indexOfLayoutChangeListenerInstruction(methodDef) >= 0 + }, +) { + fun indexOfLayoutChangeListenerInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.toString() == "Landroid/view/View;->addOnLayoutChangeListener(Landroid/view/View${'$'}OnLayoutChangeListener;)V" + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/InitializeButtonsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/InitializeButtonsFingerprint.kt similarity index 86% rename from src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/InitializeButtonsFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/InitializeButtonsFingerprint.kt index 0b054f08d1..98d93cf687 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/fingerprints/InitializeButtonsFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/InitializeButtonsFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.youtube.utils.fingerprints +package app.revanced.patches.youtube.utils.navigation.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ImageOnlyTab diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/MobileTopBarButtonOnClickFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/MobileTopBarButtonOnClickFingerprint.kt deleted file mode 100644 index 48215f9c8e..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/MobileTopBarButtonOnClickFingerprint.kt +++ /dev/null @@ -1,8 +0,0 @@ -package app.revanced.patches.youtube.utils.navigation.fingerprints - -import app.revanced.patcher.fingerprint.MethodFingerprint - -object MobileTopBarButtonOnClickFingerprint : MethodFingerprint( - strings = listOf("MenuButtonRendererKey"), - customFingerprint = { methodDef, _ -> methodDef.name == "onClick" } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/SettingsActivityOnBackPressedFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/SettingsActivityOnBackPressedFingerprint.kt deleted file mode 100644 index 03fe1c87c0..0000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/navigation/fingerprints/SettingsActivityOnBackPressedFingerprint.kt +++ /dev/null @@ -1,14 +0,0 @@ -package app.revanced.patches.youtube.utils.navigation.fingerprints - -import app.revanced.patcher.extensions.or -import app.revanced.patcher.fingerprint.MethodFingerprint -import com.android.tools.smali.dexlib2.AccessFlags - -object SettingsActivityOnBackPressedFingerprint : MethodFingerprint( - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - returnType = "V", - customFingerprint = { methodDef, _ -> - methodDef.definingClass.endsWith("/SettingsActivity;") - && methodDef.name == "onBackPressed" - } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/pip/fingerprints/PiPPlaybackFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/pip/fingerprints/PiPPlaybackFingerprint.kt index 6062c13ca6..a342536644 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/pip/fingerprints/PiPPlaybackFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/pip/fingerprints/PiPPlaybackFingerprint.kt @@ -1,11 +1,12 @@ package app.revanced.patches.youtube.utils.pip.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.utils.PlayerResponseModelUtils.PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR import com.android.tools.smali.dexlib2.Opcode internal object PiPPlaybackFingerprint : MethodFingerprint( returnType = "Z", - parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"), + parameters = listOf(PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR), opcodes = listOf( Opcode.INVOKE_INTERFACE, Opcode.MOVE_RESULT_OBJECT, diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt index 324b691315..03f5c5ca26 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt @@ -16,8 +16,9 @@ import app.revanced.patches.youtube.utils.playercontrols.fingerprints.MotionEven import app.revanced.patches.youtube.utils.playercontrols.fingerprints.PlayerControlsVisibilityEntityModelFingerprint import app.revanced.patches.youtube.utils.playercontrols.fingerprints.PlayerControlsVisibilityFingerprint import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.alsoResolve +import app.revanced.util.findMethodOrThrow +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction @@ -50,13 +51,11 @@ object PlayerControlsPatch : BytecodePatch( // region patch for hook visibility of play control buttons (e.g. pause, play button, etc) - PlayerButtonsVisibilityFingerprint.resolve( - context, - PlayerButtonsResourcesFingerprint.resultOrThrow().mutableClass - ) - PlayerButtonsVisibilityFingerprint.resultOrThrow().let { + PlayerButtonsVisibilityFingerprint.alsoResolve( + context, PlayerButtonsResourcesFingerprint + ).let { it.mutableMethod.apply { - val viewIndex = getTargetIndexOrThrow(Opcode.INVOKE_INTERFACE) + val viewIndex = indexOfFirstInstructionOrThrow(opcode = Opcode.INVOKE_INTERFACE) val viewRegister = getInstruction(viewIndex).registerD addInstruction( @@ -70,11 +69,9 @@ object PlayerControlsPatch : BytecodePatch( // region patch for hook visibility of play controls layout - PlayerControlsVisibilityFingerprint.resolve( - context, - YouTubeControlsOverlayFingerprint.resultOrThrow().mutableClass - ) - PlayerControlsVisibilityFingerprint.resultOrThrow().mutableMethod.addInstruction( + PlayerControlsVisibilityFingerprint.alsoResolve( + context, YouTubeControlsOverlayFingerprint + ).mutableMethod.addInstruction( 0, "invoke-static {p1}, $INTEGRATIONS_CLASS_DESCRIPTOR->changeVisibility(Z)V" ) @@ -83,12 +80,10 @@ object PlayerControlsPatch : BytecodePatch( // region patch for detecting motion events in play controls layout - MotionEventFingerprint.resolve( - context, - YouTubeControlsOverlayFingerprint.resultOrThrow().mutableClass - ) - MotionEventFingerprint.resultOrThrow().mutableMethod.apply { - val insertIndex = getTargetIndexWithMethodReferenceNameOrThrow("setTranslationY") + 1 + MotionEventFingerprint.alsoResolve( + context, YouTubeControlsOverlayFingerprint + ).mutableMethod.apply { + val insertIndex = MotionEventFingerprint.indexOfTranslationInstruction(this) + 1 addInstruction( insertIndex, @@ -121,28 +116,25 @@ object PlayerControlsPatch : BytecodePatch( // region set methods to inject into integration - val playerControlsMutableClass = - context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR)!!.mutableClass - changeVisibilityMethod = - playerControlsMutableClass.methods.single { method -> - method.name == "changeVisibility" - && method.parameters == listOf("Z", "Z") + context.findMethodOrThrow(INTEGRATIONS_CLASS_DESCRIPTOR) { + name == "changeVisibility" + && parameters == listOf("Z", "Z") } changeVisibilityNegatedImmediatelyMethod = - playerControlsMutableClass.methods.single { method -> - method.name == "changeVisibilityNegatedImmediately" + context.findMethodOrThrow(INTEGRATIONS_CLASS_DESCRIPTOR) { + name == "changeVisibilityNegatedImmediately" } initializeBottomControlButtonMethod = - playerControlsMutableClass.methods.single { method -> - method.name == "initializeBottomControlButton" + context.findMethodOrThrow(INTEGRATIONS_CLASS_DESCRIPTOR) { + name == "initializeBottomControlButton" } initializeTopControlButtonMethod = - playerControlsMutableClass.methods.single { method -> - method.name == "initializeTopControlButton" + context.findMethodOrThrow(INTEGRATIONS_CLASS_DESCRIPTOR) { + name == "initializeTopControlButton" } // endregion diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsVisibilityHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsVisibilityHookPatch.kt index b991ec65ce..5f9fd61ffe 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsVisibilityHookPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsVisibilityHookPatch.kt @@ -9,7 +9,7 @@ import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.youtube.utils.integrations.Constants.UTILS_PATH import app.revanced.patches.youtube.utils.playercontrols.fingerprints.PlayerControlsVisibilityEntityModelFingerprint import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch -import app.revanced.util.getTargetIndexOrThrow +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction @@ -31,7 +31,7 @@ object PlayerControlsVisibilityHookPatch : BytecodePatch( val staticReference = getInstruction(startIndex + 1).reference it.mutableClass.methods.find { method -> method.name == "" }?.apply { - val targetIndex = getTargetIndexOrThrow(Opcode.IPUT_OBJECT) + val targetIndex = indexOfFirstInstructionOrThrow(Opcode.IPUT_OBJECT) val targetRegister = getInstruction(targetIndex).registerA diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/fingerprints/MotionEventFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/fingerprints/MotionEventFingerprint.kt index 45d9d329d3..743129ab40 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/fingerprints/MotionEventFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/fingerprints/MotionEventFingerprint.kt @@ -1,9 +1,21 @@ package app.revanced.patches.youtube.utils.playercontrols.fingerprints -import app.revanced.util.fingerprint.MethodReferenceNameFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.utils.playercontrols.fingerprints.MotionEventFingerprint.indexOfTranslationInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversed +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.reference.MethodReference -internal object MotionEventFingerprint : MethodReferenceNameFingerprint( +internal object MotionEventFingerprint : MethodFingerprint( returnType = "V", parameters = listOf("Landroid/view/MotionEvent;"), - reference = { "setTranslationY" } -) + customFingerprint = { methodDef, _ -> + indexOfTranslationInstruction(methodDef) >= 0 + } +) { + fun indexOfTranslationInstruction(methodDef: Method) = + methodDef.indexOfFirstInstructionReversed { + getReference()?.name == "setTranslationY" + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/PlayerTypeHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/PlayerTypeHookPatch.kt index c5f0b86d51..748a0f96db 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/PlayerTypeHookPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/PlayerTypeHookPatch.kt @@ -13,15 +13,21 @@ import app.revanced.patches.youtube.utils.integrations.Constants.UTILS_PATH import app.revanced.patches.youtube.utils.playertype.fingerprint.ActionBarSearchResultsFingerprint import app.revanced.patches.youtube.utils.playertype.fingerprint.BrowseIdClassFingerprint import app.revanced.patches.youtube.utils.playertype.fingerprint.PlayerTypeFingerprint +import app.revanced.patches.youtube.utils.playertype.fingerprint.ReelWatchPagerFingerprint import app.revanced.patches.youtube.utils.playertype.fingerprint.VideoStateFingerprint import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch -import app.revanced.util.addFieldAndInstructions -import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ReelWatchPlayer +import app.revanced.util.addStaticFieldToIntegration +import app.revanced.util.alsoResolve +import app.revanced.util.findMethodOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstStringInstructionOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference @@ -31,6 +37,7 @@ object PlayerTypeHookPatch : BytecodePatch( ActionBarSearchResultsFingerprint, BrowseIdClassFingerprint, PlayerTypeFingerprint, + ReelWatchPagerFingerprint, YouTubeControlsOverlayFingerprint ) ) { @@ -44,36 +51,48 @@ object PlayerTypeHookPatch : BytecodePatch( // region patch for set player type - PlayerTypeFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - addInstruction( - 0, - "invoke-static {p1}, " + - "$INTEGRATIONS_PLAYER_TYPE_HOOK_CLASS_DESCRIPTOR->setPlayerType(Ljava/lang/Enum;)V" - ) + PlayerTypeFingerprint.resultOrThrow().mutableMethod.addInstruction( + 0, + "invoke-static {p1}, " + + "$INTEGRATIONS_PLAYER_TYPE_HOOK_CLASS_DESCRIPTOR->setPlayerType(Ljava/lang/Enum;)V" + ) + + // endregion + + // region patch for set shorts player state + + ReelWatchPagerFingerprint.resultOrThrow().mutableMethod.apply { + val literIndex = indexOfFirstWideLiteralInstructionValueOrThrow(ReelWatchPlayer) + 2 + val registerIndex = indexOfFirstInstructionOrThrow(literIndex) { + opcode == Opcode.MOVE_RESULT_OBJECT } + val viewRegister = getInstruction(registerIndex).registerA + + addInstruction( + registerIndex + 1, + "invoke-static {v$viewRegister}, " + + "$INTEGRATIONS_PLAYER_TYPE_HOOK_CLASS_DESCRIPTOR->onShortsCreate(Landroid/view/View;)V" + ) } // endregion // region patch for set video state - YouTubeControlsOverlayFingerprint.resultOrThrow().let { parentResult -> - VideoStateFingerprint.also { - it.resolve(context, parentResult.classDef) - }.resultOrThrow().let { - it.mutableMethod.apply { - val endIndex = it.scanResult.patternScanResult!!.endIndex - val videoStateFieldName = - getInstruction(endIndex).reference - - addInstructions( - 0, """ - iget-object v0, p1, $videoStateFieldName # copy VideoState parameter field - invoke-static {v0}, $INTEGRATIONS_PLAYER_TYPE_HOOK_CLASS_DESCRIPTOR->setVideoState(Ljava/lang/Enum;)V - """ - ) - } + VideoStateFingerprint.alsoResolve( + context, YouTubeControlsOverlayFingerprint + ).let { + it.mutableMethod.apply { + val endIndex = it.scanResult.patternScanResult!!.endIndex + val videoStateFieldName = + getInstruction(endIndex).reference + + addInstructions( + 0, """ + iget-object v0, p1, $videoStateFieldName # copy VideoState parameter field + invoke-static {v0}, $INTEGRATIONS_PLAYER_TYPE_HOOK_CLASS_DESCRIPTOR->setVideoState(Ljava/lang/Enum;)V + """ + ) } } @@ -83,40 +102,37 @@ object PlayerTypeHookPatch : BytecodePatch( BrowseIdClassFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = getStringInstructionIndex("VL") - 1 - val targetReference = getInstruction(targetIndex).reference - val targetClass = - context.findClass((targetReference as FieldReference).definingClass)!!.mutableClass - - targetClass.methods.find { method -> method.name == "" } - ?.apply { - val browseIdFieldIndex = getTargetIndexOrThrow(Opcode.IPUT_OBJECT) - val browseIdFieldName = - (getInstruction(browseIdFieldIndex).reference as FieldReference).name - - val smaliInstructions = + val targetIndex = indexOfFirstStringInstructionOrThrow("VL") - 1 + val targetClass = getInstruction(targetIndex) + .getReference() + ?.definingClass + ?: throw PatchException("Could not find browseId class") + + context.findMethodOrThrow(targetClass).apply { + val browseIdFieldReference = getInstruction( + indexOfFirstInstructionOrThrow(Opcode.IPUT_OBJECT) + ).reference + val browseIdFieldName = (browseIdFieldReference as FieldReference).name + + val smaliInstructions = + """ + if-eqz v0, :ignore + iget-object v0, v0, $definingClass->$browseIdFieldName:Ljava/lang/String; + if-eqz v0, :ignore + return-object v0 + :ignore + const-string v0, "" + return-object v0 """ - if-eqz v0, :ignore - iget-object v0, v0, $definingClass->$browseIdFieldName:Ljava/lang/String; - if-eqz v0, :ignore - return-object v0 - :ignore - const-string v0, "" - return-object v0 - """ - - val rootViewMutableClass = - context.findClass(INTEGRATIONS_ROOT_VIEW_HOOK_CLASS_DESCRIPTOR)!!.mutableClass - - rootViewMutableClass.addFieldAndInstructions( - context, - "getBrowseId", - "browseIdClass", - definingClass, - smaliInstructions, - true - ) - } ?: throw PatchException("BrowseIdClass not found!") + + context.addStaticFieldToIntegration( + INTEGRATIONS_ROOT_VIEW_HOOK_CLASS_DESCRIPTOR, + "getBrowseId", + "browseIdClass", + definingClass, + smaliInstructions + ) + } } } @@ -129,7 +145,7 @@ object PlayerTypeHookPatch : BytecodePatch( // so this works regardless which layout is used. ActionBarSearchResultsFingerprint.resultOrThrow().mutableMethod.apply { val instructionIndex = - getTargetIndexWithMethodReferenceNameOrThrow("setLayoutDirection") + ActionBarSearchResultsFingerprint.indexOfLayoutDirectionInstruction(this) val viewRegister = getInstruction(instructionIndex).registerC addInstruction( diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/fingerprint/ActionBarSearchResultsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/fingerprint/ActionBarSearchResultsFingerprint.kt index 228f084897..d14e8a9e57 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/fingerprint/ActionBarSearchResultsFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/fingerprint/ActionBarSearchResultsFingerprint.kt @@ -1,13 +1,28 @@ package app.revanced.patches.youtube.utils.playertype.fingerprint import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.utils.playertype.fingerprint.ActionBarSearchResultsFingerprint.indexOfLayoutDirectionInstruction import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ActionBarSearchResultsViewMic -import app.revanced.util.fingerprint.LiteralValueFingerprint +import app.revanced.util.containsWideLiteralInstructionValue +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction 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 object ActionBarSearchResultsFingerprint : LiteralValueFingerprint( +internal object ActionBarSearchResultsFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, returnType = "Landroid/view/View;", - parameters = listOf("Landroid/view/LayoutInflater;"), - literalSupplier = { ActionBarSearchResultsViewMic } -) \ No newline at end of file + customFingerprint = { methodDef, _ -> + methodDef.containsWideLiteralInstructionValue(ActionBarSearchResultsViewMic) && + indexOfLayoutDirectionInstruction(methodDef) >= 0 + } +) { + fun indexOfLayoutDirectionInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference().toString() == "Landroid/view/View;->setLayoutDirection(I)V" + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/fingerprint/ReelWatchPagerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/fingerprint/ReelWatchPagerFingerprint.kt new file mode 100644 index 0000000000..7bb7d066b2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/fingerprint/ReelWatchPagerFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.utils.playertype.fingerprint + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ReelWatchPlayer +import app.revanced.util.fingerprint.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object ReelWatchPagerFingerprint : LiteralValueFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Landroid/view/View;", + literalSupplier = { ReelWatchPlayer } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/recyclerview/BottomSheetRecyclerViewPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/recyclerview/BottomSheetRecyclerViewPatch.kt index b045449254..560fd7437a 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/recyclerview/BottomSheetRecyclerViewPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/recyclerview/BottomSheetRecyclerViewPatch.kt @@ -2,18 +2,16 @@ package app.revanced.patches.youtube.utils.recyclerview import app.revanced.patcher.data.BytecodeContext 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.recyclerview.fingerprints.BottomSheetRecyclerViewBuilderFingerprint import app.revanced.patches.youtube.utils.recyclerview.fingerprints.RecyclerViewTreeObserverFingerprint import app.revanced.util.getReference -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.getWideLiteralInstructionIndex import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference object BottomSheetRecyclerViewPatch : BytecodePatch( @@ -35,15 +33,10 @@ object BottomSheetRecyclerViewPatch : BytecodePatch( * Therefore, we need to force this to be true. */ BottomSheetRecyclerViewBuilderFingerprint.result?.let { - it.mutableMethod.apply { - val targetIndex = getWideLiteralInstructionIndex(45382015) + 2 - val targetRegister = getInstruction(targetIndex).registerA - - addInstruction( - targetIndex + 1, - "const/4 v$targetRegister, 0x1" - ) - } + BottomSheetRecyclerViewBuilderFingerprint.injectLiteralInstructionBooleanCall( + 45382015, + "0x1" + ) } RecyclerViewTreeObserverFingerprint.resultOrThrow().mutableMethod.apply { @@ -54,7 +47,7 @@ object BottomSheetRecyclerViewPatch : BytecodePatch( && getReference()?.type == "Landroid/view/ViewTreeObserver${'$'}OnDrawListener;" } recyclerViewTreeObserverInsertIndex = - getTargetIndexReversedOrThrow(onDrawListenerIndex, Opcode.CHECK_CAST) + 1 + indexOfFirstInstructionReversedOrThrow(onDrawListenerIndex, Opcode.CHECK_CAST) + 1 } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt index 82361b9f6d..a8c77ce038 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt @@ -31,6 +31,7 @@ object SharedResourceIdPatch : ResourcePatch() { var BadgeLabel = -1L var Bar = -1L var BarContainerHeight = -1L + var BottomBarContainer = -1L var BottomSheetFooterText = -1L var BottomSheetRecyclerView = -1L var BottomUiContainerStub = -1L @@ -44,6 +45,7 @@ object SharedResourceIdPatch : ResourcePatch() { var ControlsLayoutStub = -1L var DarkBackground = -1L var DarkSplashAnimation = -1L + var DesignBottomSheet = -1L var DonationCompanion = -1L var DrawerContentView = -1L var DrawerResults = -1L @@ -94,6 +96,7 @@ object SharedResourceIdPatch : ResourcePatch() { var ReelRightLikeIcon = -1L var ReelTimeBarPlayedColor = -1L var ReelVodTimeStampsContainer = -1L + var ReelWatchPlayer = -1L var RelatedChipCloudMargin = -1L var RightComment = -1L var ScrimOverlay = -1L @@ -136,6 +139,7 @@ object SharedResourceIdPatch : ResourcePatch() { BadgeLabel = getId(ID, "badge_label") Bar = getId(LAYOUT, "bar") BarContainerHeight = getId(DIMEN, "bar_container_height") + BottomBarContainer = getId(ID, "bottom_bar_container") BottomSheetFooterText = getId(ID, "bottom_sheet_footer_text") BottomSheetRecyclerView = getId(LAYOUT, "bottom_sheet_recycler_view") BottomUiContainerStub = getId(ID, "bottom_ui_container_stub") @@ -149,6 +153,7 @@ object SharedResourceIdPatch : ResourcePatch() { ControlsLayoutStub = getId(ID, "controls_layout_stub") DarkBackground = getId(ID, "dark_background") DarkSplashAnimation = getId(ID, "dark_splash_animation") + DesignBottomSheet = getId(ID, "design_bottom_sheet") DonationCompanion = getId(LAYOUT, "donation_companion") DrawerContentView = getId(ID, "drawer_content_view") DrawerResults = getId(ID, "drawer_results") @@ -202,6 +207,7 @@ object SharedResourceIdPatch : ResourcePatch() { ReelRightLikeIcon = getId(DRAWABLE, "reel_right_like_icon") ReelTimeBarPlayedColor = getId(COLOR, "reel_time_bar_played_color") ReelVodTimeStampsContainer = getId(ID, "reel_vod_timestamps_container") + ReelWatchPlayer = getId(ID, "reel_watch_player") RelatedChipCloudMargin = getId(LAYOUT, "related_chip_cloud_reduced_margins") RightComment = getId(DRAWABLE, "ic_right_comment_32c") ScrimOverlay = getId(ID, "scrim_overlay") diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/general/ReturnYouTubeDislikePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/general/ReturnYouTubeDislikePatch.kt index b381e3c193..a580ea3ca1 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/general/ReturnYouTubeDislikePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/general/ReturnYouTubeDislikePatch.kt @@ -4,6 +4,7 @@ import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patcher.patch.PatchException import app.revanced.patches.shared.litho.LithoFilterPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.integrations.Constants.COMPONENTS_PATH @@ -18,11 +19,17 @@ import app.revanced.patches.youtube.utils.returnyoutubedislike.shorts.ReturnYouT import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.video.information.VideoInformationPatch import app.revanced.patches.youtube.video.videoid.VideoIdPatch -import app.revanced.util.getTargetIndexWithFieldReferenceTypeOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction 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 @Suppress("unused") object ReturnYouTubeDislikePatch : BaseBytecodePatch( @@ -70,20 +77,43 @@ object ReturnYouTubeDislikePatch : BaseBytecodePatch( TextComponentContextFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val conversionContextFieldIndex = - getTargetIndexWithFieldReferenceTypeOrThrow("Ljava/util/Map;") - 1 + val conversionContextFieldIndex = indexOfFirstInstructionOrThrow { + getReference()?.type == "Ljava/util/Map;" + } - 1 val conversionContextFieldReference = getInstruction(conversionContextFieldIndex).reference - val charSequenceIndex = - getTargetIndexWithFieldReferenceTypeOrThrow("Ljava/util/BitSet;") - 1 - val charSequenceRegister = - getInstruction(charSequenceIndex).registerA - val freeRegister = - getInstruction(charSequenceIndex).registerB + val charSequenceIndex1932 = indexOfFirstInstruction { + getReference()?.type == "Ljava/util/BitSet;" + } - 1 + val charSequenceIndex1933 = indexOfFirstInstruction { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL && + reference?.returnType == "V" && + reference.parameterTypes.firstOrNull() == "Ljava/lang/CharSequence;" + } + + val insertIndex: Int + val charSequenceRegister: Int + + if (charSequenceIndex1932 > -2) { + charSequenceRegister = + getInstruction(charSequenceIndex1932).registerA + insertIndex = charSequenceIndex1932 - 1 + } else if (charSequenceIndex1933 > -1) { + charSequenceRegister = + getInstruction(charSequenceIndex1933).registerD + insertIndex = charSequenceIndex1933 + } else { + throw PatchException("Could not find insert index") + } + + val freeRegister = getInstruction( + indexOfFirstInstructionOrThrow(insertIndex, Opcode.IGET_OBJECT) + ).registerA addInstructions( - charSequenceIndex - 1, """ + insertIndex, """ move-object/from16 v$freeRegister, p0 iget-object v$freeRegister, v$freeRegister, $conversionContextFieldReference invoke-static {v$freeRegister, v$charSequenceRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence; diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/rollingnumber/ReturnYouTubeDislikeRollingNumberPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/rollingnumber/ReturnYouTubeDislikeRollingNumberPatch.kt index 29490b0045..22256003b4 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/rollingnumber/ReturnYouTubeDislikeRollingNumberPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/rollingnumber/ReturnYouTubeDislikeRollingNumberPatch.kt @@ -6,7 +6,6 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.youtube.utils.fingerprints.RollingNumberTextViewAnimationUpdateFingerprint @@ -17,15 +16,17 @@ import app.revanced.patches.youtube.utils.returnyoutubedislike.rollingnumber.fin import app.revanced.patches.youtube.utils.returnyoutubedislike.rollingnumber.fingerprints.RollingNumberMeasureTextParentFingerprint import app.revanced.patches.youtube.utils.returnyoutubedislike.rollingnumber.fingerprints.RollingNumberSetterFingerprint import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.alsoResolve +import app.revanced.util.findMethodOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction -import com.android.tools.smali.dexlib2.iface.reference.Reference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Patch(dependencies = [SettingsPatch::class]) object ReturnYouTubeDislikeRollingNumberPatch : BytecodePatch( @@ -46,127 +47,115 @@ object ReturnYouTubeDislikeRollingNumberPatch : BytecodePatch( * In order to maintain compatibility with YouTube v18.48.39 or previous versions, * This patch is applied only to the version after YouTube v18.49.37 */ - if (SettingsPatch.upward1849) { - - RollingNumberSetterFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val rollingNumberClassIndex = it.scanResult.patternScanResult!!.startIndex - val rollingNumberClassReference = - getInstruction(rollingNumberClassIndex).reference - val rollingNumberClass = - context.findClass(rollingNumberClassReference.toString())!!.mutableClass - - lateinit var charSequenceFieldReference: Reference - - rollingNumberClass.methods.find { method -> method.name == "" } - ?.apply { - val rollingNumberFieldIndex = getTargetIndexOrThrow(Opcode.IPUT_OBJECT) - charSequenceFieldReference = - getInstruction(rollingNumberFieldIndex).reference - } ?: throw PatchException("RollingNumberClass not found!") - - val insertIndex = rollingNumberClassIndex + 1 + if (!SettingsPatch.upward1849) { + return + } - val charSequenceInstanceRegister = - getInstruction(rollingNumberClassIndex).registerA + RollingNumberSetterFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + val rollingNumberClassIndex = it.scanResult.patternScanResult!!.startIndex + val rollingNumberClassReference = + getInstruction(rollingNumberClassIndex).reference.toString() + val rollingNumberConstructorMethod = + context.findMethodOrThrow(rollingNumberClassReference) + val charSequenceFieldReference = with(rollingNumberConstructorMethod) { + getInstruction( + indexOfFirstInstructionOrThrow(Opcode.IPUT_OBJECT) + ).reference + } - val registerCount = implementation!!.registerCount + val insertIndex = rollingNumberClassIndex + 1 + val charSequenceInstanceRegister = + getInstruction(rollingNumberClassIndex).registerA + val registerCount = implementation!!.registerCount - // This register is being overwritten, so it is free to use. - val freeRegister = registerCount - 1 - val conversionContextRegister = registerCount - parameters.size + 1 + // This register is being overwritten, so it is free to use. + val freeRegister = registerCount - 1 + val conversionContextRegister = registerCount - parameters.size + 1 - addInstructions( - insertIndex, """ + addInstructions( + insertIndex, """ iget-object v$freeRegister, v$charSequenceInstanceRegister, $charSequenceFieldReference invoke-static {v$conversionContextRegister, v$freeRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onRollingNumberLoaded(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/String; move-result-object v$freeRegister iput-object v$freeRegister, v$charSequenceInstanceRegister, $charSequenceFieldReference """ - ) - } + ) } + } - // Rolling Number text views use the measured width of the raw string for layout. - // Modify the measure text calculation to include the left drawable separator if needed. - RollingNumberMeasureAnimatedTextFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val endIndex = it.scanResult.patternScanResult!!.endIndex - val measuredTextWidthIndex = endIndex - 2 - val measuredTextWidthRegister = - getInstruction(measuredTextWidthIndex).registerA - - addInstructions( - endIndex + 1, """ - invoke-static {p1, v$measuredTextWidthRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onRollingNumberMeasured(Ljava/lang/String;F)F - move-result v$measuredTextWidthRegister - """ - ) - - val ifGeIndex = getTargetIndexOrThrow(Opcode.IF_GE) - val ifGeInstruction = getInstruction(ifGeIndex) - - removeInstruction(ifGeIndex) - addInstructionsWithLabels( - ifGeIndex, """ - if-ge v${ifGeInstruction.registerA}, v${ifGeInstruction.registerB}, :jump - """, ExternalLabel("jump", getInstruction(endIndex)) - ) - } + // Rolling Number text views use the measured width of the raw string for layout. + // Modify the measure text calculation to include the left drawable separator if needed. + RollingNumberMeasureAnimatedTextFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + val endIndex = it.scanResult.patternScanResult!!.endIndex + val measuredTextWidthIndex = endIndex - 2 + val measuredTextWidthRegister = + getInstruction(measuredTextWidthIndex).registerA + + addInstructions( + endIndex + 1, """ + invoke-static {p1, v$measuredTextWidthRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onRollingNumberMeasured(Ljava/lang/String;F)F + move-result v$measuredTextWidthRegister + """ + ) + + val ifGeIndex = indexOfFirstInstructionOrThrow(opcode = Opcode.IF_GE) + val ifGeInstruction = getInstruction(ifGeIndex) + + removeInstruction(ifGeIndex) + addInstructionsWithLabels( + ifGeIndex, """ + if-ge v${ifGeInstruction.registerA}, v${ifGeInstruction.registerB}, :jump + """, ExternalLabel("jump", getInstruction(endIndex)) + ) } + } - RollingNumberMeasureTextParentFingerprint.resultOrThrow().classDef.let { parentClassDef -> - RollingNumberMeasureStaticLabelFingerprint.resolve(context, parentClassDef) - - // Additional text measurement method. Used if YouTube decides not to animate the likes count - // and sometimes used for initial video load. - RollingNumberMeasureStaticLabelFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val measureTextIndex = it.scanResult.patternScanResult!!.startIndex + 1 - val freeRegister = getInstruction(0).registerA - - addInstructions( - measureTextIndex + 1, """ - move-result v$freeRegister - invoke-static {p1, v$freeRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onRollingNumberMeasured(Ljava/lang/String;F)F - """ - ) - } - } + RollingNumberMeasureStaticLabelFingerprint.alsoResolve( + context, + RollingNumberMeasureTextParentFingerprint + ).let { + it.mutableMethod.apply { + val measureTextIndex = it.scanResult.patternScanResult!!.startIndex + 1 + val freeRegister = getInstruction(0).registerA + + addInstructions( + measureTextIndex + 1, """ + move-result v$freeRegister + invoke-static {p1, v$freeRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onRollingNumberMeasured(Ljava/lang/String;F)F + """ + ) } + } - // The rolling number Span is missing styling since it's initially set as a String. - // Modify the UI text view and use the styled like/dislike Span. - RollingNumberTextViewFingerprint.resultOrThrow().let { parentResult -> - // Initial TextView is set in this method. - val initiallyCreatedTextViewMethod = parentResult.mutableMethod - - // Video less than 24 hours after uploaded, like counts will be updated in real time. - // Whenever like counts are updated, TextView is set in this method. - val realTimeUpdateTextViewMethod = - RollingNumberTextViewAnimationUpdateFingerprint.also { - it.resolve(context, parentResult.classDef) - }.resultOrThrow().mutableMethod - - arrayOf( - initiallyCreatedTextViewMethod, - realTimeUpdateTextViewMethod - ).forEach { insertMethod -> - insertMethod.apply { - val setTextIndex = getTargetIndexWithMethodReferenceNameOrThrow("setText") - val textViewRegister = - getInstruction(setTextIndex).registerC - val textSpanRegister = - getInstruction(setTextIndex).registerD - - addInstructions( - setTextIndex, """ - invoke-static {v$textViewRegister, v$textSpanRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->updateRollingNumber(Landroid/widget/TextView;Ljava/lang/CharSequence;)Ljava/lang/CharSequence; - move-result-object v$textSpanRegister - """ - ) - } + // The rolling number Span is missing styling since it's initially set as a String. + // Modify the UI text view and use the styled like/dislike Span. + arrayOf( + // Initial TextView is set in this method. + RollingNumberTextViewFingerprint + .resultOrThrow(), + + // Video less than 24 hours after uploaded, like counts will be updated in real time. + // Whenever like counts are updated, TextView is set in this method. + RollingNumberTextViewAnimationUpdateFingerprint + .alsoResolve(context, RollingNumberTextViewFingerprint) + ).forEach { fingerprintResult -> + fingerprintResult.mutableMethod.apply { + val setTextIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "setText" } + val textViewRegister = + getInstruction(setTextIndex).registerC + val textSpanRegister = + getInstruction(setTextIndex).registerD + + addInstructions( + setTextIndex, """ + invoke-static {v$textViewRegister, v$textSpanRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->updateRollingNumber(Landroid/widget/TextView;Ljava/lang/CharSequence;)Ljava/lang/CharSequence; + move-result-object v$textSpanRegister + """ + ) } } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/rollingnumber/fingerprints/RollingNumberMeasureAnimatedTextFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/rollingnumber/fingerprints/RollingNumberMeasureAnimatedTextFingerprint.kt index 80ceaec836..a154493325 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/rollingnumber/fingerprints/RollingNumberMeasureAnimatedTextFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/rollingnumber/fingerprints/RollingNumberMeasureAnimatedTextFingerprint.kt @@ -1,12 +1,15 @@ package app.revanced.patches.youtube.utils.returnyoutubedislike.rollingnumber.fingerprints -import app.revanced.util.fingerprint.ReferenceFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.reference.MethodReference /** * This fingerprint is compatible with YouTube v18.30.xx+ */ -internal object RollingNumberMeasureAnimatedTextFingerprint : ReferenceFingerprint( +internal object RollingNumberMeasureAnimatedTextFingerprint : MethodFingerprint( opcodes = listOf( Opcode.INVOKE_VIRTUAL, Opcode.MOVE_RESULT, @@ -14,5 +17,9 @@ internal object RollingNumberMeasureAnimatedTextFingerprint : ReferenceFingerpri Opcode.ADD_INT_LIT8, Opcode.GOTO ), - reference = { "Landroid/text/TextPaint;->measureText([CII)F" } + customFingerprint = { methodDef, _ -> + methodDef.indexOfFirstInstruction { + getReference()?.toString() == "Landroid/text/TextPaint;->measureText([CII)F" + } >= 0 + } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/shorts/ReturnYouTubeDislikeShortsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/shorts/ReturnYouTubeDislikeShortsPatch.kt index cdb6c89465..d174f5ece2 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/shorts/ReturnYouTubeDislikeShortsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/shorts/ReturnYouTubeDislikeShortsPatch.kt @@ -12,14 +12,15 @@ import app.revanced.patches.youtube.utils.fingerprints.TextComponentSpecFingerpr import app.revanced.patches.youtube.utils.integrations.Constants.UTILS_PATH import app.revanced.patches.youtube.utils.returnyoutubedislike.shorts.fingerprints.ShortsTextViewFingerprint import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow -import app.revanced.util.getTargetIndexWithReferenceOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Patch(dependencies = [SettingsPatch::class]) object ReturnYouTubeDislikeShortsPatch : BytecodePatch( @@ -37,9 +38,9 @@ object ReturnYouTubeDislikeShortsPatch : BytecodePatch( val startIndex = it.scanResult.patternScanResult!!.startIndex val isDisLikesBooleanIndex = - getTargetIndexReversedOrThrow(startIndex, Opcode.IGET_BOOLEAN) + indexOfFirstInstructionReversedOrThrow(startIndex, Opcode.IGET_BOOLEAN) val textViewFieldIndex = - getTargetIndexReversedOrThrow(startIndex, Opcode.IGET_OBJECT) + indexOfFirstInstructionReversedOrThrow(startIndex, Opcode.IGET_OBJECT) // If the field is true, the TextView is for a dislike button. val isDisLikesBooleanReference = @@ -51,7 +52,7 @@ object ReturnYouTubeDislikeShortsPatch : BytecodePatch( // Check if the hooked TextView object is that of the dislike button. // If RYD is disabled, or the TextView object is not that of the dislike button, the execution flow is not interrupted. // Otherwise, the TextView object is modified, and the execution flow is interrupted to prevent it from being changed afterward. - val insertIndex = getTargetIndexOrThrow(Opcode.CHECK_CAST) + 1 + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.CHECK_CAST) + 1 addInstructionsWithLabels( insertIndex, """ @@ -70,29 +71,30 @@ object ReturnYouTubeDislikeShortsPatch : BytecodePatch( } } - if (SettingsPatch.upward1834) { - TextComponentSpecFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - val insertIndex = - getTargetIndexWithReferenceOrThrow("Landroid/text/SpannableString;->valueOf(Ljava/lang/CharSequence;)Landroid/text/SpannableString;") - - val charSequenceRegister = - getInstruction(insertIndex).registerC - val conversionContextRegister = - getInstruction(0).registerA - - val replaceReference = - getInstruction(insertIndex).reference + if (!SettingsPatch.upward1834) { + return + } - addInstructions( - insertIndex + 1, """ - invoke-static {v$conversionContextRegister, v$charSequenceRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onCharSequenceLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence; - move-result-object v$charSequenceRegister - invoke-static {v$charSequenceRegister}, $replaceReference - """ - ) - removeInstruction(insertIndex) + TextComponentSpecFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + val insertIndex = indexOfFirstInstructionOrThrow { + getReference()?.toString() == "Landroid/text/SpannableString;->valueOf(Ljava/lang/CharSequence;)Landroid/text/SpannableString;" } + val charSequenceRegister = + getInstruction(insertIndex).registerC + val conversionContextRegister = + getInstruction(0).registerA + val replaceReference = + getInstruction(insertIndex).reference + + addInstructions( + insertIndex + 1, """ + invoke-static {v$conversionContextRegister, v$charSequenceRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onCharSequenceLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence; + move-result-object v$charSequenceRegister + invoke-static {v$charSequenceRegister}, $replaceReference + """ + ) + removeInstruction(insertIndex) } } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt index 26c3649ac3..451cd2b863 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt @@ -50,23 +50,27 @@ object ResourceUtils { } fun ResourceContext.addEntryValues( - path: String, - speedEntryValues: String, - attributeName: String + attributeName: String, + attributeValue: String, + path: String = "res/values/arrays.xml", + prepend: Boolean = true, ) { xmlEditor[path].use { with(it.file) { val resourcesNode = getElementsByTagName("resources").item(0) as Element val newElement: Element = createElement("item") - for (i in 0 until resourcesNode.childNodes.length) { val node = resourcesNode.childNodes.item(i) as? Element ?: continue if (node.getAttribute("name") == attributeName) { - newElement.appendChild(createTextNode(speedEntryValues)) + newElement.appendChild(createTextNode(attributeValue)) - node.appendChild(newElement) + if (prepend) { + node.appendChild(newElement) + } else { + node.insertBefore(newElement, node.firstChild) + } } } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt index 6568cc42a1..385c536a6c 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt @@ -6,7 +6,6 @@ import app.revanced.patches.shared.elements.StringsElementsUtils.removeStringsEl import app.revanced.patches.shared.mapping.ResourceMappingPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.fix.cairo.CairoSettingsPatch -import app.revanced.patches.youtube.utils.fix.litho.ConversionContextObfuscationPatch import app.revanced.patches.youtube.utils.integrations.IntegrationsPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference @@ -36,8 +35,6 @@ object SettingsPatch : BaseResourcePatch( SharedResourceIdPatch::class, SettingsBytecodePatch::class, CairoSettingsPatch::class, - // Add dependency to the settings patch as a limitation of patch implementation. - ConversionContextObfuscationPatch::class, ), compatiblePackages = COMPATIBLE_PACKAGE, requiresIntegrations = true @@ -319,7 +316,13 @@ object SettingsPatch : BaseResourcePatch( updatePatchStatus(patch.name!!) } + private val patchList = ArrayList() + internal fun updatePatchStatus(patchName: String) { + patchList.add(patchName) contexts.updatePatchStatus(patchName) } + + internal fun containsPatch(patchName: String) = + patchList.contains(patchName) } diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/SponsorBlockBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/SponsorBlockBytecodePatch.kt index 3012c270eb..9bda4f425d 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/SponsorBlockBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/SponsorBlockBytecodePatch.kt @@ -19,11 +19,11 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.Inset import app.revanced.patches.youtube.utils.sponsorblock.fingerprints.RectangleFieldInvalidatorFingerprint import app.revanced.patches.youtube.utils.sponsorblock.fingerprints.SegmentPlaybackControllerFingerprint import app.revanced.patches.youtube.video.information.VideoInformationPatch -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexWithFieldReferenceTypeReversedOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow -import app.revanced.util.getTargetIndexWithMethodReferenceNameReversedOrThrow -import app.revanced.util.getWideLiteralInstructionIndex +import app.revanced.util.alsoResolve +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.resultOrThrow import app.revanced.util.updatePatchStatus import com.android.tools.smali.dexlib2.Opcode @@ -31,6 +31,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Patch( dependencies = [ @@ -74,13 +75,11 @@ object SponsorBlockBytecodePatch : BytecodePatch( ) } - val seekBarClass = SeekbarFingerprint.resultOrThrow().mutableClass - SeekbarOnDrawFingerprint.resolve(context, seekBarClass) - RectangleFieldInvalidatorFingerprint.resolve(context, seekBarClass) - - SeekbarOnDrawFingerprint.resultOrThrow().mutableMethod.apply { + SeekbarOnDrawFingerprint.alsoResolve( + context, SeekbarFingerprint + ).mutableMethod.apply { // Get left and right of seekbar rectangle - val moveObjectIndex = getTargetIndexOrThrow(Opcode.MOVE_OBJECT_FROM16) + val moveObjectIndex = indexOfFirstInstructionOrThrow(opcode = Opcode.MOVE_OBJECT_FROM16) addInstruction( moveObjectIndex + 1, @@ -89,7 +88,9 @@ object SponsorBlockBytecodePatch : BytecodePatch( ) // Set seekbar thickness - val roundIndex = getTargetIndexWithMethodReferenceNameOrThrow("round") + 1 + val roundIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "round" + } + 1 val roundRegister = getInstruction(roundIndex).registerA addInstruction( @@ -99,7 +100,9 @@ object SponsorBlockBytecodePatch : BytecodePatch( ) // Draw segment - val drawCircleIndex = getTargetIndexWithMethodReferenceNameReversedOrThrow("drawCircle") + val drawCircleIndex = indexOfFirstInstructionReversedOrThrow { + getReference()?.name == "drawCircle" + } val drawCircleInstruction = getInstruction(drawCircleIndex) addInstruction( drawCircleIndex, @@ -116,7 +119,9 @@ object SponsorBlockBytecodePatch : BytecodePatch( // Append timestamp TotalTimeFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = getTargetIndexWithMethodReferenceNameOrThrow("getString") + 1 + val targetIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "getString" + } + 1 val targetRegister = getInstruction(targetIndex).registerA addInstructions( @@ -131,8 +136,9 @@ object SponsorBlockBytecodePatch : BytecodePatch( // Initialize the SponsorBlock view YouTubeControlsOverlayFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = getWideLiteralInstructionIndex(InsetOverlayViewLayout) - val checkCastIndex = getTargetIndexOrThrow(targetIndex, Opcode.CHECK_CAST) + val targetIndex = + indexOfFirstWideLiteralInstructionValueOrThrow(InsetOverlayViewLayout) + val checkCastIndex = indexOfFirstInstructionOrThrow(targetIndex, Opcode.CHECK_CAST) val targetRegister = getInstruction(checkCastIndex).registerA @@ -144,14 +150,15 @@ object SponsorBlockBytecodePatch : BytecodePatch( } // Replace strings - RectangleFieldInvalidatorFingerprint.resultOrThrow().let { result -> + RectangleFieldInvalidatorFingerprint.alsoResolve( + context, SeekbarFingerprint + ).let { result -> result.mutableMethod.apply { val invalidateIndex = - getTargetIndexWithMethodReferenceNameReversedOrThrow("invalidate") - val rectangleIndex = getTargetIndexWithFieldReferenceTypeReversedOrThrow( - invalidateIndex + 1, - "Landroid/graphics/Rect;" - ) + RectangleFieldInvalidatorFingerprint.indexOfInvalidateInstruction(this) + val rectangleIndex = indexOfFirstInstructionReversedOrThrow(invalidateIndex + 1) { + getReference()?.type == "Landroid/graphics/Rect;" + } val rectangleFieldName = (getInstruction(rectangleIndex).reference as FieldReference).name diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/fingerprints/RectangleFieldInvalidatorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/fingerprints/RectangleFieldInvalidatorFingerprint.kt index 598513e7ce..89415c67a3 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/fingerprints/RectangleFieldInvalidatorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/fingerprints/RectangleFieldInvalidatorFingerprint.kt @@ -1,9 +1,21 @@ package app.revanced.patches.youtube.utils.sponsorblock.fingerprints -import app.revanced.util.fingerprint.MethodReferenceNameFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.utils.sponsorblock.fingerprints.RectangleFieldInvalidatorFingerprint.indexOfInvalidateInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversed +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.reference.MethodReference -internal object RectangleFieldInvalidatorFingerprint : MethodReferenceNameFingerprint( +internal object RectangleFieldInvalidatorFingerprint : MethodFingerprint( returnType = "V", parameters = emptyList(), - reference = { "invalidate" } -) + customFingerprint = { methodDef, _ -> + indexOfInvalidateInstruction(methodDef) >= 0 + } +) { + fun indexOfInvalidateInstruction(methodDef: Method) = + methodDef.indexOfFirstInstructionReversed { + getReference()?.name == "invalidate" + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/trackingurlhook/TrackingUrlHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/trackingurlhook/TrackingUrlHookPatch.kt index b2e2464b65..5012f0c09d 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/trackingurlhook/TrackingUrlHookPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/trackingurlhook/TrackingUrlHookPatch.kt @@ -6,9 +6,12 @@ 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.trackingurlhook.fingerprints.TrackingUrlModelFingerprint -import app.revanced.util.getTargetIndexWithMethodReferenceNameOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference object TrackingUrlHookPatch : BytecodePatch( setOf(TrackingUrlModelFingerprint) @@ -22,7 +25,10 @@ object TrackingUrlHookPatch : BytecodePatch( internal fun hookTrackingUrl( descriptor: String ) = trackingUrlMethod.apply { - val targetIndex = getTargetIndexWithMethodReferenceNameOrThrow("parse") + 1 + val targetIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_STATIC && + getReference()?.name == "parse" + } + 1 val targetRegister = getInstruction(targetIndex).registerA var smaliInstruction = "invoke-static {v$targetRegister}, $descriptor" diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt index 414f8ef623..bda19a0c36 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt @@ -10,11 +10,13 @@ import app.revanced.patcher.fingerprint.MethodFingerprintResult import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.Patch -import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.smali.toInstructions import app.revanced.patches.shared.fingerprints.MdxPlayerDirectorSetVideoStageFingerprint +import app.revanced.patches.shared.fingerprints.VideoLengthFingerprint +import app.revanced.patches.youtube.utils.PlayerResponseModelUtils.PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR +import app.revanced.patches.youtube.utils.PlayerResponseModelUtils.indexOfPlayerResponseModelInstruction import app.revanced.patches.youtube.utils.fingerprints.VideoEndFingerprint import app.revanced.patches.youtube.utils.integrations.Constants.SHARED_PATH import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch @@ -29,20 +31,18 @@ import app.revanced.patches.youtube.video.information.fingerprints.SeekRelativeF import app.revanced.patches.youtube.video.information.fingerprints.VideoIdFingerprint import app.revanced.patches.youtube.video.information.fingerprints.VideoIdFingerprintBackgroundPlay import app.revanced.patches.youtube.video.information.fingerprints.VideoIdFingerprintShorts -import app.revanced.patches.youtube.video.information.fingerprints.VideoLengthFingerprint import app.revanced.patches.youtube.video.information.fingerprints.VideoQualityListFingerprint import app.revanced.patches.youtube.video.information.fingerprints.VideoQualityTextFingerprint import app.revanced.patches.youtube.video.information.fingerprints.VideoTitleFingerprint import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch import app.revanced.patches.youtube.video.videoid.VideoIdPatch -import app.revanced.util.addFieldAndInstructions +import app.revanced.util.addStaticFieldToIntegration import app.revanced.util.alsoResolve import app.revanced.util.getReference -import app.revanced.util.getTargetIndexOrThrow -import app.revanced.util.getTargetIndexReversedOrThrow import app.revanced.util.getWalkerMethod -import app.revanced.util.getWideLiteralInstructionIndex import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstWideLiteralInstructionValueOrThrow import app.revanced.util.resultOrThrow import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -88,9 +88,6 @@ object VideoInformationPatch : BytecodePatch( private const val INTEGRATIONS_CLASS_DESCRIPTOR = "$SHARED_PATH/VideoInformation;" - private const val PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR = - "Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;" - private const val REGISTER_PLAYER_RESPONSE_MODEL = 8 private const val REGISTER_CHANNEL_ID = 0 @@ -123,7 +120,6 @@ object VideoInformationPatch : BytecodePatch( private var seekSourceMethodName = "" private var seekRelativeSourceMethodName = "" - private lateinit var videoInformationMutableClass: MutableClass private lateinit var context: BytecodeContext private lateinit var playerConstructorMethod: MutableMethod @@ -160,7 +156,7 @@ object VideoInformationPatch : BytecodePatch( 4, """ # first enum (field a) is SEEK_SOURCE_UNKNOWN sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType - invoke-virtual {p0, p1, p2, v0}, ${definingClass}->$seekMethodName(J$seekSourceEnumType)Z + invoke-virtual {p0, p1, p2, v0}, $definingClass->$seekMethodName(J$seekSourceEnumType)Z move-result p1 return p1 """.toInstructions(), @@ -181,21 +177,18 @@ object VideoInformationPatch : BytecodePatch( return v0 """ - videoInformationMutableClass.addFieldAndInstructions( - context, + context.addStaticFieldToIntegration( + INTEGRATIONS_CLASS_DESCRIPTOR, methodName, fieldName, definingClass, - smaliInstructions, - true + smaliInstructions ) } } override fun execute(context: BytecodeContext) { this.context = context - videoInformationMutableClass = - context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR)!!.mutableClass VideoEndFingerprint.resultOrThrow().let { it.mutableMethod.apply { @@ -233,9 +226,12 @@ object VideoInformationPatch : BytecodePatch( "videoInformationClass" ) - val literalIndex = getWideLiteralInstructionIndex(45368273) + val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow(45368273) val walkerIndex = - getTargetIndexReversedOrThrow(literalIndex, Opcode.INVOKE_VIRTUAL_RANGE) + indexOfFirstInstructionReversedOrThrow( + literalIndex, + Opcode.INVOKE_VIRTUAL_RANGE + ) videoEndMethod = getWalkerMethod(context, walkerIndex) @@ -275,19 +271,20 @@ object VideoInformationPatch : BytecodePatch( /** * Set current video information */ - channelIdMethodCall = ChannelIdFingerprint.getMethodName("Ljava/lang/String;") - channelNameMethodCall = ChannelNameFingerprint.getMethodName("Ljava/lang/String;") - videoIdMethodCall = VideoIdFingerprint.getMethodName("Ljava/lang/String;") - videoTitleMethodCall = VideoTitleFingerprint.getMethodName("Ljava/lang/String;") - videoLengthMethodCall = VideoLengthFingerprint.getMethodName("J") - videoIsLiveMethodCall = ChannelIdFingerprint.getMethodName("Z") + channelIdMethodCall = + ChannelIdFingerprint.getPlayerResponseInstruction("Ljava/lang/String;") + channelNameMethodCall = + ChannelNameFingerprint.getPlayerResponseInstruction("Ljava/lang/String;") + videoIdMethodCall = VideoIdFingerprint.getPlayerResponseInstruction("Ljava/lang/String;") + videoTitleMethodCall = + VideoTitleFingerprint.getPlayerResponseInstruction("Ljava/lang/String;") + videoLengthMethodCall = VideoLengthFingerprint.getPlayerResponseInstruction("J") + videoIsLiveMethodCall = ChannelIdFingerprint.getPlayerResponseInstruction("Z") PlaybackInitializationFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = indexOfFirstInstructionOrThrow { - opcode == Opcode.INVOKE_DIRECT - && getReference()?.returnType == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR - } + 1 + val targetIndex = + PlaybackInitializationFingerprint.indexOfPlayerResponseModelInstruction(this) + 1 val targetRegister = getInstruction(targetIndex).registerA addInstruction( @@ -304,10 +301,7 @@ object VideoInformationPatch : BytecodePatch( VideoIdFingerprintBackgroundPlay.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = indexOfFirstInstructionOrThrow { - opcode == Opcode.INVOKE_INTERFACE - && getReference()?.definingClass == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR - } + val targetIndex = indexOfPlayerResponseModelInstruction(this) val targetRegister = getInstruction(targetIndex).registerC addInstruction( @@ -322,10 +316,7 @@ object VideoInformationPatch : BytecodePatch( VideoIdFingerprintShorts.resultOrThrow().let { it.mutableMethod.apply { - val targetIndex = indexOfFirstInstructionOrThrow { - opcode == Opcode.INVOKE_INTERFACE - && getReference()?.definingClass == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR - } + val targetIndex = indexOfPlayerResponseModelInstruction(this) val targetRegister = getInstruction(targetIndex).registerC addInstruction( @@ -370,10 +361,11 @@ object VideoInformationPatch : BytecodePatch( OnPlaybackSpeedItemClickFingerprint.resultOrThrow().let { it.mutableMethod.apply { speedSelectionInsertMethod = this - val speedSelectionValueInstructionIndex = getTargetIndexOrThrow(Opcode.IGET) + val speedSelectionValueInstructionIndex = + indexOfFirstInstructionOrThrow(Opcode.IGET) val setPlaybackSpeedContainerClassFieldIndex = - getTargetIndexReversedOrThrow( + indexOfFirstInstructionReversedOrThrow( speedSelectionValueInstructionIndex, Opcode.IGET_OBJECT ) @@ -452,8 +444,8 @@ object VideoInformationPatch : BytecodePatch( return-void """ - videoInformationMutableClass.addFieldAndInstructions( - context, + context.addStaticFieldToIntegration( + INTEGRATIONS_CLASS_DESCRIPTOR, "overridePlaybackSpeed", "playbackSpeedClass", playbackSpeedClass, @@ -493,13 +485,12 @@ object VideoInformationPatch : BytecodePatch( return-void """ - videoInformationMutableClass.addFieldAndInstructions( - context, + context.addStaticFieldToIntegration( + INTEGRATIONS_CLASS_DESCRIPTOR, "overrideVideoQuality", "videoQualityClass", videoQualityClass, - smaliInstructions, - true + smaliInstructions ) } @@ -557,16 +548,18 @@ object VideoInformationPatch : BytecodePatch( "invoke-static { p1, p2 }, $targetMethodClass->$targetMethodName(J)V" ) - private fun MethodFingerprint.getMethodName(returnType: String): String { + private fun MethodFingerprint.getPlayerResponseInstruction(returnType: String): String { resultOrThrow().mutableMethod.apply { - val targetIndex = indexOfFirstInstructionOrThrow { - opcode == Opcode.INVOKE_INTERFACE - && getReference()?.definingClass == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR - && getReference()?.returnType == returnType - } - val targetReference = getInstruction(targetIndex).reference + val targetReference = getInstruction( + indexOfFirstInstructionOrThrow { + val reference = getReference() + opcode == Opcode.INVOKE_INTERFACE && + reference?.definingClass == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR && + reference.returnType == returnType + } + ).reference - return "invoke-interface {v${REGISTER_PLAYER_RESPONSE_MODEL}}, $targetReference" + return "invoke-interface {v$REGISTER_PLAYER_RESPONSE_MODEL}, $targetReference" } } @@ -586,7 +579,7 @@ object VideoInformationPatch : BytecodePatch( annotations, null, ImmutableMethodImplementation( - 9, """ + REGISTER_PLAYER_RESPONSE_MODEL + 1, """ $channelIdMethodCall move-result-object v$REGISTER_CHANNEL_ID $channelNameMethodCall @@ -618,7 +611,7 @@ object VideoInformationPatch : BytecodePatch( insert( index, - "v${REGISTER_CHANNEL_ID} .. v${REGISTER_VIDEO_IS_LIVE}", + "v$REGISTER_CHANNEL_ID .. v$REGISTER_VIDEO_IS_LIVE", descriptor ) } @@ -632,7 +625,7 @@ object VideoInformationPatch : BytecodePatch( insert( index, - "v${REGISTER_CHANNEL_ID} .. v${REGISTER_VIDEO_IS_LIVE}", + "v$REGISTER_CHANNEL_ID .. v$REGISTER_VIDEO_IS_LIVE", descriptor ) } @@ -646,7 +639,7 @@ object VideoInformationPatch : BytecodePatch( insert( index, - "v${REGISTER_CHANNEL_ID} .. v${REGISTER_VIDEO_IS_LIVE}", + "v$REGISTER_CHANNEL_ID .. v$REGISTER_VIDEO_IS_LIVE", descriptor ) } diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/OnPlaybackSpeedItemClickFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/OnPlaybackSpeedItemClickFingerprint.kt index 58bd65e74c..3655b5282a 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/OnPlaybackSpeedItemClickFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/OnPlaybackSpeedItemClickFingerprint.kt @@ -2,7 +2,9 @@ package app.revanced.patches.youtube.video.information.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.utils.PlayerResponseModelUtils.PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.reference.FieldReference @@ -12,9 +14,10 @@ internal object OnPlaybackSpeedItemClickFingerprint : MethodFingerprint( returnType = "V", parameters = listOf("Landroid/widget/AdapterView;", "Landroid/view/View;", "I", "J"), customFingerprint = { methodDef, _ -> - methodDef.name == "onItemClick" && methodDef.implementation?.instructions?.find { - it.opcode == Opcode.IGET_OBJECT && - it.getReference()!!.type == "Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;" - } != null + methodDef.name == "onItemClick" && + methodDef.indexOfFirstInstruction { + opcode == Opcode.IGET_OBJECT && + getReference()?.type == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR + } >= 0 } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/PlaybackInitializationFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/PlaybackInitializationFingerprint.kt index 39b05a5d20..d5bbd3cd67 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/PlaybackInitializationFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/PlaybackInitializationFingerprint.kt @@ -2,11 +2,27 @@ package app.revanced.patches.youtube.video.information.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.utils.PlayerResponseModelUtils.PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR +import app.revanced.patches.youtube.video.information.fingerprints.PlaybackInitializationFingerprint.indexOfPlayerResponseModelInstruction +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction 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 object PlaybackInitializationFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = emptyList(), - strings = listOf("play() called when the player wasn\'t loaded.") -) + strings = listOf("play() called when the player wasn\'t loaded."), + customFingerprint = { methodDef, _ -> + indexOfPlayerResponseModelInstruction(methodDef) >= 0 + } +) { + fun indexOfPlayerResponseModelInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + opcode == Opcode.INVOKE_DIRECT && + getReference()?.returnType == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/VideoIdFingerprintBackgroundPlay.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/VideoIdFingerprintBackgroundPlay.kt index 0b360f7cd1..750929252a 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/VideoIdFingerprintBackgroundPlay.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/VideoIdFingerprintBackgroundPlay.kt @@ -1,8 +1,7 @@ package app.revanced.patches.youtube.video.information.fingerprints -import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint -import com.android.tools.smali.dexlib2.AccessFlags +import app.revanced.patches.youtube.utils.PlayerResponseModelUtils.indexOfPlayerResponseModelInstruction import com.android.tools.smali.dexlib2.Opcode /** @@ -10,7 +9,6 @@ import com.android.tools.smali.dexlib2.Opcode */ internal object VideoIdFingerprintBackgroundPlay : MethodFingerprint( returnType = "V", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.DECLARED_SYNCHRONIZED, parameters = listOf("L"), opcodes = listOf( Opcode.IF_EQZ, @@ -23,6 +21,9 @@ internal object VideoIdFingerprintBackgroundPlay : MethodFingerprint( Opcode.RETURN_VOID ), customFingerprint = { methodDef, classDef -> - methodDef.name == "l" && classDef.methods.count() == 17 + methodDef.name == "l" && + classDef.methods.count() == 17 && + methodDef.implementation != null && + indexOfPlayerResponseModelInstruction(methodDef) >= 0 } ) diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/VideoIdFingerprintShorts.kt b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/VideoIdFingerprintShorts.kt index 0f34edbb4a..0e419ab536 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/VideoIdFingerprintShorts.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/information/fingerprints/VideoIdFingerprintShorts.kt @@ -1,9 +1,12 @@ package app.revanced.patches.youtube.video.information.fingerprints import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.util.containsWideLiteralInstructionIndex -import app.revanced.util.getTargetIndexWithFieldReferenceName +import app.revanced.patches.youtube.utils.PlayerResponseModelUtils.PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR +import app.revanced.util.containsWideLiteralInstructionValue +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.reference.FieldReference /** * This fingerprint is compatible with all versions of YouTube starting from v18.29.38 to supported versions. @@ -12,15 +15,17 @@ import com.android.tools.smali.dexlib2.Opcode */ internal object VideoIdFingerprintShorts : MethodFingerprint( returnType = "V", - parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"), + parameters = listOf(PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR), opcodes = listOf( Opcode.INVOKE_INTERFACE, Opcode.MOVE_RESULT_OBJECT ), customFingerprint = custom@{ methodDef, _ -> - if (methodDef.containsWideLiteralInstructionIndex(45365621)) + if (methodDef.containsWideLiteralInstructionValue(45365621)) return@custom true - methodDef.getTargetIndexWithFieldReferenceName("reelWatchEndpoint") >= 0 + methodDef.indexOfFirstInstruction { + getReference()?.name == "reelWatchEndpoint" + } >= 0 } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt index d532cf245e..6a17f77c82 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt @@ -30,12 +30,12 @@ import app.revanced.patches.youtube.video.playback.fingerprints.PlaybackSpeedCha import app.revanced.patches.youtube.video.playback.fingerprints.PlaybackSpeedInitializeFingerprint import app.revanced.patches.youtube.video.playback.fingerprints.QualityChangedFromRecyclerViewFingerprint import app.revanced.patches.youtube.video.playback.fingerprints.QualitySetterFingerprint +import app.revanced.patches.youtube.video.playback.fingerprints.VP9CapabilityFingerprint import app.revanced.patches.youtube.video.videoid.VideoIdPatch import app.revanced.util.getReference -import app.revanced.util.getStringInstructionIndex -import app.revanced.util.getTargetIndexOrThrow import app.revanced.util.getWalkerMethod import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstStringInstructionOrThrow import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow import app.revanced.util.updatePatchStatus @@ -72,7 +72,8 @@ object VideoPlaybackPatch : BaseBytecodePatch( QualityChangedFromRecyclerViewFingerprint, QualityMenuViewInflateFingerprint, QualitySetterFingerprint, - VideoEndFingerprint + VideoEndFingerprint, + VP9CapabilityFingerprint ) ) { private const val PLAYBACK_SPEED_MENU_FILTER_CLASS_DESCRIPTOR = @@ -81,6 +82,8 @@ object VideoPlaybackPatch : BaseBytecodePatch( "$COMPONENTS_PATH/VideoQualityMenuFilter;" private const val INTEGRATIONS_AV1_CODEC_CLASS_DESCRIPTOR = "$VIDEO_PATH/AV1CodecPatch;" + private const val INTEGRATIONS_VP9_CODEC_CLASS_DESCRIPTOR = + "$VIDEO_PATH/VP9CodecPatch;" private const val INTEGRATIONS_CUSTOM_PLAYBACK_SPEED_CLASS_DESCRIPTOR = "$VIDEO_PATH/CustomPlaybackSpeedPatch;" private const val INTEGRATIONS_HDR_VIDEO_CLASS_DESCRIPTOR = @@ -108,7 +111,8 @@ object VideoPlaybackPatch : BaseBytecodePatch( // region patch for disable HDR video HDRCapabilityFingerprint.resultOrThrow().mutableMethod.apply { - val stringIndex = getStringInstructionIndex("av1_profile_main_10_hdr_10_plus_supported") + val stringIndex = + indexOfFirstStringInstructionOrThrow("av1_profile_main_10_hdr_10_plus_supported") val walkerIndex = indexOfFirstInstructionOrThrow(stringIndex) { val reference = getReference() reference?.parameterTypes == listOf("I", "Landroid/view/Display;") @@ -145,7 +149,8 @@ object VideoPlaybackPatch : BaseBytecodePatch( speedSelectionInsertMethod ).forEach { it.apply { - val speedSelectionValueInstructionIndex = getTargetIndexOrThrow(Opcode.IGET) + val speedSelectionValueInstructionIndex = + indexOfFirstInstructionOrThrow(Opcode.IGET) val speedSelectionValueRegister = getInstruction(speedSelectionValueInstructionIndex).registerA @@ -223,7 +228,7 @@ object VideoPlaybackPatch : BaseBytecodePatch( QualityMenuViewInflateFingerprint.resultOrThrow().let { it.mutableMethod.apply { - val insertIndex = getTargetIndexOrThrow(Opcode.CHECK_CAST) + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.CHECK_CAST) val insertRegister = getInstruction(insertIndex).registerA addInstruction( @@ -236,7 +241,7 @@ object VideoPlaybackPatch : BaseBytecodePatch( it.mutableClass.methods.find { method -> method.name == "onItemClick" } onItemClickMethod?.apply { - val insertIndex = getTargetIndexOrThrow(Opcode.IGET_OBJECT) + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT) val insertRegister = getInstruction(insertIndex).registerA val jumpIndex = indexOfFirstInstructionOrThrow { @@ -287,12 +292,12 @@ object VideoPlaybackPatch : BaseBytecodePatch( AV1CodecFingerprint.result?.let { it.mutableMethod.apply { - val insertIndex = getStringInstructionIndex("video/av01") + val insertIndex = indexOfFirstStringInstructionOrThrow("video/av01") val insertRegister = getInstruction(insertIndex).registerA addInstructions( insertIndex + 1, """ - invoke-static {v$insertRegister}, $INTEGRATIONS_AV1_CODEC_CLASS_DESCRIPTOR->replaceCodec(Ljava/lang/String;)Ljava/lang/String; + invoke-static/range {v$insertRegister .. v$insertRegister}, $INTEGRATIONS_AV1_CODEC_CLASS_DESCRIPTOR->replaceCodec(Ljava/lang/String;)Ljava/lang/String; move-result-object v$insertRegister """ ) @@ -326,6 +331,21 @@ object VideoPlaybackPatch : BaseBytecodePatch( // endregion + // region patch for disable VP9 codec + + VP9CapabilityFingerprint.resultOrThrow().mutableMethod.apply { + addInstructionsWithLabels( + 0, """ + invoke-static {}, $INTEGRATIONS_VP9_CODEC_CLASS_DESCRIPTOR->disableVP9Codec()Z + move-result v0 + if-nez v0, :default + return v0 + """, ExternalLabel("default", getInstruction(0)) + ) + } + + // endregion + /** * Add settings */ diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/playback/fingerprints/AV1CodecFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/playback/fingerprints/AV1CodecFingerprint.kt index 30442651b2..88e28d7fa1 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/video/playback/fingerprints/AV1CodecFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/video/playback/fingerprints/AV1CodecFingerprint.kt @@ -2,7 +2,7 @@ package app.revanced.patches.youtube.video.playback.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.AccessFlags internal object AV1CodecFingerprint : MethodFingerprint( @@ -13,6 +13,6 @@ internal object AV1CodecFingerprint : MethodFingerprint( if (methodDef.returnType == "Ljava/util/List;") return@handler false - methodDef.containsWideLiteralInstructionIndex(1987076931) + methodDef.containsWideLiteralInstructionValue(1987076931) } ) diff --git a/src/main/kotlin/app/revanced/patches/youtube/video/playback/fingerprints/VP9CapabilityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/video/playback/fingerprints/VP9CapabilityFingerprint.kt new file mode 100644 index 0000000000..e904b93125 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/video/playback/fingerprints/VP9CapabilityFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.video.playback.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object VP9CapabilityFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Z", + strings = listOf( + "vp9_supported", + "video/x-vnd.on2.vp9" + ) +) diff --git a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index 2c4f5d906e..46adf69e01 100644 --- a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -16,6 +16,7 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableField import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.util.fingerprint.MultiMethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.Method @@ -25,16 +26,21 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i -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.Reference +import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.immutable.ImmutableField import com.android.tools.smali.dexlib2.util.MethodUtil const val REGISTER_TEMPLATE_REPLACEMENT: String = "REGISTER_INDEX" +fun MethodFingerprint.isDeprecated() = + javaClass.annotations[0].toString().contains("Deprecated") + fun MethodFingerprint.resultOrThrow() = result ?: throw exception +fun MultiMethodFingerprint.resultOrThrow() = result.ifEmpty { throw exception } + /** * The [PatchException] of failing to resolve a [MethodFingerprint]. * @@ -43,6 +49,9 @@ fun MethodFingerprint.resultOrThrow() = result ?: throw exception val MethodFingerprint.exception get() = PatchException("Failed to resolve ${this.javaClass.simpleName}") +val MultiMethodFingerprint.exception + get() = PatchException("Failed to resolve ${this.javaClass.simpleName}") + fun MethodFingerprint.alsoResolve(context: BytecodeContext, fingerprint: MethodFingerprint) = also { resolve(context, fingerprint.resultOrThrow().classDef) }.resultOrThrow() @@ -96,45 +105,51 @@ fun MutableMethod.injectHideViewCall( "invoke-static { v$viewRegister }, $classDescriptor->$targetMethod(Landroid/view/View;)V" ) -fun MethodFingerprint.literalInstructionBooleanHook( +fun MethodFingerprint.injectLiteralInstructionBooleanCall( literal: Int, descriptor: String -) = literalInstructionBooleanHook(literal.toLong(), descriptor) +) = injectLiteralInstructionBooleanCall(literal.toLong(), descriptor) -fun MethodFingerprint.literalInstructionBooleanHook( +fun MethodFingerprint.injectLiteralInstructionBooleanCall( literal: Long, descriptor: String ) { resultOrThrow().mutableMethod.apply { - val literalIndex = getWideLiteralInstructionIndex(literal) - val targetIndex = getTargetIndex(literalIndex, Opcode.MOVE_RESULT) + val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow(literal) + val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT) val targetRegister = getInstruction(targetIndex).registerA - val smaliInstruction = if (descriptor.endsWith("(Z)Z")) - "invoke-static {v$targetRegister}, $descriptor" - else - "invoke-static {}, $descriptor" - - addInstructions( - targetIndex + 1, """ - $smaliInstruction + val smaliInstruction = + if (descriptor.startsWith("0x")) """ + const/16 v$targetRegister, $descriptor + """ + else if (descriptor.endsWith("(Z)Z")) """ + invoke-static {v$targetRegister}, $descriptor + move-result v$targetRegister + """ + else """ + invoke-static {}, $descriptor move-result v$targetRegister """ + + addInstructions( + targetIndex + 1, + smaliInstruction ) } } -fun MethodFingerprint.literalInstructionViewHook( +fun MethodFingerprint.injectLiteralInstructionViewCall( literal: Long, smaliInstruction: String -) = resultOrThrow().mutableMethod.literalInstructionViewHook(literal, smaliInstruction) +) = resultOrThrow().mutableMethod.injectLiteralInstructionViewCall(literal, smaliInstruction) -fun MutableMethod.literalInstructionViewHook( +fun MutableMethod.injectLiteralInstructionViewCall( literal: Long, smaliInstruction: String ) { - val literalIndex = getWideLiteralInstructionIndex(literal) - val targetIndex = getTargetIndex(literalIndex, Opcode.MOVE_RESULT_OBJECT) + val literalIndex = indexOfFirstWideLiteralInstructionValueOrThrow(literal) + val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT_OBJECT) val targetRegister = getInstruction(targetIndex).registerA.toString() addInstructions( @@ -143,7 +158,7 @@ fun MutableMethod.literalInstructionViewHook( ) } -fun BytecodeContext.literalInstructionViewHook( +fun BytecodeContext.injectLiteralInstructionViewCall( literal: Long, smaliInstruction: String ) { @@ -160,14 +175,14 @@ fun BytecodeContext.literalInstructionViewHook( context.proxy(classDef) .mutableClass .findMutableMethodOf(method) - .literalInstructionViewHook(literal, smaliInstruction) + .injectLiteralInstructionViewCall(literal, smaliInstruction) } } } } } -fun BytecodeContext.literalInstructionHook( +fun BytecodeContext.replaceLiteralInstructionCall( literal: Long, smaliInstruction: String ) { @@ -184,7 +199,7 @@ fun BytecodeContext.literalInstructionHook( context.proxy(classDef) .mutableClass .findMutableMethodOf(method).apply { - val index = getWideLiteralInstructionIndex(literal) + val index = indexOfFirstWideLiteralInstructionValueOrThrow(literal) val register = (instruction as OneRegisterInstruction).registerA.toString() @@ -200,25 +215,17 @@ fun BytecodeContext.literalInstructionHook( } /** - * Get the index of the first instruction with the literal value or throw a [PatchException]. + * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex]. * - * @throws [PatchException] if the literal index not found. + * @param startIndex Optional starting index to start searching from. + * @return -1 if the instruction is not found. + * @see indexOfFirstInstructionOrThrow */ -fun Method.indexOfWideLiteralInstructionOrThrow(literal: Long): Int { - val index = getWideLiteralInstructionIndex(literal) - if (index < 0) { - val value = - if (literal >= 2130706432) // 0x7f000000, general resource id - String.format("%#X", literal).lowercase() - else - literal.toString() - - throw PatchException("Found literal value for: '$value' but method does not contain the id: $this") +fun Method.indexOfFirstInstruction(startIndex: Int = 0, opcode: Opcode): Int = + indexOfFirstInstruction(startIndex) { + this.opcode == opcode } - return index -} - /** * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex]. * @@ -227,7 +234,14 @@ fun Method.indexOfWideLiteralInstructionOrThrow(literal: Long): Int { * @see indexOfFirstInstructionOrThrow */ fun Method.indexOfFirstInstruction(startIndex: Int = 0, predicate: Instruction.() -> Boolean): Int { - val index = this.implementation!!.instructions.drop(startIndex).indexOfFirst(predicate) + if (implementation == null) { + return -1 + } + var instructions = implementation!!.instructions + if (startIndex != 0) { + instructions = instructions.drop(startIndex) + } + val index = instructions.indexOfFirst(predicate) return if (index >= 0) { startIndex + index @@ -236,6 +250,24 @@ fun Method.indexOfFirstInstruction(startIndex: Int = 0, predicate: Instruction.( } } +fun Method.indexOfFirstInstructionOrThrow(opcode: Opcode): Int = + indexOfFirstInstructionOrThrow(0, opcode) + +/** + * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex]. + * + * @return the index of the instruction + * @throws PatchException + * @see indexOfFirstInstruction + */ +fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, opcode: Opcode): Int = + indexOfFirstInstructionOrThrow(startIndex) { + this.opcode == opcode + } + +fun Method.indexOfFirstInstructionReversedOrThrow(opcode: Opcode): Int = + indexOfFirstInstructionReversedOrThrow(null, opcode) + /** * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex]. * @@ -254,17 +286,96 @@ fun Method.indexOfFirstInstructionOrThrow( return index } +/** + * Get the index of matching instruction, + * starting from and [startIndex] and searching down. + * + * @param startIndex Optional starting index to search down from. Searching includes the start index. + * @return -1 if the instruction is not found. + * @see indexOfFirstInstructionReversedOrThrow + */ +fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, opcode: Opcode): Int = + indexOfFirstInstructionReversed(startIndex) { + this.opcode == opcode + } + +/** + * Get the index of matching instruction, + * starting from and [startIndex] and searching down. + * + * @param startIndex Optional starting index to search down from. Searching includes the start index. + * @return -1 if the instruction is not found. + * @see indexOfFirstInstructionReversedOrThrow + */ +fun Method.indexOfFirstInstructionReversed( + startIndex: Int? = null, + predicate: Instruction.() -> Boolean +): Int { + if (implementation == null) { + return -1 + } + var instructions = implementation!!.instructions + if (startIndex != null) { + instructions = instructions.take(startIndex + 1) + } + + return instructions.indexOfLast(predicate) +} + +/** + * Get the index of matching instruction, + * starting from and [startIndex] and searching down. + * + * @param startIndex Optional starting index to search down from. Searching includes the start index. + * @return -1 if the instruction is not found. + * @see indexOfFirstInstructionReversed + */ +fun Method.indexOfFirstInstructionReversedOrThrow( + startIndex: Int? = null, + opcode: Opcode +): Int = + indexOfFirstInstructionReversedOrThrow(startIndex) { + this.opcode == opcode + } + +/** + * Get the index of matching instruction, + * starting from and [startIndex] and searching down. + * + * @param startIndex Optional starting index to search down from. Searching includes the start index. + * @return -1 if the instruction is not found. + * @see indexOfFirstInstructionReversed + */ +fun Method.indexOfFirstInstructionReversedOrThrow( + startIndex: Int? = null, + predicate: Instruction.() -> Boolean +): Int { + val index = indexOfFirstInstructionReversed(startIndex, predicate) + + if (index < 0) { + throw PatchException("Could not find instruction index") + } + + return index +} + /** * @return The list of indices of the opcode in reverse order. */ -fun Method.findOpcodeIndicesReversed(opcode: Opcode): List { +fun Method.findOpcodeIndicesReversed(opcode: Opcode): List = + findOpcodeIndicesReversed { this.opcode == opcode } + +/** + * @return The list of indices of the opcode in reverse order. + */ +fun Method.findOpcodeIndicesReversed(filter: Instruction.() -> Boolean): List { val indexes = implementation!!.instructions .withIndex() - .filter { (_, instruction) -> instruction.opcode == opcode } + .filter { (_, instruction) -> filter.invoke(instruction) } .map { (index, _) -> index } .reversed() - if (indexes.isEmpty()) throw PatchException("No ${opcode.name} instructions found in: $this") + if (indexes.isEmpty()) throw PatchException("No matching instructions found in: $this") return indexes } @@ -273,40 +384,59 @@ fun Method.findOpcodeIndicesReversed(opcode: Opcode): List { * Find the index of the first wide literal instruction with the given value. * * @return the first literal instruction with the value, or -1 if not found. + * @see indexOfFirstWideLiteralInstructionValueOrThrow */ -fun Method.getWideLiteralInstructionIndex(literal: Long) = implementation?.let { +fun Method.indexOfFirstWideLiteralInstructionValue(literal: Long) = implementation?.let { it.instructions.indexOfFirst { instruction -> (instruction as? WideLiteralInstruction)?.wideLiteral == literal } } ?: -1 -fun Method.getStringInstructionIndex(value: String) = implementation?.let { - it.instructions.indexOfFirst { instruction -> - instruction.opcode == Opcode.CONST_STRING - && (instruction as? ReferenceInstruction)?.reference.toString() == value + +/** + * Find the index of the first wide literal instruction with the given value, + * or throw an exception if not found. + * + * @return the first literal instruction with the value, or throws [PatchException] if not found. + */ +fun Method.indexOfFirstWideLiteralInstructionValueOrThrow(literal: Long): Int { + val index = indexOfFirstWideLiteralInstructionValue(literal) + if (index < 0) { + val value = + if (literal >= 2130706432) // 0x7f000000, general resource id + String.format("%#X", literal).lowercase() + else + literal.toString() + + throw PatchException("Found literal value: '$value' but method does not contain the id: $this") } -} ?: -1 -fun Method.getStartsWithStringInstructionIndex(value: String) = implementation?.let { - it.instructions.indexOfFirst { instruction -> - instruction.opcode == Opcode.CONST_STRING - && (instruction as? ReferenceInstruction)?.reference.toString().startsWith(value) + return index +} + +fun Method.indexOfFirstStringInstruction(str: String) = + indexOfFirstInstruction { + opcode == Opcode.CONST_STRING && + getReference()?.string == str + } + + +fun Method.indexOfFirstStringInstructionOrThrow(str: String): Int { + val index = indexOfFirstStringInstruction(str) + if (index < 0) { + throw PatchException("Found string value for: '$str' but method does not contain the id: $this") } -} ?: -1 + + return index +} /** * Check if the method contains a literal with the given value. * * @return if the method contains a literal with the given value. */ -fun Method.containsWideLiteralInstructionIndex(literal: Long) = - getWideLiteralInstructionIndex(literal) >= 0 - -fun Method.containsMethodReferenceNameInstructionIndex(methodName: String) = - getTargetIndexWithMethodReferenceName(methodName) >= 0 - -fun Method.containsReferenceInstructionIndex(reference: String) = - getTargetIndexWithReference(reference) >= 0 +fun Method.containsWideLiteralInstructionValue(literal: Long) = + indexOfFirstWideLiteralInstructionValue(literal) >= 0 /** * Traverse the class hierarchy starting from the given root class. @@ -335,379 +465,113 @@ fun BytecodeContext.traverseClassHierarchy( inline fun Instruction.getReference() = (this as? ReferenceInstruction)?.reference as? T -/** - * Get the index of the first [Instruction] that matches the predicate. - * - * @param predicate The predicate to match. - * @return The index of the first [Instruction] that matches the predicate. - */ -fun Method.indexOfFirstInstruction(predicate: Instruction.() -> Boolean) = - this.implementation!!.instructions.indexOfFirst(predicate) - -fun MutableMethod.getTargetIndexOrThrow(opcode: Opcode) = - getTargetIndexOrThrow(0, opcode) - -fun MutableMethod.getTargetIndexOrThrow(startIndex: Int, opcode: Opcode) = - checkIndex(getTargetIndex(startIndex, opcode), startIndex, opcode) - -fun MutableMethod.getTargetIndexReversedOrThrow(opcode: Opcode) = - getTargetIndexReversedOrThrow(implementation!!.instructions.lastIndex, opcode) - -fun MutableMethod.getTargetIndexReversedOrThrow(startIndex: Int, opcode: Opcode) = - checkIndex(getTargetIndexReversed(startIndex, opcode), startIndex, opcode) - -fun Method.getTargetIndexWithFieldReferenceNameOrThrow(filedName: String) = - checkIndex(getTargetIndexWithFieldReferenceName(filedName), 0, filedName) - -fun MutableMethod.getTargetIndexWithFieldReferenceNameOrThrow(startIndex: Int, filedName: String) = - checkIndex(getTargetIndexWithFieldReferenceName(startIndex, filedName), startIndex, filedName) - -fun MutableMethod.getTargetIndexWithFieldReferenceNameReversedOrThrow(returnType: String) = - getTargetIndexWithFieldReferenceNameReversedOrThrow( - implementation!!.instructions.lastIndex, - returnType - ) - -fun MutableMethod.getTargetIndexWithFieldReferenceNameReversedOrThrow( - startIndex: Int, - returnType: String -) = - checkIndex( - getTargetIndexWithFieldReferenceNameReversed(startIndex, returnType), - startIndex, - returnType - ) - -fun Method.getTargetIndexWithFieldReferenceTypeOrThrow(returnType: String) = - checkIndex(getTargetIndexWithFieldReferenceType(returnType), 0, returnType) - -fun MutableMethod.getTargetIndexWithFieldReferenceTypeOrThrow(startIndex: Int, returnType: String) = - checkIndex(getTargetIndexWithFieldReferenceType(startIndex, returnType), startIndex, returnType) - -fun MutableMethod.getTargetIndexWithFieldReferenceTypeReversedOrThrow(returnType: String) = - getTargetIndexWithFieldReferenceTypeReversedOrThrow( - implementation!!.instructions.lastIndex, - returnType - ) - -fun MutableMethod.getTargetIndexWithFieldReferenceTypeReversedOrThrow( - startIndex: Int, - returnType: String -) = - checkIndex( - getTargetIndexWithFieldReferenceTypeReversed(startIndex, returnType), - startIndex, - returnType - ) - -fun Method.getTargetIndexWithMethodReferenceNameOrThrow(methodName: String) = - checkIndex(getTargetIndexWithMethodReferenceName(methodName), 0, methodName) - -fun MutableMethod.getTargetIndexWithMethodReferenceNameOrThrow( - startIndex: Int, - methodName: String -) = - checkIndex( - getTargetIndexWithMethodReferenceName(startIndex, methodName), - startIndex, - methodName - ) - -fun MutableMethod.getTargetIndexWithMethodReferenceNameReversedOrThrow(methodName: String) = - getTargetIndexWithMethodReferenceNameReversedOrThrow( - implementation!!.instructions.lastIndex, - methodName - ) - -fun MutableMethod.getTargetIndexWithMethodReferenceNameReversedOrThrow( - startIndex: Int, - methodName: String -) = - checkIndex( - getTargetIndexWithMethodReferenceNameReversed(startIndex, methodName), - startIndex, - methodName - ) - -fun Method.getTargetIndexWithReferenceOrThrow(reference: String) = - checkIndex(getTargetIndexWithReference(reference), 0, reference) - -fun MutableMethod.getTargetIndexWithReferenceOrThrow(startIndex: Int, reference: String) = - checkIndex(getTargetIndexWithReference(startIndex, reference), startIndex, reference) - -fun MutableMethod.getTargetIndexWithReferenceReversedOrThrow(reference: String) = - getTargetIndexWithReferenceReversedOrThrow(implementation!!.instructions.lastIndex, reference) - -fun MutableMethod.getTargetIndexWithReferenceReversedOrThrow(startIndex: Int, reference: String) = - checkIndex(getTargetIndexWithReferenceReversed(startIndex, reference), startIndex, reference) - -fun checkIndex(index: Int, startIndex: Int, opcode: Opcode): Int { - if (index < 0) { - throw PatchException("Target index not found. startIndex: $startIndex, opcode: $opcode") - } - return index -} - -fun checkIndex(index: Int, startIndex: Int, name: String): Int { - if (index < 0) { - throw PatchException("Target index not found. startIndex: $startIndex, name: $name") - } - return index -} - -fun MutableMethod.getTargetIndex(opcode: Opcode) = getTargetIndex(0, opcode) - -fun MutableMethod.getTargetIndex(startIndex: Int, opcode: Opcode) = - implementation!!.instructions.let { - startIndex + it.subList(startIndex, it.size - 1).indexOfFirst { instruction -> - instruction.opcode == opcode - } - } - -fun MutableMethod.getTargetIndexReversed(opcode: Opcode) = - getTargetIndexReversed(implementation!!.instructions.size - 1, opcode) - -fun MutableMethod.getTargetIndexReversed(startIndex: Int, opcode: Opcode): Int { - for (index in startIndex downTo 0) { - if (getInstruction(index).opcode != opcode) - continue - - return index - } - return -1 -} - -fun Method.getTargetIndexWithFieldReferenceName(filedName: String) = implementation?.let { - it.instructions.indexOfFirst { instruction -> - instruction.getReference()?.name == filedName - } -} ?: -1 - -fun MutableMethod.getTargetIndexWithFieldReferenceName(startIndex: Int, filedName: String) = - implementation!!.instructions.let { - startIndex + it.subList(startIndex, it.lastIndex).indexOfFirst { instruction -> - instruction.getReference()?.name == filedName - } - } - -fun MutableMethod.getTargetIndexWithFieldReferenceNameReversed(returnType: String) = - getTargetIndexWithFieldReferenceTypeReversed( - implementation!!.instructions.lastIndex, - returnType - ) - -fun MutableMethod.getTargetIndexWithFieldReferenceNameReversed( - startIndex: Int, - filedName: String -): Int { - for (index in startIndex downTo 0) { - val instruction = getInstruction(index) - if (instruction.getReference()?.name != filedName) - continue - - return index - } - return -1 -} - -fun Method.getTargetIndexWithFieldReferenceType(returnType: String) = implementation?.let { - it.instructions.indexOfFirst { instruction -> - instruction.getReference()?.type == returnType - } -} ?: -1 - -fun MutableMethod.getTargetIndexWithFieldReferenceTypeReversed(returnType: String) = - getTargetIndexWithFieldReferenceTypeReversed(implementation!!.instructions.size - 1, returnType) - -fun MutableMethod.getTargetIndexWithFieldReferenceType(startIndex: Int, returnType: String) = - implementation!!.instructions.let { - startIndex + it.subList(startIndex, it.size - 1).indexOfFirst { instruction -> - instruction.getReference()?.type == returnType - } - } - -fun MutableMethod.getTargetIndexWithFieldReferenceTypeReversed( - startIndex: Int, - returnType: String -): Int { - for (index in startIndex downTo 0) { - val instruction = getInstruction(index) - if (instruction.getReference()?.type != returnType) - continue - - return index - } - return -1 -} - -fun Method.getTargetIndexWithMethodReferenceName(methodName: String) = implementation?.let { - it.instructions.indexOfFirst { instruction -> - instruction.getReference()?.name == methodName - } -} ?: -1 - -fun MutableMethod.getTargetIndexWithMethodReferenceNameReversed(methodName: String) = - getTargetIndexWithMethodReferenceNameReversed( - implementation!!.instructions.size - 1, - methodName - ) - - -fun MutableMethod.getTargetIndexWithMethodReferenceName(startIndex: Int, methodName: String) = - implementation!!.instructions.let { - startIndex + it.subList(startIndex, it.size - 1).indexOfFirst { instruction -> - instruction.getReference()?.name == methodName - } - } - -fun MutableMethod.getTargetIndexWithMethodReferenceNameReversed( - startIndex: Int, - methodName: String -): Int { - for (index in startIndex downTo 0) { - val instruction = getInstruction(index) - if (instruction.getReference()?.name != methodName) - continue - - return index - } - return -1 -} - -fun Method.getTargetIndexWithReference(reference: String) = implementation?.let { - it.instructions.indexOfFirst { instruction -> - (instruction as? ReferenceInstruction)?.reference.toString().contains(reference) - } -} ?: -1 - -fun MutableMethod.getTargetIndexWithReference(reference: String) = - getTargetIndexWithReference(0, reference) - -fun MutableMethod.getTargetIndexWithReferenceReversed(reference: String) = - getTargetIndexWithReferenceReversed(implementation!!.instructions.size - 1, reference) - -fun MutableMethod.getTargetIndexWithReference(startIndex: Int, reference: String) = - implementation!!.instructions.let { - startIndex + it.subList(startIndex, it.size - 1).indexOfFirst { instruction -> - (instruction as? ReferenceInstruction)?.reference.toString().contains(reference) - } - } - -fun MutableMethod.getTargetIndexWithReferenceReversed(startIndex: Int, reference: String): Int { - for (index in startIndex downTo 0) { - val instruction = getInstruction(index) - if (!(instruction as? ReferenceInstruction)?.reference.toString().contains(reference)) - continue - - return index - } - return -1 -} - fun MethodFingerprintResult.getWalkerMethod(context: BytecodeContext, offset: Int) = mutableMethod.getWalkerMethod(context, offset) /** - * MethodWalker structural limitations cause incorrect class to be found - * - * MethodReference to find in YouTube 18.29.38: - * 'Lng;->d(Lou;)Z' - * - * Class found by MethodWalker in YouTube 18.29.38: - * 'Lcom/google/android/gms/maps/model/LatLng;' - * - * The reason this error occurs is because [BytecodeContext.findClass] checks whether className is included or not - * - * In ReVanced Patcher 19.3.1: - * fun findClass(className: String) = findClass { it.type.contains(className) } - * - * (Class 'Lcom/google/android/gms/maps/model/LatLng;' is returned because class 'Lcom/google/android/gms/maps/model/LatLng;' contains keyword 'Lng;') + * MethodWalker can find the wrong class: + * https://github.com/ReVanced/revanced-patcher/issues/309 * * As a workaround, redefine MethodWalker here */ fun MutableMethod.getWalkerMethod(context: BytecodeContext, offset: Int): MutableMethod { val newMethod = getInstruction(offset).reference as MethodReference - return context.findClass { classDef -> classDef.type == newMethod.definingClass } - ?.mutableClass - ?.methods - ?.first { method -> MethodUtil.methodSignaturesMatch(method, newMethod) } - ?: throw PatchException("This method can not be walked at offset $offset inside the method $name") + return context.findMethodOrThrow(newMethod.definingClass) { + MethodUtil.methodSignaturesMatch(this, newMethod) + } } -fun MutableClass.addFieldAndInstructions( - context: BytecodeContext, +fun BytecodeContext.addStaticFieldToIntegration( + className: String, methodName: String, fieldName: String, objectClass: String, smaliInstructions: String, - shouldAddConstructor: Boolean + shouldAddConstructor: Boolean = true ) { - val objectCall = "$this->$fieldName:$objectClass" - - methods.single { method -> method.name == methodName }.apply { - staticFields.add( - ImmutableField( - definingClass, - fieldName, - objectClass, - AccessFlags.PUBLIC or AccessFlags.STATIC, - null, - annotations, - null - ).toMutable() - ) + val mutableClass = findClass { classDef -> classDef.type == className } + ?.mutableClass + ?: throw PatchException("No matching classes found: $className") + + val objectCall = "$mutableClass->$fieldName:$objectClass" + + mutableClass.apply { + methods.first { method -> method.name == methodName }.apply { + staticFields.add( + ImmutableField( + definingClass, + fieldName, + objectClass, + AccessFlags.PUBLIC or AccessFlags.STATIC, + null, + annotations, + null + ).toMutable() + ) - addInstructionsWithLabels( - 0, - """ + addInstructionsWithLabels( + 0, + """ sget-object v0, $objectCall """ + smaliInstructions - ) + ) + } } - if (shouldAddConstructor) { - context.findClass(objectClass)!!.mutableClass.methods - .filter { method -> method.name == "" } - .forEach { mutableMethod -> - mutableMethod.apply { - val initializeIndex = indexOfFirstInstructionOrThrow { - opcode == Opcode.INVOKE_DIRECT && getReference()?.name == "" - } - val insertIndex = if (initializeIndex == -1) - 1 - else - initializeIndex + 1 - - val initializeRegister = if (initializeIndex == -1) - "p0" - else - "v${getInstruction(initializeIndex).registerC}" - - addInstruction( - insertIndex, - "sput-object $initializeRegister, $objectCall" - ) + if (!shouldAddConstructor) return + + findMethodsOrThrow(objectClass) + .filter { method -> MethodUtil.isConstructor(method) } + .forEach { mutableMethod -> + mutableMethod.apply { + val initializeIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_DIRECT && + getReference()?.name == "" } + val insertIndex = if (initializeIndex == -1) + 1 + else + initializeIndex + 1 + + val initializeRegister = if (initializeIndex == -1) + "p0" + else + "v${getInstruction(initializeIndex).registerC}" + + addInstruction( + insertIndex, + "sput-object $initializeRegister, $objectCall" + ) } + } +} + +fun BytecodeContext.findMethodOrThrow( + reference: String, + methodPredicate: Method.() -> Boolean = { MethodUtil.isConstructor(this) } +) = findMethodsOrThrow(reference).first(methodPredicate) + +fun BytecodeContext.findMethodsOrThrow(reference: String): MutableSet { + val methods = + findClass { classDef -> classDef.type == reference } + ?.mutableClass + ?.methods + + if (methods != null) { + return methods + } else { + throw PatchException("No matching methods found in: $reference") } } fun BytecodeContext.updatePatchStatus( className: String, methodName: String -) { - this.classes.forEach { classDef -> - if (classDef.type.endsWith(className)) { - val patchStatusMethod = - this.proxy(classDef).mutableClass.methods.first { it.name == methodName } - - patchStatusMethod.replaceInstruction( - 0, - "const/4 v0, 0x1" - ) - } - } -} +) = findMethodOrThrow(className) { name == methodName } + .replaceInstruction( + 0, + "const/4 v0, 0x1" + ) /** * Return the resolved methods of [MethodFingerprint]s early. @@ -728,7 +592,7 @@ fun List.returnEarly(bool: Boolean = false) { return v0 """ - else -> throw Exception("This case should never happen.") + else -> throw PatchException("This case should never happen: ${fingerprint.javaClass.simpleName}") } result.mutableMethod.addInstructions(0, stringInstructions) diff --git a/src/main/kotlin/app/revanced/util/ResourceUtils.kt b/src/main/kotlin/app/revanced/util/ResourceUtils.kt index 38ddccbd3f..7e46120347 100644 --- a/src/main/kotlin/app/revanced/util/ResourceUtils.kt +++ b/src/main/kotlin/app/revanced/util/ResourceUtils.kt @@ -8,6 +8,7 @@ import app.revanced.patcher.patch.options.PatchOption import app.revanced.patcher.util.DomFileEditor import org.w3c.dom.Element import org.w3c.dom.Node +import org.w3c.dom.NodeList import java.io.File import java.io.InputStream import java.nio.file.Files @@ -26,6 +27,8 @@ fun PatchOption.lowerCaseOrThrow() = valueOrThrow() fun PatchOption.underBarOrThrow() = lowerCaseOrThrow() .replace(" ", "_") + .replace("(", "") + .replace(")", "") fun Node.adoptChild(tagName: String, block: Element.() -> Unit) { val child = ownerDocument.createElement(tagName) @@ -193,4 +196,30 @@ fun String.copyXmlNode(source: DomFileEditor, target: DomFileEditor): AutoClosea source.close() target.close() } +} + +internal fun NodeList.findElementByAttributeValue(attributeName: String, value: String): Element? { + for (i in 0 until length) { + val node = item(i) + if (node.nodeType == Node.ELEMENT_NODE) { + val element = node as Element + + if (element.getAttribute(attributeName) == value) { + return element + } + + // Recursively search. + val found = element.childNodes.findElementByAttributeValue(attributeName, value) + if (found != null) { + return found + } + } + } + + return null +} + +internal fun NodeList.findElementByAttributeValueOrThrow(attributeName: String, value: String): Element { + return findElementByAttributeValue(attributeName, value) + ?: throw PatchException("Could not find: $attributeName $value") } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/fingerprint/LiteralValueFingerprint.kt b/src/main/kotlin/app/revanced/util/fingerprint/LiteralValueFingerprint.kt index 293855da43..ea7cf3ebb6 100644 --- a/src/main/kotlin/app/revanced/util/fingerprint/LiteralValueFingerprint.kt +++ b/src/main/kotlin/app/revanced/util/fingerprint/LiteralValueFingerprint.kt @@ -1,7 +1,7 @@ package app.revanced.util.fingerprint import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.util.containsWideLiteralInstructionIndex +import app.revanced.util.containsWideLiteralInstructionValue import com.android.tools.smali.dexlib2.Opcode /** @@ -29,6 +29,6 @@ abstract class LiteralValueFingerprint( opcodes = opcodes, strings = strings, customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionIndex(literalSupplier()) + methodDef.containsWideLiteralInstructionValue(literalSupplier()) } ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/fingerprint/MethodReferenceNameFingerprint.kt b/src/main/kotlin/app/revanced/util/fingerprint/MethodReferenceNameFingerprint.kt deleted file mode 100644 index f2252f8815..0000000000 --- a/src/main/kotlin/app/revanced/util/fingerprint/MethodReferenceNameFingerprint.kt +++ /dev/null @@ -1,34 +0,0 @@ -package app.revanced.util.fingerprint - -import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.util.containsMethodReferenceNameInstructionIndex -import com.android.tools.smali.dexlib2.Opcode - -/** - * A fingerprint to resolve methods that contain a specific method reference name value. - * - * @param returnType The method's return type compared using String.startsWith. - * @param accessFlags The method's exact access flags using values of AccessFlags. - * @param parameters The parameters of the method. Partial matches allowed and follow the same rules as returnType. - * @param opcodes An opcode pattern of the method's instructions. Wildcard or unknown opcodes can be specified by null. - * @param strings A list of the method's strings compared each using String.contains. - * @param reference A supplier for the method reference name value to check for. - */ -abstract class MethodReferenceNameFingerprint( - returnType: String? = null, - accessFlags: Int? = null, - parameters: Iterable? = null, - opcodes: Iterable? = null, - strings: Iterable? = null, - // Has to be a supplier because the fingerprint is created before patches can check reference. - reference: () -> String -) : MethodFingerprint( - returnType = returnType, - accessFlags = accessFlags, - parameters = parameters, - opcodes = opcodes, - strings = strings, - customFingerprint = { methodDef, _ -> - methodDef.containsMethodReferenceNameInstructionIndex(reference()) - } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/fingerprint/MultiMethodFingerprint.kt b/src/main/kotlin/app/revanced/util/fingerprint/MultiMethodFingerprint.kt new file mode 100644 index 0000000000..b25b538762 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/fingerprint/MultiMethodFingerprint.kt @@ -0,0 +1,208 @@ +package app.revanced.util.fingerprint + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patcher.fingerprint.MethodFingerprintResult +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.ClassDef +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.StringReference + +private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch +private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult + +/** + * Represents the [MethodFingerprint] for a method. + * @param returnType The return type of the method. + * @param accessFlags The access flags of the method. + * @param parameters The parameters of the method. + * @param opcodes The list of opcodes of the method. + * @param strings A list of strings which a method contains. + * @param customFingerprint A custom condition for this fingerprint. + * A `null` opcode is equals to an unknown opcode. + */ +abstract class MultiMethodFingerprint( + val returnType: String? = null, + val accessFlags: Int? = null, + val parameters: Iterable? = null, + val opcodes: Iterable? = null, + val strings: Iterable? = null, + val customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null +) { + /** + * The result of the [MethodFingerprint]. + */ + var result = mutableListOf() + private var resolved = false + + companion object { + /** + * Resolve a list of [MethodFingerprint] against a list of [ClassDef]. + * + * @param classes The classes on which to resolve the [MethodFingerprint] in. + * @param context The [BytecodeContext] to host proxies. + * @return True if the resolution was successful, false otherwise. + */ + fun Iterable.resolve( + context: BytecodeContext, + classes: Iterable + ) { + for (fingerprint in this) { // For each fingerprint + if (fingerprint.resolved) continue + for (classDef in classes) // search through all classes for the fingerprint + fingerprint.resolve(context, classDef) + fingerprint.resolved = true + } + } + + /** + * Resolve a [MethodFingerprint] against a [ClassDef]. + * + * @param forClass The class on which to resolve the [MethodFingerprint] in. + * @param context The [BytecodeContext] to host proxies. + * @return True if the resolution was successful, false otherwise. + */ + fun MultiMethodFingerprint.resolve(context: BytecodeContext, forClass: ClassDef): Boolean { + for (method in forClass.methods) + if (this.resolve(context, method, forClass)) + return true + return false + } + + /** + * Resolve a [MethodFingerprint] against a [Method]. + * + * @param method The class on which to resolve the [MethodFingerprint] in. + * @param forClass The class on which to resolve the [MethodFingerprint]. + * @param context The [BytecodeContext] to host proxies. + * @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise. + */ + fun MultiMethodFingerprint.resolve( + context: BytecodeContext, + method: Method, + forClass: ClassDef + ): Boolean { + val methodFingerprint = this + + if (methodFingerprint.returnType != null && !method.returnType.startsWith( + methodFingerprint.returnType + ) + ) + return false + + if (methodFingerprint.accessFlags != null && methodFingerprint.accessFlags != method.accessFlags) + return false + + fun parametersEqual( + parameters1: Iterable, parameters2: Iterable + ): Boolean { + if (parameters1.count() != parameters2.count()) return false + val iterator1 = parameters1.iterator() + parameters2.forEach { + if (!it.startsWith(iterator1.next())) return false + } + return true + } + + if (methodFingerprint.parameters != null && !parametersEqual( + methodFingerprint.parameters, // TODO: parseParameters() + method.parameterTypes + ) + ) return false + + @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") + if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!( + method, + forClass + ) + ) + return false + + val stringsScanResult = if (methodFingerprint.strings != null) { + StringsScanResult( + buildList { + val implementation = method.implementation ?: return false + + val stringsList = methodFingerprint.strings.toMutableList() + + implementation.instructions.forEachIndexed { instructionIndex, instruction -> + if ( + instruction.opcode != Opcode.CONST_STRING && + instruction.opcode != Opcode.CONST_STRING_JUMBO + ) return@forEachIndexed + + val string = + ((instruction as ReferenceInstruction).reference as StringReference).string + val index = stringsList.indexOfFirst(string::contains) + if (index == -1) return@forEachIndexed + + add(StringMatch(string, instructionIndex)) + stringsList.removeAt(index) + } + + if (stringsList.isNotEmpty()) return false + } + ) + } else null + + val patternScanResult = if (methodFingerprint.opcodes != null) { + method.implementation?.instructions ?: return false + + method.patternScan(methodFingerprint) ?: return false + } else null + + methodFingerprint.result.add( + MethodFingerprintResult( + method, + forClass, + MethodFingerprintResult.MethodFingerprintScanResult( + patternScanResult, + stringsScanResult + ), + context + ) + ) + + return true + } + + private fun Method.patternScan( + fingerprint: MultiMethodFingerprint + ): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? { + val instructions = this.implementation!!.instructions + + val pattern = fingerprint.opcodes!! + val instructionLength = instructions.count() + val patternLength = pattern.count() + + for (index in 0 until instructionLength) { + var patternIndex = 0 + + while (index + patternIndex < instructionLength) { + val originalOpcode = instructions.elementAt(index + patternIndex).opcode + val patternOpcode = pattern.elementAt(patternIndex) + + if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) { + // reaching maximum threshold (0) means, + // the pattern does not match to the current instructions + break + } + + if (patternIndex < patternLength - 1) { + // if the entire pattern has not been scanned yet + // continue the scan + patternIndex++ + continue + } + return MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult( + index, + index + patternIndex + ) + } + } + + return null + } + } +} diff --git a/src/main/kotlin/app/revanced/util/fingerprint/ReferenceFingerprint.kt b/src/main/kotlin/app/revanced/util/fingerprint/ReferenceFingerprint.kt deleted file mode 100644 index 69cb6b057a..0000000000 --- a/src/main/kotlin/app/revanced/util/fingerprint/ReferenceFingerprint.kt +++ /dev/null @@ -1,34 +0,0 @@ -package app.revanced.util.fingerprint - -import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.util.containsReferenceInstructionIndex -import com.android.tools.smali.dexlib2.Opcode - -/** - * A fingerprint to resolve methods that contain a specific reference value. - * - * @param returnType The method's return type compared using String.startsWith. - * @param accessFlags The method's exact access flags using values of AccessFlags. - * @param parameters The parameters of the method. Partial matches allowed and follow the same rules as returnType. - * @param opcodes An opcode pattern of the method's instructions. Wildcard or unknown opcodes can be specified by null. - * @param strings A list of the method's strings compared each using String.contains. - * @param reference A supplier for the reference value to check for. - */ -abstract class ReferenceFingerprint( - returnType: String? = null, - accessFlags: Int? = null, - parameters: Iterable? = null, - opcodes: Iterable? = null, - strings: Iterable? = null, - // Has to be a supplier because the fingerprint is created before patches can check reference. - reference: () -> String -) : MethodFingerprint( - returnType = returnType, - accessFlags = accessFlags, - parameters = parameters, - opcodes = opcodes, - strings = strings, - customFingerprint = { methodDef, _ -> - methodDef.containsReferenceInstructionIndex(reference()) - } -) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/patch/MultiMethodBytecodePatch.kt b/src/main/kotlin/app/revanced/util/patch/MultiMethodBytecodePatch.kt new file mode 100644 index 0000000000..fa137319a6 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/patch/MultiMethodBytecodePatch.kt @@ -0,0 +1,16 @@ +package app.revanced.util.patch + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.util.fingerprint.MultiMethodFingerprint +import app.revanced.util.fingerprint.MultiMethodFingerprint.Companion.resolve + +abstract class MultiMethodBytecodePatch( + val fingerprints: Set = setOf(), + val multiFingerprints: Set = setOf() +) : BytecodePatch(fingerprints) { + override fun execute(context: BytecodeContext) { + multiFingerprints.resolve(context, context.classes) + } +} diff --git a/src/main/resources/music/branding/afn_blue/monochrome/drawable/ic_app_icons_themed_youtube_music.xml b/src/main/resources/music/branding/afn_blue/monochrome/drawable/ic_app_icons_themed_youtube_music.xml index e88c905d5d..9d5e8a28f3 100644 --- a/src/main/resources/music/branding/afn_blue/monochrome/drawable/ic_app_icons_themed_youtube_music.xml +++ b/src/main/resources/music/branding/afn_blue/monochrome/drawable/ic_app_icons_themed_youtube_music.xml @@ -1,788 +1,15 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/src/main/resources/music/branding/afn_blue/settings/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/branding/afn_blue/settings/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..b34305bd05 --- /dev/null +++ b/src/main/resources/music/branding/afn_blue/settings/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,1011 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/music/branding/afn_red/monochrome/drawable/ic_app_icons_themed_youtube_music.xml b/src/main/resources/music/branding/afn_red/monochrome/drawable/ic_app_icons_themed_youtube_music.xml index e88c905d5d..9d5e8a28f3 100644 --- a/src/main/resources/music/branding/afn_red/monochrome/drawable/ic_app_icons_themed_youtube_music.xml +++ b/src/main/resources/music/branding/afn_red/monochrome/drawable/ic_app_icons_themed_youtube_music.xml @@ -1,788 +1,15 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/src/main/resources/music/branding/afn_red/settings/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/branding/afn_red/settings/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..ae0a784050 --- /dev/null +++ b/src/main/resources/music/branding/afn_red/settings/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,1007 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/music/branding/mmt/settings/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/branding/mmt/settings/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..6280d60264 --- /dev/null +++ b/src/main/resources/music/branding/mmt/settings/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,1023 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/music/branding/revancify_blue/settings/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/branding/revancify_blue/settings/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..31a5a95170 --- /dev/null +++ b/src/main/resources/music/branding/revancify_blue/settings/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,1027 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/music/branding/revancify_red/settings/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/branding/revancify_red/settings/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..4e9a3e24ce --- /dev/null +++ b/src/main/resources/music/branding/revancify_red/settings/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,1023 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/music/branding/youtube_music/settings/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/branding/youtube_music/settings/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..3cbd7311a8 --- /dev/null +++ b/src/main/resources/music/branding/youtube_music/settings/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,31 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/music/settings/host/values/strings.xml b/src/main/resources/music/settings/host/values/strings.xml index 51e591a927..0e9c153038 100644 --- a/src/main/resources/music/settings/host/values/strings.xml +++ b/src/main/resources/music/settings/host/values/strings.xml @@ -1,7 +1,8 @@ - + ReVanced Extended + Reset to default values. Restart to load the layout normally @@ -58,11 +59,6 @@ Please download %2$s from the website." Hide fullscreen ads Hides fullscreen ads. - Close fullscreen ads - "If it is enabled, fullscreen ads are closed through the Close button. -If it is disabled, fullscreen ads are blocked. (there may be side effects)" - Fullscreen ads have been blocked. (DialogType: %s) - Fullscreen ads have been closed. Hide general ads Hides general ads. Hide media ads @@ -279,8 +275,8 @@ Some features may not work properly in the old player layout." Remembers the last video quality selected. Show a toast Show a toast when changing the default video quality. - Custom speeds must be less than %sx. Using default values. - Invalid custom playback speeds. Using default values. + Custom speeds must be less than %sx. + Invalid custom playback speeds. Changing default speed to %s. Changing default mobile data quality to %s. Failed to set quality. @@ -363,7 +359,7 @@ Some features may not work properly in the old player layout." Color: Color changed. Color reset. - Invalid color code. Color reset to default. + Invalid color code. Reset color Data is provided by the SponsorBlock API. Tap here to learn more and see downloads for other platforms. @@ -409,6 +405,9 @@ Tap on the continue button and disable battery optimizations." Sanitize sharing links Removes tracking query parameters from URLs when sharing links. + Open default app settings + To open YouTube Music links in RVX Music, enable \'Open supported links\' and enable the supported web addresses. + Import / Export settings Import or export settings. diff --git a/src/main/resources/music/settings/icons/drawable-xxhdpi/empty_icon.png b/src/main/resources/music/settings/icons/drawable-xxhdpi/empty_icon.png new file mode 100644 index 0000000000..8b97b78aea Binary files /dev/null and b/src/main/resources/music/settings/icons/drawable-xxhdpi/empty_icon.png differ diff --git a/src/main/resources/music/settings/icons/drawable/icon.xml b/src/main/resources/music/settings/icons/drawable/icon.xml new file mode 100644 index 0000000000..cdcba371c5 --- /dev/null +++ b/src/main/resources/music/settings/icons/drawable/icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/music/settings/icons/extension/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/settings/icons/extension/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..987e224f2a --- /dev/null +++ b/src/main/resources/music/settings/icons/extension/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/music/settings/icons/gear/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/settings/icons/gear/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..984b1fc6df --- /dev/null +++ b/src/main/resources/music/settings/icons/gear/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/music/settings/icons/revanced/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/settings/icons/revanced/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..cbf886b16a --- /dev/null +++ b/src/main/resources/music/settings/icons/revanced/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/music/settings/icons/revanced_colored/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/settings/icons/revanced_colored/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..d48f055ba4 --- /dev/null +++ b/src/main/resources/music/settings/icons/revanced_colored/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,12702 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/music/translations/bg-rBG/strings.xml b/src/main/resources/music/translations/bg-rBG/strings.xml index d3d2044330..362ef96b5d 100644 --- a/src/main/resources/music/translations/bg-rBG/strings.xml +++ b/src/main/resources/music/translations/bg-rBG/strings.xml @@ -1,6 +1,6 @@ - + ReVanced Extended Рестартирайте, за да заредите оформлението нормално @@ -49,11 +49,6 @@ Реклами Скриване на рекламите в режим на цял екран Скриване на рекламите в режим на цял екран. - Как да затворите реклами на цял екран - "Ако е активирана, рекламата на цял екран се затваря чрез бутона Затвори. -Ако е деактивирано, рекламата на цял екран е блокирана. (могат да възникнат нежелани реакции)" - Рекламите на цял екран са блокирани. (Тип на диалоговия прозорец: %s) - Рекламите на цял екран са затворени. Скриване на общите реклами Скриване на общите реклами. Скриване на музикални реклами diff --git a/src/main/resources/music/translations/bn/strings.xml b/src/main/resources/music/translations/bn/strings.xml index 7bf375e2dc..7ff7361ee9 100644 --- a/src/main/resources/music/translations/bn/strings.xml +++ b/src/main/resources/music/translations/bn/strings.xml @@ -1,6 +1,6 @@ - + অ্যাকাউন্ট মেনু লুকান diff --git a/src/main/resources/music/translations/cs-rCZ/strings.xml b/src/main/resources/music/translations/cs-rCZ/strings.xml index d39346adcb..3bb063a583 100644 --- a/src/main/resources/music/translations/cs-rCZ/strings.xml +++ b/src/main/resources/music/translations/cs-rCZ/strings.xml @@ -1,6 +1,6 @@ - + ReVanced Extended diff --git a/src/main/resources/music/translations/el-rGR/strings.xml b/src/main/resources/music/translations/el-rGR/strings.xml index 44d13ce3da..ee5bbb9207 100644 --- a/src/main/resources/music/translations/el-rGR/strings.xml +++ b/src/main/resources/music/translations/el-rGR/strings.xml @@ -1,7 +1,8 @@ - + ReVanced Extended + Επαναφέρθηκε στην προεπιλεγμένη τιμή. Επανεκκίνηση ώστε να φορτωθεί σωστά η εφαρμογή Ανανέωση και επανεκκίνηση @@ -49,11 +50,6 @@ Διαφημίσεις Απόκρυψη διαφημίσεων πλήρους οθόνης Απόκρυψη των ενδιάμεσων διαφημίσεων πλήρους οθόνης. - Κλείσιμο διαφημίσεων πλήρους οθόνης - "Αν είναι ενεργοποιημένο, οι διαφημίσεις πλήρους οθόνης κλείνουν μέσω του κουμπιού κλεισίματος. -Αν είναι απενεργοποιημένο, οι διαφημίσεις πλήρους οθόνης είναι αυτόματα αποκλεισμένες. (μπορεί να υπάρχουν παρενέργειες)" - Οι διαφημίσεις πλήρους οθόνης αποκλείστηκαν. (Τύπος: %s) - Οι διαφημίσεις πλήρους οθόνης έκλεισαν. Απόκρυψη γενικών διαφημίσεων Απόκρυψη των γενικών διαφημίσεων. Απόκρυψη διαφημίσεων μουσικής @@ -255,8 +251,8 @@ Απομνημόνευση της τελευταίας ποιότητας βίντεο που επιλέχθηκε. Εμφάνιση μηνύματος Εμφάνιση μηνύματος στο κάτω μέρος της οθόνης κατά την αλλαγή προεπιλεγμένης ποιότητας βίντεο. - Οι ταχύτητες πρέπει να είναι μικρότερες από %sx, επαναφέρθηκαν. - Μη έγκυρες ταχύτητες αναπαραγωγής, επαναφέρθηκαν. + Οι ταχύτητες πρέπει να είναι μικρότερες από %sx. + Μη έγκυρες ταχύτητες αναπαραγωγής. Η προεπιλεγμένη ταχύτητα άλλαξε σε %s. Η προεπιλεγμένη ποιότητα δεδομένων άλλαξε σε %s. Αποτυχία ρύθμισης της ποιότητας. @@ -329,7 +325,7 @@ Χρώμα: Το χρώμα άλλαξε. Το χρώμα επαναφέρθηκε. - Μη έγκυρος κωδικός χρώματος, επαναφέρθηκε. + Μη έγκυρος κωδικός χρώματος. Επαναφορά χρώματος Τα δεδομένα παρέχονται από το SponsorBlock API. Πατήστε για να μάθετε περισσότερα και να δείτε λήψεις για άλλες πλατφόρμες. Σχετικά με @@ -339,7 +335,7 @@ Παράκαμψη μπλοκαρίσματος φόρτωσης εικόνων Αντικατάσταση του domain για την φόρτωση εικόνων όπου είναι μπλοκαρισμένες σε ορισμένες περιοχές ώστε να μπορούν να ληφθούν μικρογραφίες βίντεο, εικόνες δημοσιεύσεων, κλπ. Αλλαγή μενού κοινοποίησης - Αλλαγή του μενού κοινοποίησης σε αυτό του συστήματος σας αντί του YouTube. + Αλλαγή του μενού κοινοποίησης σε αυτό του συστήματος σας αντί του YouTube Music. Απενεργοποίηση εφέ εκκίνησης θέματος Cairo Απενεργοποίηση των εφέ θέματος Cairo κατά την εκκίνηση της εφαρμογής. Ενεργοποίηση καταγραφής σφαλμάτων @@ -366,6 +362,8 @@ Συνέχεια Καθαρισμός συνδέσμων κοινοποίησης Αφαίρεση των παραμέτρων παρακολούθησης από τις διευθύνσεις URL κατά την κοινοποίηση συνδέσμων. + Άνοιγμα ρυθμίσεων προεπιλεγμένων εφαρμογών + Για να ανοίγουν οι συνδέσμοι YouTube Music στο RVX Music, ενεργοποιήστε το «Άνοιγμα υποστηριζόμενων συνδέσμων» και τις υποστηριζόμενες διευθύνσεις ιστού. Εισαγωγή / Εξαγωγή Εισαγωγή ή εξαγωγή των ρυθμίσεών σας. Εξαγωγή ρυθμίσεων σε αρχείο diff --git a/src/main/resources/music/translations/es-rES/strings.xml b/src/main/resources/music/translations/es-rES/strings.xml index 21bbd91a9b..52e947dc89 100644 --- a/src/main/resources/music/translations/es-rES/strings.xml +++ b/src/main/resources/music/translations/es-rES/strings.xml @@ -1,7 +1,8 @@ - + ReVanced Extended + Restablecer a valores por defecto. Reiniciar para cargar el diseño normalmente Actualizar y reiniciar @@ -49,11 +50,6 @@ Descarga %2$s desde el sitio web." Anuncios Ocultar anuncios en pantalla completa Oculta anuncios en pantalla completa. - Cerrar anuncios en pantalla completa - "Si está habilitado, los anuncios a pantalla completa se cierran mediante el botón Cerrar. -Si está deshabilitado, se bloquean los anuncios a pantalla completa. (puede haber efectos secundarios)" - Se han bloqueado anuncios en pantalla completa. (Tipo de diálogo: %s) - Se han cerrado anuncios en pantalla completa. Ocultar anuncios generales Oculta anuncios generales. Ocultar anuncios de música @@ -64,6 +60,8 @@ Si está deshabilitado, se bloquean los anuncios a pantalla completa. (puede hab Oculta popups de promoción premium. Ocultar banner de renovación premium Oculta banner de renovación premium. + Ocultar banner de alerta de promoción + Oculta el banner de alerta de promoción. Menú desplegable Activar diálogo compacto @@ -338,6 +336,8 @@ Algunas características pueden no funcionar correctamente en la disposición de Reemplaza el dominio que está bloqueado en algunas regiones para que las miniaturas de la lista de reproducción, avatares de canales, etc. puedan ser recibidas. Cambiar la hoja de compartir Cambia la hoja de compartir en la app a la hoja de compartir del sistema. + Desactiva la animación Cairo + Deshabilita la animación de bienvenida \"Cairo\" cuando se inicia la aplicación. Activar registro de depuración Imprime el registro de depuración. Incluir búfer en registro de depuración @@ -360,6 +360,8 @@ Pulsa el botón de continuar y desactiva las optimizaciones de la batería."Continuar Desinfectar enlaces compartidos Elimina los parámetros de consulta de seguimiento de las URL al compartir enlaces. + Abrir ajustes predeterminados de la app + Para abrir los enlaces de YouTube Music en RVX Music, activa \'Abrir enlaces soportados\' y activa las direcciones web soportadas. Importar / Exportar Importar o exportar ajustes como texto. Exportar ajustes a archivo diff --git a/src/main/resources/music/translations/fr-rFR/strings.xml b/src/main/resources/music/translations/fr-rFR/strings.xml index 831740bdf5..6527593172 100644 --- a/src/main/resources/music/translations/fr-rFR/strings.xml +++ b/src/main/resources/music/translations/fr-rFR/strings.xml @@ -1,7 +1,8 @@ - + ReVanced Extended + Réinitialiser les valeurs par défaut. Redémarrer pour charger l\'interface correctement Appliquer et redémarrer ? @@ -49,11 +50,6 @@ Veuillez télécharger %2$s à partir du site web." Publicités Masquer les publicités en plein écran Masque les publicités en plein écran. - Fermer les publicités en plein écran - "Si activé, les publicités en plein écran seront fermées grâce au bouton \"Fermer\". -Si désactivé, Les publicités en plein écran seront bloquées. (peut avoir des effets secondaires)" - La publicité en plein écran a été bloquée. (Type de dialogue : %s) - La publicité en plein écran a été fermée. Masquer les publicités générales Masque les publicités générales. Masquer les publicités musicales @@ -255,8 +251,8 @@ Certaines fonctions peuvent ne pas fonctionner sur l'ancienne mise en page."Enregistre la dernière qualité vidéo sélectionnée. Afficher un message Afficher un message lorsque vous modifiez la qualité vidéo par défaut. - Les vitesses personnalisées doivent être inférieures à %sx. Valeur réinitialisée par défaut. - Vitesses de lecture invalides. Valeur réinitialisée par défaut. + Les vitesses personnalisées doivent être inférieures à %sx. + Vitesses de lecture invalides. Vitesse de lecture modifiée par %s. La résolution sur les données mobiles a été modifiée par %s. Impossible de définir la qualité. @@ -329,7 +325,7 @@ Certaines fonctions peuvent ne pas fonctionner sur l'ancienne mise en page."Couleur (hex) : Couleur modifiée. Couleur réinitialisée. - Code couleur invalide. Couleur réinitialisée par défaut. + Code couleur invalide. Réinitialiser la couleur Les données sont fournies par l\'API SponsorBlock. Cliquez ici pour en savoir plus et voir les téléchargements pour d\'autres plateformes. À propos @@ -340,6 +336,8 @@ Certaines fonctions peuvent ne pas fonctionner sur l'ancienne mise en page."Remplace le domaine qui est bloqué dans certaines régions afin que les miniatures des listes de lecture, les avatars des chaînes, etc. puissent être reçus. Modifier la fiche de partage Remplace la fiche de partage de l\'appli par celui du système. + Désactiver l\'animation Cairo au démarrage + Désactive l\'animation Cairo lors du démarrage de l\'application. Activer le journal de débogage Enregistrer le journal de débogage. Activer les informations sur la mémoire tampon dans le journal de débogage @@ -366,6 +364,8 @@ Cliquez sur le bouton Continuer et désactivez les optimisations de la batterie. Continuer Nettoyer les liens partagés Supprime les paramètres de suivi (tracking) des URL lors du partage de liens. + Ouvrir les paramètres par défaut de l\'application + Pour ouvrir les liens YouTube Music dans RVX Music, activez \'Ouvrir les liens compatibles\' et activez les adresses web prises en charge. Importer / Exporter les paramètres Importe ou exporte les paramètres. Exporter les paramètres vers un fichier diff --git a/src/main/resources/music/translations/hu-rHU/strings.xml b/src/main/resources/music/translations/hu-rHU/strings.xml index 92c12d5db5..7d984d7eef 100644 --- a/src/main/resources/music/translations/hu-rHU/strings.xml +++ b/src/main/resources/music/translations/hu-rHU/strings.xml @@ -1,6 +1,6 @@ - + ReVanced Extended Indítsd újra az elrendezés normál betöltéséhez @@ -49,11 +49,6 @@ Töltsd le a(z) %2$s weboldalról." Hirdetések Teljes képernyős hirdetések elrejtése Teljes képernyős hirdetések elrejtése. - Teljes képernyős hirdetések bezárása - "Ha engedélyezve van, akkor a teljes képernyő hírdetéseket a bezárás gombbal lehet eltüntetni. -Ha tiltva van, akkor blokkolja a t. k. hírdetéseket. (lehetnek mellékhatások)" - Teljes képernyős hírdetések blokkolva. (DialogTípus: %s) - Teljes képernyős hírdetések bezárva. Általános hirdetések elrejtése Elrejti az általános hirdetéseket. Zenei hirdetések elrejtése @@ -64,6 +59,8 @@ Ha tiltva van, akkor blokkolja a t. k. hírdetéseket. (lehetnek mellékhatások Elrejti a felugró prémium hírdetéseket. Prémium megújítás szalaghírdetés elrejtése Elrejti a prémium megújítás szalaghírdetést. + Promóciós figyelmeztető banner elrejtése + Promóciós figyelmeztető banner elrejtése. Felugró menü Kompakt menü engedélyezése diff --git a/src/main/resources/music/translations/id-rID/strings.xml b/src/main/resources/music/translations/id-rID/strings.xml index fba07240a2..92249c0827 100644 --- a/src/main/resources/music/translations/id-rID/strings.xml +++ b/src/main/resources/music/translations/id-rID/strings.xml @@ -1,6 +1,6 @@ - + ReVanced Extended Mulai ulang untuk memuat layout secara normal @@ -49,11 +49,6 @@ Download %2$s dari website." Iklan Sembunyikan iklan fullscreen Menyembunyikan iklan fullscreen. - Tutup iklan fullscreen - "Jika diaktifkan, iklan fullscreen akan ditutup lewat tombol Close. -Jika dimatikan, iklan fullscreen akan di block. (kemungkinan akan ada side effect)" - Iklan fullscreen telah diblok. (Blokir: %s) - Iklan fullscreen telah ditutup. Sembunyikan Iklan Umum Menyembunyikan Iklan Umum. Sembunyikan iklan musik diff --git a/src/main/resources/music/translations/in/strings.xml b/src/main/resources/music/translations/in/strings.xml index fba07240a2..92249c0827 100644 --- a/src/main/resources/music/translations/in/strings.xml +++ b/src/main/resources/music/translations/in/strings.xml @@ -1,6 +1,6 @@ - + ReVanced Extended Mulai ulang untuk memuat layout secara normal @@ -49,11 +49,6 @@ Download %2$s dari website." Iklan Sembunyikan iklan fullscreen Menyembunyikan iklan fullscreen. - Tutup iklan fullscreen - "Jika diaktifkan, iklan fullscreen akan ditutup lewat tombol Close. -Jika dimatikan, iklan fullscreen akan di block. (kemungkinan akan ada side effect)" - Iklan fullscreen telah diblok. (Blokir: %s) - Iklan fullscreen telah ditutup. Sembunyikan Iklan Umum Menyembunyikan Iklan Umum. Sembunyikan iklan musik diff --git a/src/main/resources/music/translations/it-rIT/strings.xml b/src/main/resources/music/translations/it-rIT/strings.xml index 38ffb6401d..68fa2a0709 100644 --- a/src/main/resources/music/translations/it-rIT/strings.xml +++ b/src/main/resources/music/translations/it-rIT/strings.xml @@ -1,6 +1,6 @@ - + Nascondi il menu dell\'account diff --git a/src/main/resources/music/translations/ja-rJP/strings.xml b/src/main/resources/music/translations/ja-rJP/strings.xml index f0282c331b..4d811c9c86 100644 --- a/src/main/resources/music/translations/ja-rJP/strings.xml +++ b/src/main/resources/music/translations/ja-rJP/strings.xml @@ -1,6 +1,6 @@ - + ReVanced Extended 再起動してレイアウトを正常に読み込みます @@ -49,11 +49,6 @@ 広告 全画面広告を非表示 全画面広告を非表示にします。 - 全画面広告を閉じる - "有効の場合、全画面広告は閉じるボタンで閉じられます。 -無効にすると、全画面広告はブロックされます(バグが発生するかもしれません)" - 全画面広告をブロックしました(ダイアログの種類: %s) - 全画面広告を閉じました 一般広告を非表示 一般広告を非表示にします。 音楽の広告を非表示 @@ -64,6 +59,8 @@ プレミアムプロモーションポップアップを非表示にします。 プレミアム更新バナーを非表示 プレミアム更新バナーを非表示にします。 + プロモーションバナーを非表示 + プロモーションバナーを非表示にします。 フライアウトメニュー コンパクトなダイアログ @@ -338,6 +335,8 @@ YT Music の設定だけでなく、ReVanced Extended の設定も非表示に プレイリストのサムネイルやチャンネルアバターなどを受信できるように、一部の地域でブロックされているドメインを置き換えます。 共有メニューを変更 アプリ内共有メニューからシステムの共有メニューに置き換えます。 + Cairo スプラッシュアニメーションを無効にする + アプリ起動時のCairo のスプラッシュアニメーションを無効にします。 デバッグログ デバッグログを出力します。 デバッグバッファログを有効化 diff --git a/src/main/resources/music/translations/ko-rKR/strings.xml b/src/main/resources/music/translations/ko-rKR/strings.xml index b651c21ad6..2d9447280a 100644 --- a/src/main/resources/music/translations/ko-rKR/strings.xml +++ b/src/main/resources/music/translations/ko-rKR/strings.xml @@ -1,7 +1,8 @@ - + ReVanced Extended 설정 + 기본값으로 초기화합니다. 레이아웃을 정상적으로 불러오기 위해 다시 시작합니다. 새로고침 및 다시 시작 @@ -49,11 +50,6 @@ 광고 전체 화면 광고 제거 전체 화면 광고를 숨깁니다. - 전체 화면 광고 닫기 - "활성화하면 닫기 버튼을 누르면 전체 화면 광고가 닫혀집니다. -비활성화하면 전체 화면 광고가 차단됩니다. (문제점이 발생할 수 있습니다.)" - 전체 화면 광고가 차단되었습니다. (다이얼로그 타입: %s) - 전체 화면 광고가 닫혔습니다. 일반 레이아웃 광고 제거 일반 레이아웃 광고를 숨깁니다. 음악 광고 제거 @@ -250,14 +246,14 @@ YT Music 설정 메뉴뿐만 아니라 ReVanced Extended 설정 메뉴도 숨겨 사용하고 싶은 재생 속도 값을 추가하거나 변경할 수 있습니다. 재생 속도 저장 활성화 재생 속도 값을 변경할 때마다 저장합니다. - 팝업 메시지 표시하기 + 팝업 메시지 표시 기본 동영상 재생 속도 값으로 변경되었을 때, 팝업 메시지를 표시합니다. 동영상 품질 저장 활성화 동영상 품질 값을 변경할 때마다 저장합니다. - 팝업 메시지 표시하기 + 팝업 메시지 표시 기본 동영상 화질 값으로 변경되었을 때, 팝업 메시지를 표시합니다. - 사용자 정의 재생 속도는 %s배속보다 작아야 하므로 기본값으로 초기화합니다. - 잘못된 재생 속도 값이므로 기본값으로 초기화합니다. + 사용자 정의 재생 속도는 %s배속보다 작아야 합니다. + 잘못된 재생 속도 값입니다. 기본 재생 속도 값을 %s으로 변경합니다. 모바일 네트워크 이용 시 기본 동영상 품질 값을 %s로 변경합니다. 동영상 품질을 설정할 수 없습니다. @@ -265,20 +261,20 @@ YT Music 설정 메뉴뿐만 아니라 ReVanced Extended 설정 메뉴도 숨겨 Return YouTube Dislike Return YouTube Dislike 활성화 - 싫어요 개수를 표시합니다. - 싫어요 개수를 퍼센트로 표시 - 싫어요 개수를 숫자가 아닌 퍼센트로 표시합니다. + 싫어요 수를 표시합니다. + 싫어요 수를 퍼센트로 표시 + 싫어요 수를 숫자가 아닌 퍼센트로 표시합니다. 좋아요 버튼에서 구분선 제거 좋아요 버튼에서 구분선을 숨깁니다. API를 사용할 수 없을 때 팝업 메시지 표시 ReturnYouTubeDislike를 사용할 수 없을 때, 팝업 메시지를 표시합니다. 정보 ReturnYouTubeDislike.com - 싫어요 개수의 데이터는 Return YouTube Dislike API에 의해 제공됩니다. 자세한 내용을 보려면 여기를 누르세요. - 싫어요 개수를 일시적으로 표시할 수 없습니다 (응답 시간 초과). - 싫어요 개수를 표시할 수 없습니다 (상태 코드: %d). - 싫어요 개수를 표시할 수 없습니다. (클라이언트 API 제한 도달) - 싫어요 개수를 표시할 수 없습니다 (%s). + 싫어요 수의 데이터는 Return YouTube Dislike API에 의해 제공됩니다. 자세한 내용을 보려면 여기를 누르세요. + 싫어요 수를 일시적으로 표시할 수 없습니다 (응답 시간 초과). + 싫어요 수를 표시할 수 없습니다 (상태 코드: %d). + 싫어요 수를 표시할 수 없습니다. (클라이언트 API 제한 도달) + 싫어요 수를 표시할 수 없습니다 (%s). SponsorBlock SponsorBlock 활성화 @@ -330,14 +326,14 @@ YT Music 설정 메뉴뿐만 아니라 ReVanced Extended 설정 메뉴도 숨겨 색상: 설정한 색상을 적용하였습니다. 색상을 초기화하였습니다. - 잘못된 헥스 코드이므로 기본값으로 초기화합니다. + 잘못된 헥스 코드입니다. 색상 초기화 건너뛸 구간의 데이터는 SponsorBlock API에 의해 제공됩니다. 자세한 내용을 보려면 여기를 누르세요. 정보 sponsor.ajay.app 기타 - 이미지 표시 제한 국가 우회하기 + 이미지 표시 제한 국가 우회 이미지 도메인을 변경하여 일부 국가에서 차단된 재생목록 썸네일, 채널 프로필 사진, 커뮤니티 게시물 이미지 등을 수신할 수 있습니다. 공유 시트 변경 YT Music 기본 공유 시트에서 Android 기본 공유 시트로 변경합니다.\n\n• 공유 버튼으로 바로 Android 기본 공유 메뉴를 실행할 수 있습니다. @@ -369,6 +365,8 @@ YT Music 설정 메뉴뿐만 아니라 ReVanced Extended 설정 메뉴도 숨겨 계속하기 추적 쿼리를 제거한 링크 공유 링크를 공유할 때, URL에서 추적 쿼리 매개변수를 제거합니다. + 기본 앱 설정 열기 + YT Music 링크를 RVX Music으로 열려면 \'지원되는 링크 열기\'를 활성화하고 지원되는 링크를 추가하세요. 링크 추가가 잠겨있다면 순정 YT Music 앱 정보 → \'기본적으로 열기\'에서 \'지원되는 링크 열기\'를 비활성화한 후에 추가할 수 있습니다. 설정 가져오기 / 내보내기 설정을 가져오거나 내보낼 수 있습니다. 파일로 설정 내보내기 diff --git a/src/main/resources/music/translations/nl-rNL/strings.xml b/src/main/resources/music/translations/nl-rNL/strings.xml index bf2c545561..10775280d6 100644 --- a/src/main/resources/music/translations/nl-rNL/strings.xml +++ b/src/main/resources/music/translations/nl-rNL/strings.xml @@ -1,6 +1,6 @@ - + ReVanced ExtExtended Start opnieuw op om de lay-out normaal te laden diff --git a/src/main/resources/music/translations/pl-rPL/strings.xml b/src/main/resources/music/translations/pl-rPL/strings.xml index 3fd0ec1e5e..a286e2d996 100644 --- a/src/main/resources/music/translations/pl-rPL/strings.xml +++ b/src/main/resources/music/translations/pl-rPL/strings.xml @@ -1,7 +1,8 @@ - + ReVanced Extended + Przywrócono domyślne wartości. Uruchom ponownie, aby wczytać układ poprawnie Odśwież i uruchom ponownie @@ -49,11 +50,6 @@ Pobierz %2$s ze strony." Reklamy Ukryj reklamy pełnoekranowe Ukrywa reklamy pełnoekranowe - Zamknij pełnoekranowe reklamy - "Jeśli opcja jest włączona, pełnoekranowe reklamy są zamykane poprzez przycisk zamknięcia. -Jeśli opcja jest wyłączona, pełnoekranowe reklamy są blokowane (mogą wystąpić efekty uboczne)" - Zablokowano reklamy pełnoekranowe. (Typ okna: %s) - Zamknięto reklamy pełnoekranowe. Ukryj ogólne reklamy Ukrywa ogólne reklamy. Ukryj reklamy multimedialne @@ -255,8 +251,8 @@ Niektóre ustawienia mogą nie działać poprawnie ze starym układem odtwarzacz Zapisuje ostatnią wybraną jakość teledysku. Komunikaty o zmianie domyślnej jakości teledysków Komunikaty będą wyświetlane po zmianie domyślnej jakości teledysków. - Nieprawidłowe niestandardowe prędkości odtwarzania. Przywrócono domyślne wartości. - Nieprawidłowe niestandardowe prędkości odtwarzania. Przywrócono domyślne wartości. + Niestandardowe prędkości muszą być mniejsze niż %sx. + Nieprawidłowe niestandardowe prędkości odtwarzania. Zmieniono domyślną prędkość odtwarzania na %s. Zmieniono domyślną jakość podczas używania sieci mobilnej na %s. Jakość nie została ustawiona. @@ -329,7 +325,7 @@ Niektóre ustawienia mogą nie działać poprawnie ze starym układem odtwarzacz Kolor: Kolor został zmieniony. Kolor został zresetowany. - Nieprawidłowy kod koloru. Przywrócono domyślny kolor. + Nieprawidłowy kod koloru. Zresetuj kolor Dane są dostarczane przez API SponsorBlock. Stuknij tutaj, aby dowiedzieć się więcej i pobrać na inne platformy. O integracji @@ -368,6 +364,8 @@ Kontynuuj i wyłącz optymalizację baterii." Kontynuuj Oczyść udostępniane linki Usuwa parametry śledzących zapytań z adresów URL podczas udostępniania linków. + Otwórz systemowe ustawienia aplikacji + Aby otwierać linki YouTube Music w RVX Music, przejdź do opcji obsługiwanych linków w ustawieniach i włącz obsługiwane adresy internetowe dla RVX. Importuj/Eksportuj ustawienia Zaimportuj lub wyeksportuj ustawienia Wyeksportuj ustawienia do pliku diff --git a/src/main/resources/music/translations/pt-rBR/strings.xml b/src/main/resources/music/translations/pt-rBR/strings.xml index 802de6bbfc..01186c0be3 100644 --- a/src/main/resources/music/translations/pt-rBR/strings.xml +++ b/src/main/resources/music/translations/pt-rBR/strings.xml @@ -1,7 +1,8 @@ - + ReVanced Extended + Redefinir para os valores padrão. Reinicie para carregar o layout normalmente Atualizar e reiniciar @@ -49,11 +50,6 @@ Por favor, baixe %2$s do site." Anúncios Ocultar anúncios em tela cheia Oculta anúncios em tela cheia. - Fechar anúncios em tela cheia - "Se estiver ativado, os anúncios em tela cheia são fechados através do botão Fechar. -Se estiver desativado, os anúncios em tela cheia serão bloqueados. (pode haver efeitos colaterais)" - Os anúncios em tela cheia foram bloqueados. (Tipo de diálogo: %s) - Os anúncios de tela cheia foram fechados. Ocultar anúncios gerais Oculta anúncios gerais. Ocultar anúncios de mídia @@ -255,8 +251,8 @@ Alguns recursos podem não funcionar corretamente no layout antigo do reprodutor Lembra a última qualidade de vídeo selecionada. Exibir uma notificação flutuante Exibir uma notificação flutuante quando alterar a qualidade padrão do vídeo. - Velocidades personalizadas devem ser menores que %sx. Usando valores padrão. - Velocidade personalizada de reprodução inválida. Usando valores padrão. + Velocidades personalizadas devem ser menores que %sx. + Velocidades de reprodução personalizadas inválidas. Alterando a velocidade padrão para %s. Alterando a qualidade padrão de dados móveis para %s. Falha ao definir qualidade. @@ -329,7 +325,7 @@ Alguns recursos podem não funcionar corretamente no layout antigo do reprodutor Cor: Cor alterada. Redefinir cor. - Código de cor inválido. Redefinir cor para o padrão. + Código de cor inválido. Redefinir cor Os dados são fornecidos pela API do SponsorBlock. Toque aqui para aprender mais e ver downloads para outras plataformas. Sobre @@ -340,6 +336,8 @@ Alguns recursos podem não funcionar corretamente no layout antigo do reprodutor Substitui o domínio que está bloqueado em algumas regiões para que miniaturas para playlists, avatares de canais, etc. possam ser recebidos. Alterar menu de compartilhamento Alterar o menu de compartilhamento do app para o meno de compartilhamento do sistema. + Desativar a animação inicial do Cairo + Desabilita a animação inicial do Cairo quando o aplicativo é iniciado. Ativar o relatório de depuração Imprime o relatório de depuração Ativar o registro de depuração do buffer @@ -366,6 +364,8 @@ Toque no botão continuar e desative as otimizações da bateria." Continuar Limpar links compartilhados Remove os parâmetros de consulta de rastreamento das URLs ao compartilhar os links. + Abrir configurações padrão do aplicativo + Para abrir os links de música do YouTube no RVX Music, ative \'Abrir links suportados\' e ative os endereços web suportados. Importar / Exportar configurações Importar ou exportar as configurações como texto. Exportar configurações para um arquivo diff --git a/src/main/resources/music/translations/ro-rRO/strings.xml b/src/main/resources/music/translations/ro-rRO/strings.xml index e268254daf..8bb126d264 100644 --- a/src/main/resources/music/translations/ro-rRO/strings.xml +++ b/src/main/resources/music/translations/ro-rRO/strings.xml @@ -1,6 +1,6 @@ - + Ascunde meniul contului diff --git a/src/main/resources/music/translations/ru-rRU/strings.xml b/src/main/resources/music/translations/ru-rRU/strings.xml index 1417269289..ddbe67a1cb 100644 --- a/src/main/resources/music/translations/ru-rRU/strings.xml +++ b/src/main/resources/music/translations/ru-rRU/strings.xml @@ -1,6 +1,6 @@ - + ReVanced Extended Перезапустите для правильной загрузки интерфейса @@ -49,11 +49,6 @@ Реклама Полноэкранная реклама Скрывает полноэкранную рекламу. - Закрывать полноэкранную рекламу - "Если включено, полноэкранная реклама закрывается через кнопку Закрыть. -Если отключено, полноэкранная реклама блокируется. (могут возникать побочные эффекты)" - Полноэкранная реклама была заблокирована. (Тип диалога: %s) - Полноэкранная реклама была закрыта. Скрыть рекламу общего формата Скрывает рекламу общего формата. Скрыть музыкальную рекламу @@ -64,6 +59,8 @@ Скрывает всплывающую рекламу Premium. Скрыть баннер продления Premium Скрывает баннер продления Premium. + Скрыть баннер с уведомлением о промо акции + Скрывает баннер с уведомлением о промо акции. Выдвижное меню Компактный вид окна @@ -336,8 +333,10 @@ Разное Обойти ограничения изображений по региону Заменяет заблокированный в некоторых регионах домен, чтобы можно было получать миниатюры плейлистов, аватары каналов и т. д. - Изменить список \"Поделиться\" - Меняет встроенное диалоговое окно \"Поделиться\" на системное. + Изменить окно \"Поделиться\" + Меняет тип диалогового окна \"Поделиться\" из встроенного на системное. + Отключить анимацию Кайро + Отключает анимацию Кайро при запуске приложения. Ведение журнала отладки Выводит журнал отладки. Ведение журналов отладки буфера diff --git a/src/main/resources/music/translations/tr-rTR/strings.xml b/src/main/resources/music/translations/tr-rTR/strings.xml index 6ebe3d43c5..9f119440ba 100644 --- a/src/main/resources/music/translations/tr-rTR/strings.xml +++ b/src/main/resources/music/translations/tr-rTR/strings.xml @@ -1,6 +1,6 @@ - + ReVanced Extended Düzeni normal şekilde yüklemek için yeniden başlatın @@ -49,10 +49,6 @@ Lütfen web sitesinden %2$s dosyasını indirin." Reklamlar Tam ekran reklamlarını gizle Tam ekran reklamlarını gizler. - Tam ekran reklamlarını kapat - "Etkinleştirilirse Kapat düğmesi aracılığıyla tam ekran reklamlar kapatılır. Devre dışı bırakılırsa tam ekran reklamlar engellenir. (yan etkiler olabilir)" - Tam ekran reklamlar engellendi. (İletişim Türü: %s) - Tam ekran reklamlar kapatıldı. Genel reklamları gizle Genel reklamları gizler. Müzik reklamlarını gizle diff --git a/src/main/resources/music/translations/uk-rUA/strings.xml b/src/main/resources/music/translations/uk-rUA/strings.xml index 4764290f57..a7aa08dc4c 100644 --- a/src/main/resources/music/translations/uk-rUA/strings.xml +++ b/src/main/resources/music/translations/uk-rUA/strings.xml @@ -1,7 +1,8 @@ - + ReVanced Extended + Скинуто до значень за замовчуванням. Перезапустіть, щоб нормально завантажився макет Оновити та перезавантажити? @@ -49,11 +50,6 @@ Реклама Приховати повноекранну рекламу Приховує повноекранну рекламу. - Закривати повноекранну рекламу - "Якщо ввімкнено, повноекранна реклама закривається за допомогою кнопки Закрити. -Якщо вимкнено, повноекранна реклама блокується. (можуть бути побічні ефекти)" - Повноекранну рекламу заблоковано. (Тип діалогу: %s) - Повноекранну рекламу закрито. Приховати загальну рекламу Приховує загальну рекламу. Приховати медіарекламу @@ -255,8 +251,8 @@ Запам\'ятовує останню вибрану якість відео. Показувати тост Показує тост під час зміни стандартної якості відео. - Користувацькі швидкості мають бути меншими за %sx. Швидкості скинуто до стандартних значень. - Неправильні користувацькі швидкості відтворення. Використовуються типові значення. + Користувацькі швидкості мають бути меншими за %sx. + Неправильні користувацькі швидкості відтворення. Зміна типової швидкості на %s. Зміна типової якості відео в мобільній мережі на %s. Не вдалося встановити обрану якість. @@ -329,7 +325,7 @@ Колір: Колір змінено. Колір скинуто. - Недійсний код кольору. Колір скинуто до стандартного. + Недійсний код кольору. Скинути колір Дані надаються SponsorBlock API. Натисніть тут, щоб дізнатися більше та побачити завантаження для інших платформ. Про інтеграцію @@ -339,7 +335,9 @@ Змінити домен зображень Замінює домен для зображень, заблокований у деяких регіонах, що дозволить отримувати мініатюри списків відтворення, аватари каналів тощо. Змінити діалог поширення - Змінює тип вікна діалогу поширення з вбудованого до системного. + Змінює тип вікна діалогу поширення з вбудованого на системний. + Вимкнути сплеш анімацію Каїр + Вимикає сплеш анімацію Каїр під час запуску застосунку. Увімкнути протоколи налагодження Виводить протокол налагодження. Увімкнути ведення журналу буфера налагодження @@ -366,6 +364,8 @@ Продовжити Обробляти поширення посилань Видаляє параметри запиту відстеження з URL-адрес під час обміну посиланнями. + Відкрити налаштування за замовчуванням + Щоб відкривати посилання на YouTube Music у RVX Music, увімкніть \"Відкривати підтримувані посилання\" та активуйте підтримувані веб-адреси. Імпорт / Експорт налаштувань Імпортує або експортує налаштування. Експорт налаштувань у файл diff --git a/src/main/resources/music/translations/vi-rVN/strings.xml b/src/main/resources/music/translations/vi-rVN/strings.xml index 02edd88bf9..97f96f3526 100644 --- a/src/main/resources/music/translations/vi-rVN/strings.xml +++ b/src/main/resources/music/translations/vi-rVN/strings.xml @@ -1,7 +1,8 @@ - + Cài đặt ReVanced Extended + Đặt lại về giá trị mặc định. Vui lòng khởi động lại ứng dụng để các tính năng hoạt động bình thường Làm mới và khởi động lại @@ -49,11 +50,6 @@ Quảng cáo Ẩn quảng cáo toàn màn hình Ẩn quảng cáo toàn màn hình. - Đóng quảng cáo toàn màn hình - "Nếu tính năng này bật, quảng cáo toàn màn hình sẽ được đóng thông qua nút Đóng. -Nếu tính năng này tắt, quảng cáo toàn màn hình sẽ bị chặn (có thể có tác dụng phụ)." - Quảng cáo toàn màn hình đã bị chặn. (Loại hộp thoại: %s) - Quảng cáo toàn màn hình đã được đóng. Ẩn quảng cáo chung Ẩn quảng cáo xuất hiện trước khi phát. Ẩn quảng cáo @@ -255,8 +251,8 @@ Lưu ý:\n- Tuỳ chọn này sẽ thay đổi giao diện ứng dụng, tuy nhi Ghi nhớ chất lượng video nhạc được chọn gần đây nhất. Hiện một thông báo ngắn Hiện một thông báo ngắn khi thay đổi chất lượng mặc định của video. - Tốc độ phát tuỳ chỉnh không hợp lệ. Đã đặt lại tốc độ phát về mặc định. - Tốc độ phát tùy chỉnh không hợp lệ. Đã khôi phục lại các giá trị mặc định. + Tốc độ phát tuỳ chỉnh phải nhỏ hơn %sx. + Tốc độ phát tùy chỉnh không hợp lệ. Đã lưu tốc độ phát mặc định thành %s. Đã lưu chất lượng video mặc định trên mạng di động thành %s. Đặt chất lượng video thất bại. @@ -329,7 +325,7 @@ Lưu ý:\n- Tuỳ chọn này sẽ thay đổi giao diện ứng dụng, tuy nhi Màu: Đã thay đổi màu phân đoạn. Đã đặt lại màu phân đoạn về mặc định. - Mã màu không hợp lệ. Đã đặt lại màu về mặc định. + Mã màu không hợp lệ. Đặt lại màu Dữ liệu được cung cấp bởi API SponsorBlock. Nhấn vào đây để tìm hiểu thêm và xem các bản tải xuống cho các nền tảng khác. Giới thiệu @@ -362,6 +358,8 @@ Làm theo hướng dẫn 'Don't kill my app!' cho thiết bị của bạn và Tiếp tục Liên kết sạch khi chia sẻ Loại bỏ các tham số truy vấn theo dõi khỏi URL khi chia sẻ liên kết. + Mở theo mặc định + Để mở liên kết YouTube Music trong RVX Music, hãy bật \'Mở liên kết được hỗ trợ\' và kích hoạt các địa chỉ web được hỗ trợ. Nhập/Xuất cài đặt Nhập hoặc xuất các tuỳ chọn cài đặt của bạn. Xuất cài đặt dưới dạng tệp diff --git a/src/main/resources/music/translations/zh-rCN/strings.xml b/src/main/resources/music/translations/zh-rCN/strings.xml index 69ab604d25..d02e2d4ccf 100644 --- a/src/main/resources/music/translations/zh-rCN/strings.xml +++ b/src/main/resources/music/translations/zh-rCN/strings.xml @@ -1,6 +1,6 @@ - + ReVanced Extended 重启应用以正常加载界面布局 @@ -49,11 +49,6 @@ 广告 隐藏全屏广告 隐藏全屏广告 - 关闭全屏广告 - "如果启用,全屏广告将通过关闭按钮关闭 -如果禁用,全屏广告将被屏蔽(可能有副作用)" - 全屏广告已屏蔽(对话类型: %s) - 全屏广告已关闭 隐藏一般广告 隐藏一般广告 音乐广告 diff --git a/src/main/resources/music/translations/zh-rTW/strings.xml b/src/main/resources/music/translations/zh-rTW/strings.xml index 101aa700db..f022707e61 100644 --- a/src/main/resources/music/translations/zh-rTW/strings.xml +++ b/src/main/resources/music/translations/zh-rTW/strings.xml @@ -1,6 +1,6 @@ - + ReVanced 擴充功能 重新啟動以套用更改後的介面 diff --git a/src/main/resources/music/visual/icons/extension/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/visual/icons/extension/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..987e224f2a --- /dev/null +++ b/src/main/resources/music/visual/icons/extension/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/music/visual/icons/gear/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/visual/icons/gear/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..984b1fc6df --- /dev/null +++ b/src/main/resources/music/visual/icons/gear/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/music/visual/icons/revanced/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/visual/icons/revanced/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..cbf886b16a --- /dev/null +++ b/src/main/resources/music/visual/icons/revanced/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/music/visual/icons/revanced_colored/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/visual/icons/revanced_colored/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..d48f055ba4 --- /dev/null +++ b/src/main/resources/music/visual/icons/revanced_colored/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,12702 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/pref_key_parent_tools_icon.xml b/src/main/resources/music/visual/shared/drawable/pref_key_parent_tools_icon.xml new file mode 100644 index 0000000000..f541848b44 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/pref_key_parent_tools_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/revanced_extended_settings_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_extended_settings_icon.xml new file mode 100644 index 0000000000..987e224f2a --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_extended_settings_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_account_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_account_icon.xml new file mode 100644 index 0000000000..1cb2414bc3 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_account_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_action_bar_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_action_bar_icon.xml new file mode 100644 index 0000000000..b36ded4134 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_action_bar_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_ads_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_ads_icon.xml new file mode 100644 index 0000000000..5e124ea662 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_ads_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_flyout_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_flyout_icon.xml new file mode 100644 index 0000000000..86e0717fac --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_flyout_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_general_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_general_icon.xml new file mode 100644 index 0000000000..e1879d9889 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_general_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_misc_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_misc_icon.xml new file mode 100644 index 0000000000..5399e53bbc --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_misc_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_navigation_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_navigation_icon.xml new file mode 100644 index 0000000000..9b04311d85 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_navigation_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_player_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_player_icon.xml new file mode 100644 index 0000000000..b2579b49af --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_player_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_ryd_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_ryd_icon.xml new file mode 100644 index 0000000000..5d46c6930d --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_ryd_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_sb_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_sb_icon.xml new file mode 100644 index 0000000000..9371175a70 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_sb_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_video_icon.xml b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_video_icon.xml new file mode 100644 index 0000000000..0cee63a17f --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/revanced_preference_screen_video_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/settings_header_about_youtube_music_icon.xml b/src/main/resources/music/visual/shared/drawable/settings_header_about_youtube_music_icon.xml new file mode 100644 index 0000000000..66a8987d7a --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/settings_header_about_youtube_music_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/settings_header_data_saving_icon.xml b/src/main/resources/music/visual/shared/drawable/settings_header_data_saving_icon.xml new file mode 100644 index 0000000000..7b219966e4 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/settings_header_data_saving_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/settings_header_downloads_and_storage_icon.xml b/src/main/resources/music/visual/shared/drawable/settings_header_downloads_and_storage_icon.xml new file mode 100644 index 0000000000..ade43ef0bc --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/settings_header_downloads_and_storage_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/settings_header_general_icon.xml b/src/main/resources/music/visual/shared/drawable/settings_header_general_icon.xml new file mode 100644 index 0000000000..e1879d9889 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/settings_header_general_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/settings_header_notifications_icon.xml b/src/main/resources/music/visual/shared/drawable/settings_header_notifications_icon.xml new file mode 100644 index 0000000000..d2a5b1d2e8 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/settings_header_notifications_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/settings_header_paid_memberships_icon.xml b/src/main/resources/music/visual/shared/drawable/settings_header_paid_memberships_icon.xml new file mode 100644 index 0000000000..bf3cab7bde --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/settings_header_paid_memberships_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/settings_header_playback_icon.xml b/src/main/resources/music/visual/shared/drawable/settings_header_playback_icon.xml new file mode 100644 index 0000000000..79dfcf9444 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/settings_header_playback_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/settings_header_privacy_and_location_icon.xml b/src/main/resources/music/visual/shared/drawable/settings_header_privacy_and_location_icon.xml new file mode 100644 index 0000000000..45d371fdb1 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/settings_header_privacy_and_location_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/music/visual/shared/drawable/settings_header_recommendations_icon.xml b/src/main/resources/music/visual/shared/drawable/settings_header_recommendations_icon.xml new file mode 100644 index 0000000000..b57590f8c3 --- /dev/null +++ b/src/main/resources/music/visual/shared/drawable/settings_header_recommendations_icon.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/afn_blue/settings/drawable/revanced_extended_settings_key_icon.xml b/src/main/resources/youtube/branding/afn_blue/settings/drawable/revanced_extended_settings_key_icon.xml index ede82d9f1f..c420684c34 100644 --- a/src/main/resources/youtube/branding/afn_blue/settings/drawable/revanced_extended_settings_key_icon.xml +++ b/src/main/resources/youtube/branding/afn_blue/settings/drawable/revanced_extended_settings_key_icon.xml @@ -1,2032 +1,29 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:height="48dp" + android:viewportWidth="740" + android:viewportHeight="740"> + + + + diff --git a/src/main/resources/youtube/branding/afn_blue/splash/drawable/avd_anim.xml b/src/main/resources/youtube/branding/afn_blue/splash/drawable/avd_anim.xml new file mode 100644 index 0000000000..9512159953 --- /dev/null +++ b/src/main/resources/youtube/branding/afn_blue/splash/drawable/avd_anim.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/youtube/branding/afn_blue/splash/values-v31/styles.xml b/src/main/resources/youtube/branding/afn_blue/splash/values-v31/styles.xml new file mode 100644 index 0000000000..c7462f74ae --- /dev/null +++ b/src/main/resources/youtube/branding/afn_blue/splash/values-v31/styles.xml @@ -0,0 +1,7 @@ + + + + diff --git a/src/main/resources/youtube/branding/afn_red/settings/drawable/revanced_extended_settings_key_icon.xml b/src/main/resources/youtube/branding/afn_red/settings/drawable/revanced_extended_settings_key_icon.xml index bc9942311e..97c066bd83 100644 --- a/src/main/resources/youtube/branding/afn_red/settings/drawable/revanced_extended_settings_key_icon.xml +++ b/src/main/resources/youtube/branding/afn_red/settings/drawable/revanced_extended_settings_key_icon.xml @@ -1,2232 +1,29 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:height="48dp" + android:viewportWidth="740" + android:viewportHeight="740"> + + + + diff --git a/src/main/resources/youtube/branding/afn_red/splash/drawable/avd_anim.xml b/src/main/resources/youtube/branding/afn_red/splash/drawable/avd_anim.xml new file mode 100644 index 0000000000..877e9452b6 --- /dev/null +++ b/src/main/resources/youtube/branding/afn_red/splash/drawable/avd_anim.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/youtube/branding/afn_red/splash/values-v31/styles.xml b/src/main/resources/youtube/branding/afn_red/splash/values-v31/styles.xml new file mode 100644 index 0000000000..c7462f74ae --- /dev/null +++ b/src/main/resources/youtube/branding/afn_red/splash/values-v31/styles.xml @@ -0,0 +1,7 @@ + + + + diff --git a/src/main/resources/youtube/branding/mmt/settings/drawable/revanced_extended_settings_key_icon.xml b/src/main/resources/youtube/branding/mmt/settings/drawable/revanced_extended_settings_key_icon.xml index 085d98c173..13a1298ec5 100644 --- a/src/main/resources/youtube/branding/mmt/settings/drawable/revanced_extended_settings_key_icon.xml +++ b/src/main/resources/youtube/branding/mmt/settings/drawable/revanced_extended_settings_key_icon.xml @@ -1,3288 +1,25 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + android:height="48dp" + android:viewportWidth="326" + android:viewportHeight="326"> + + + + diff --git a/src/main/resources/youtube/branding/revancify_blue/settings/drawable/revanced_extended_settings_key_icon.xml b/src/main/resources/youtube/branding/revancify_blue/settings/drawable/revanced_extended_settings_key_icon.xml index c088bcfcc8..5f052277f4 100644 --- a/src/main/resources/youtube/branding/revancify_blue/settings/drawable/revanced_extended_settings_key_icon.xml +++ b/src/main/resources/youtube/branding/revancify_blue/settings/drawable/revanced_extended_settings_key_icon.xml @@ -1,1034 +1,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:android="http://schemas.android.com/apk/res/android" + android:name="vector" + android:width="48dp" + android:height="48dp" + android:viewportWidth="384" + android:viewportHeight="384"> + + + + + diff --git a/src/main/resources/youtube/branding/revancify_red/settings/drawable/revanced_extended_settings_key_icon.xml b/src/main/resources/youtube/branding/revancify_red/settings/drawable/revanced_extended_settings_key_icon.xml index 98cfb9cccc..668ea4cc6f 100644 --- a/src/main/resources/youtube/branding/revancify_red/settings/drawable/revanced_extended_settings_key_icon.xml +++ b/src/main/resources/youtube/branding/revancify_red/settings/drawable/revanced_extended_settings_key_icon.xml @@ -1,1026 +1,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:android="http://schemas.android.com/apk/res/android" + android:name="vector" + android:width="48dp" + android:height="48dp" + android:viewportWidth="384" + android:viewportHeight="384"> + + + + + diff --git a/src/main/resources/youtube/branding/youtube/settings/drawable/revanced_extended_settings_key_icon.xml b/src/main/resources/youtube/branding/youtube/settings/drawable/revanced_extended_settings_key_icon.xml index 924dc807c2..274b2e64f7 100644 --- a/src/main/resources/youtube/branding/youtube/settings/drawable/revanced_extended_settings_key_icon.xml +++ b/src/main/resources/youtube/branding/youtube/settings/drawable/revanced_extended_settings_key_icon.xml @@ -1,5 +1,25 @@ - - - - + + + + + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_premium_wordmark_header_dark.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_premium_wordmark_header_dark.png new file mode 100644 index 0000000000..5ba804ae05 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_premium_wordmark_header_dark.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_premium_wordmark_header_light.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_premium_wordmark_header_light.png new file mode 100644 index 0000000000..5ba804ae05 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_premium_wordmark_header_light.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_wordmark_header_dark.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_wordmark_header_dark.png new file mode 100644 index 0000000000..5ba804ae05 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_wordmark_header_dark.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_wordmark_header_light.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_wordmark_header_light.png new file mode 100644 index 0000000000..5ba804ae05 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-hdpi/yt_wordmark_header_light.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_premium_wordmark_header_dark.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_premium_wordmark_header_dark.png new file mode 100644 index 0000000000..3542f98f51 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_premium_wordmark_header_dark.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_premium_wordmark_header_light.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_premium_wordmark_header_light.png new file mode 100644 index 0000000000..3542f98f51 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_premium_wordmark_header_light.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_wordmark_header_dark.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_wordmark_header_dark.png new file mode 100644 index 0000000000..3542f98f51 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_wordmark_header_dark.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_wordmark_header_light.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_wordmark_header_light.png new file mode 100644 index 0000000000..3542f98f51 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-mdpi/yt_wordmark_header_light.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_premium_wordmark_header_dark.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_premium_wordmark_header_dark.png new file mode 100644 index 0000000000..c12303a7d3 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_premium_wordmark_header_dark.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_premium_wordmark_header_light.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_premium_wordmark_header_light.png new file mode 100644 index 0000000000..c12303a7d3 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_premium_wordmark_header_light.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_wordmark_header_dark.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_wordmark_header_dark.png new file mode 100644 index 0000000000..c12303a7d3 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_wordmark_header_dark.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_wordmark_header_light.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_wordmark_header_light.png new file mode 100644 index 0000000000..c12303a7d3 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xhdpi/yt_wordmark_header_light.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_premium_wordmark_header_dark.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_premium_wordmark_header_dark.png new file mode 100644 index 0000000000..aece8cfd94 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_premium_wordmark_header_dark.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_premium_wordmark_header_light.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_premium_wordmark_header_light.png new file mode 100644 index 0000000000..aece8cfd94 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_premium_wordmark_header_light.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_wordmark_header_dark.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_wordmark_header_dark.png new file mode 100644 index 0000000000..aece8cfd94 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_wordmark_header_dark.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_wordmark_header_light.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_wordmark_header_light.png new file mode 100644 index 0000000000..aece8cfd94 Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxhdpi/yt_wordmark_header_light.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_premium_wordmark_header_dark.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_premium_wordmark_header_dark.png new file mode 100644 index 0000000000..0ba37b7c0c Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_premium_wordmark_header_dark.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_premium_wordmark_header_light.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_premium_wordmark_header_light.png new file mode 100644 index 0000000000..0ba37b7c0c Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_premium_wordmark_header_light.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_wordmark_header_dark.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_wordmark_header_dark.png new file mode 100644 index 0000000000..0ba37b7c0c Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_wordmark_header_dark.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_wordmark_header_light.png b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_wordmark_header_light.png new file mode 100644 index 0000000000..0ba37b7c0c Binary files /dev/null and b/src/main/resources/youtube/branding/youtube_minimal_header/header/drawable-xxxhdpi/yt_wordmark_header_light.png differ diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/settings/drawable/revanced_extended_settings_key_icon.xml b/src/main/resources/youtube/branding/youtube_minimal_header/settings/drawable/revanced_extended_settings_key_icon.xml new file mode 100644 index 0000000000..274b2e64f7 --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/settings/drawable/revanced_extended_settings_key_icon.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__1__0.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__1__0.xml new file mode 100644 index 0000000000..35eb1ac386 --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__1__0.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__1__1.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__1__1.xml new file mode 100644 index 0000000000..35eb1ac386 --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__1__1.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__2__0.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__2__0.xml new file mode 100644 index 0000000000..d6566bd9ff --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__2__0.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__2__1.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__2__1.xml new file mode 100644 index 0000000000..d6566bd9ff --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__2__1.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__3__0.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__3__0.xml new file mode 100644 index 0000000000..d6566bd9ff --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__3__0.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__3__1.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__3__1.xml new file mode 100644 index 0000000000..d6566bd9ff --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$$avd_anim__3__1.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__0.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__0.xml new file mode 100644 index 0000000000..4d44591966 --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__0.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__1.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__1.xml new file mode 100644 index 0000000000..6e99759e16 --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__1.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__2.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__2.xml new file mode 100644 index 0000000000..15d9885e98 --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__2.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__3.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__3.xml new file mode 100644 index 0000000000..8d6ff8a2ec --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__3.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__4.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__4.xml new file mode 100644 index 0000000000..d4e2228621 --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/$avd_anim__4.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/avd_anim.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/avd_anim.xml new file mode 100644 index 0000000000..49266b4f4c --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/drawable/avd_anim.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/youtube/branding/youtube_minimal_header/splash/values-v31/styles.xml b/src/main/resources/youtube/branding/youtube_minimal_header/splash/values-v31/styles.xml new file mode 100644 index 0000000000..c7462f74ae --- /dev/null +++ b/src/main/resources/youtube/branding/youtube_minimal_header/splash/values-v31/styles.xml @@ -0,0 +1,7 @@ + + + + diff --git a/src/main/resources/youtube/settings/host/values/arrays.xml b/src/main/resources/youtube/settings/host/values/arrays.xml index a329bc5554..616c6bc4b1 100644 --- a/src/main/resources/youtube/settings/host/values/arrays.xml +++ b/src/main/resources/youtube/settings/host/values/arrays.xml @@ -25,9 +25,9 @@ @string/revanced_change_start_page_entry_default @string/revanced_change_start_page_entry_search + @string/revanced_change_start_page_entry_shorts @string/revanced_change_start_page_entry_subscriptions @string/revanced_change_start_page_entry_explore - @string/revanced_change_start_page_entry_shorts @string/revanced_change_start_page_entry_library @string/revanced_change_start_page_entry_liked_videos @string/revanced_change_start_page_entry_watch_later @@ -41,24 +41,24 @@ @string/revanced_change_start_page_entry_browse - - - open.search - open.subscriptions - open.explore - open.shorts - - www.youtube.com/feed/library - www.youtube.com/playlist?list=LL - www.youtube.com/playlist?list=WL - www.youtube.com/feed/history - www.youtube.com/feed/trending - www.youtube.com/gaming - www.youtube.com/channel/UC4R8DWoMoI7CAwX8_LjQHig - www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ - www.youtube.com/feed/storefront?bp=ogUCKAI%3D - www.youtube.com/channel/UCEgdi0XIXXZ-qJOFPf4JSKw - www.youtube.com/feed/guide_builder + ORIGINAL + + SEARCH + SHORTS + + SUBSCRIPTIONS + EXPLORE + LIBRARY + LIKED_VIDEO + WATCH_LATER + HISTORY + TRENDING + GAMING + LIVE + MUSIC + MOVIE + SPORTS + BROWSE @string/revanced_change_shorts_repeat_state_entry_default @@ -185,6 +185,24 @@ 17.41.37 17.33.42 + + YouTube Music + + + com.google.android.apps.youtube.music + + + -1 + 0 + +1 + +2 + + + -1 + 0 + 1 + 2 + @string/revanced_watch_history_type_entry_1 @string/revanced_watch_history_type_entry_2 diff --git a/src/main/resources/youtube/settings/host/values/strings.xml b/src/main/resources/youtube/settings/host/values/strings.xml index a3f41b233c..3826d132dd 100644 --- a/src/main/resources/youtube/settings/host/values/strings.xml +++ b/src/main/resources/youtube/settings/host/values/strings.xml @@ -7,6 +7,7 @@ ReVanced Extended Search %s + Reset to default values. Experimental Flags Do you wish to proceed? @@ -29,13 +30,6 @@ Please download %2$s from the website." Hide fullscreen ads Fullscreen ads are hidden. Fullscreen ads are shown. - Close fullscreen ads - Fullscreen ads are closed through the Close button. - "Fullscreen ads are blocked. - -Side effect: Community post images may be blocked in fullscreen." - Fullscreen ads have been blocked. (DialogType: %s) - Fullscreen ads have been closed. Hide general ads General ads are hidden. General ads are shown. @@ -88,6 +82,7 @@ Tap here to learn more about DeArrow." Toast is not shown if DeArrow is unavailable. DeArrow API endpoint The URL of the DeArrow thumbnail cache endpoint. + Invalid DeArrow API URL. Still video captures Still captures are taken from the beginning, middle, or end of each video. These images are built into YouTube and no external API is used. Use fast still captures @@ -112,6 +107,7 @@ Tap here to learn more about DeArrow." Hide album cards Album cards are hidden. Album cards are shown. + Hide carousel shelf "Hides the following shelves: • Breaking news • Continue watching @@ -119,13 +115,15 @@ Tap here to learn more about DeArrow." • Listen again • Shopping • Watch it again" - Hide carousel shelf Hide chips shelf Chips shelf is hidden. Chips shelf is shown. Hide expandable chip under videos Expandable chips are hidden. Expandable chips are shown. + Hide expandable shelves + Expandable shelves are hidden. + Expandable shelves are shown. Hide feed captions button Captions button is hidden. Captions button is shown. @@ -277,20 +275,22 @@ Limitations: • Videos with phrases such as 'People also watched' underneath." Hide low views video Hide videos with less than 1,000 views from home feeds that have been uploaded from unsubscribed channels. + Hide UPCOMING video + "Hide videos with UPCOMING label. - - Duration filter - Hide videos based on duration - Hide videos with shorter than or longer than duration.\n\nKnown issue: It will not hide videos in the player related videos, instead it will hide the timestamp. - Longer than duration - Videos with durations longer than this number will be hidden. - Shorter than duration - Videos with durations shorter than this number will be hidden. +Note: Enabling this also hide Notify me button." View count filter - Hide recommended videos by views - Hide recommended videos with less than a specified number of views.\n\nKnown issue: Videos with 0 views are not filtered. + Hide home videos by views + Videos in home feed are filtered. + Videos in home feed are not filtered. + Hide search results by views + Search results are filtered. + Search results are not filtered. + Hide subscription videos by views + Videos in subscriptions feed are filtered. + Videos in subscriptions feed are not filtered. Greater than views Videos with views greater than this number will be hidden. Less than views @@ -298,6 +298,19 @@ Limitations: View keys Specify your language template for the number of views shown under each video in the user interface. Each key (a letter/word in your language) -> value (meaning of the key) must be on a new line. Keys go before "->" sign. If you change the app or system language you need to reset this setting.\n\nExamples:\nEnglish: 10K views = K -> 1000, views -> views\nSpanish: 10 K vistas = K -> 1000, vistas -> views K -> 1 000\nM -> 1 000 000\nB -> 1 000 000 000\nviews -> views + About view count filtering + "Home / Subscription / Search results are filtered to hide videos with views less or greater than a specified number. + +Limitations: +• Shorts cannot be hidden. +• Videos with 0 views are not filtered." + Hide related videos + Related videos are hidden. + Related videos are shown. + "This setting limits the maximum number of layouts that can be loaded on the player screen. + +If the layout of the player screen changes due to server-side changes, unintended layouts may be hidden on the player screen." + Offset @@ -320,7 +333,11 @@ Limitations: Subscriptions Trending Watch later - Invalid start page, resetting to default. + Change start page type + "Start page always changes. + +Limitation: Back button on the toolbar may not work." + Start page changes only once. Disable forced auto audio tracks Forced auto audio tracks are disabled. Forced auto audio tracks are enabled. @@ -352,15 +369,6 @@ This does not bypass the age restriction. It just accepts it automatically."Enable tablet layout Spoofs the dpi to use some tablet layouts. - Override video download button - Native video download button opens your external downloader. - Native video download button opens the native in-app downloader. - Override playlist download button - Native playlist download button is always shown, and in public playlists, it opens your external downloader. - If shown, the native playlist download button opens the native in-app downloader. - Playlist downloader package name - Package name of your installed external downloader app, such as YTDLnis. - Spoof app version Version spoofed Version not spoofed @@ -405,6 +413,34 @@ Some components may not be hidden." List of component path builder strings to filter, separated by new lines. Invalid custom filter: %s. + + Hook buttons + Overrides the click action of in-app buttons. + + + Download button + + Override video download button + Native video download button opens your external downloader. + Native video download button opens the native in-app downloader. + Override playlist download button + Native playlist download button is always shown, and in public playlists, it opens your external downloader. + If shown, the native playlist download button opens the native in-app downloader. + Playlist downloader package name + Package name of your installed external downloader app, such as YTDLnis. + + + Override YouTube Music button + YouTube Music button opens the RVX Music. + YouTube Music button opens the native app. + RVX Music package name + Package name of installed RVX Music. + RVX Music + Warning + %s is not installed. Please install it. + Prerequisite + YouTube Music is required to override button action. Tap here to download YouTube Music. + Miniplayer Change the style of the in app minimized player. @@ -436,11 +472,11 @@ Some components may not be hidden." Skip forward and back are shown. Overlay opacity Opacity value between 0-100, where 0 is transparent. - Miniplayer overlay opacity must be between 0-100. Reset to default values. + Miniplayer overlay opacity must be between 0-100. - - Navigation buttons - Hide or show navigation bar section components. + + Navigation bar + Hide or show navigation bar section components. Enable narrow navigation buttons Spacing between navigation buttons is narrow. @@ -467,15 +503,22 @@ Some components may not be hidden." Navigation labels are hidden. Navigation labels are shown. Swap Create and Notifications buttons - "Swaps the positions of the Create button with the Notifications button by spoofing device information. + "Create button is switched with Notifications button. + +Note: Enabling this also forcibly hides video ads." + Create button is not switched with Notifications button. + "Disabling this might load more ads from the server. -• The device may need to be rebooted for a change of this setting to take effect. -• Disabling this setting loads more ads from the server side. -• You should disable this setting to make video ads visible." +Also, ads will no longer be blocked in Shorts. + +If this setting do not take effect, try switching to Incognito mode." Enable translucent navigation bar Navigation bar is translucent. Navigation bar is opaque. + Hide navigation bar + Navigation bar is hidden. + Navigation bar is shown. Settings menu @@ -539,7 +582,7 @@ Tap and hold to open RVX settings." Custom player overlay opacity Opacity value between 0-100, where 0 is transparent. - Player overlay opacity must be between 0-100. Reset to default values. + Player overlay opacity must be between 0-100. Disable player popup panels Auto player popup panels are disabled. Auto player popup panels are enabled. @@ -558,7 +601,7 @@ Note: • Disabling this setting does not forcefully enable the speed overlay." Speed overlay value Speed overlay value between 0-8.0. - Speed overlay value must be between 0-8.0. Reset to default values. + Speed overlay value must be between 0-8.0. Hide channel watermark Channel watermark is hidden. Channel watermark is shown. @@ -829,7 +872,7 @@ Limitation: Video title disappears when clicked." Share button is shown. Quick actions top margin Configure the spacing from the seekbar to the quick action container, between 0-32. - Quick actions top margin must be between 0-32. Reset to default values. + Quick actions top margin must be between 0-32. Disable landscape mode @@ -1091,30 +1134,6 @@ Side effect: Official headers in search results will be hidden." Hide Shopping button Shopping button is hidden. Shopping button is shown. - Hide Shop button - Shop button is hidden. - Shop button is shown. - Hide Super Thanks button - Super Thanks button is hidden. - Super Thanks button is shown. - Hide tagged products - Tagged products are hidden. - Tagged products are shown. - Hide location button - Location button is hidden. - Location button is shown. - Hide save sound to playlist button - Save sound to playlist button is hidden. - Save sound to playlist button is shown. - Hide search suggestions button - Search suggestions button is hidden. - Search suggestions button is shown. - Hide Use this sound button - Use this sound button is hidden. - Use this sound button is shown. - Hide Use template button - Use template button is hidden. - Use template button is shown. Hide info panels Info panels are hidden. Info panels are shown. @@ -1134,6 +1153,37 @@ Side effect: Official headers in search results will be hidden." Video link label is hidden. Video link label is shown. + + Suggested actions + + Hide Green screen button + Green screen button is hidden. + Green screen button is shown. + Hide Save sound button + Save sound button is hidden. + Save sound button is shown. + Hide Shop button + Shop button is hidden. + Shop button is shown. + Hide Super Thanks button + Super Thanks button is hidden. + Super Thanks button is shown. + Hide Use this sound button + Use this sound button is hidden. + Use this sound button is shown. + Hide Use template button + Use template button is hidden. + Use template button is shown. + Hide location button + Location button is hidden. + Location button is shown. + Hide search suggestions button + Search suggestions button is hidden. + Search suggestions button is shown. + Hide tagged products + Tagged products are hidden. + Tagged products are shown. + Action buttons Hide Like button @@ -1157,6 +1207,9 @@ Side effect: Official headers in search results will be hidden." Animation / Feedback + Disable Like button animation + Fountain animation is disabled above the Like button. + Fountain animation is enabled above the Like button. Hide Play & Pause button background Button background is hidden. Button background is shown. @@ -1180,17 +1233,21 @@ Limitations: Press and hold the timestamp to change the Shorts repeat status. Meta panel bottom margin Configure the spacing from the seekbar to the meta panel, between 0-64. - Meta panel bottom margin must be between 0-64. Reset to default values. + Meta panel bottom margin must be between 0-64. Hide toolbar Toolbar is hidden. Toolbar is shown. Hide navigation bar Navigation bar is hidden. Navigation bar is shown. + Height percentage of empty space + Configures the height percentage of the empty space left when the navigation bar is hidden, between 0 and 100 (%). + Height percentage must be between 0-100 (%). Replace channel handle Channel name is used. Channel handle is used. + Swipe controls @@ -1223,7 +1280,7 @@ Limitations: The text size for swipe overlay. Swipe overlay screen size Percentage of swipeable screen area.\n\nNote: This will also change the size of the screen area for the double-tap-to-seek gesture. - Swipeable area size cannot be more than 50. Reset to default value. + Swipeable area size cannot be more than 50. Swipe overlay timeout The amount of milliseconds the overlay is visible. Disable auto HDR brightness @@ -1295,6 +1352,13 @@ Info: Spoof device dimensions "Spoofs the device dimensions to the maximum value. High quality may be unlocked on some videos that require high device dimensions, but not all videos." + Disable VP9 codec + "VP9 codec is disabled. + +• Maximum resolution is 1080p. +• Video playback will use more internet data than VP9. +• VP9 codec is still used for HDR video." + VP9 codec is enabled. Replace software AV1 codec Replaces the software AV1 codec with the VP9 codec. Reject software AV1 codec response @@ -1305,8 +1369,8 @@ A different codec will be applied after about 20 seconds of buffering." Changing default mobile data quality to %s. Failed to set video quality. Changing default Wi-Fi quality to %s. - Custom speeds must be less than %sx. Using default values. - Invalid custom playback speeds. Using default values. + Custom speeds must be less than %sx. + Invalid custom playback speeds. @@ -1529,6 +1593,7 @@ Limitation: Dislikes may not appear if the user is not logged in or in incognito Username successfully changed. Your reputation is <b>%.2f</b> You\'ve created <b>%s</b> segments + Tap here to view your segments. SponsorBlock leaderboard You\'ve saved people from <b>%s</b> segments Tap here to see the global stats and top contributors. @@ -1633,9 +1698,13 @@ Tap on the continue button and disable battery optimizations." TV HTML5 Web Spoofing side effects - • Movies or paid videos may not play. + "• Movies or paid videos may not play. +• Livestreams start from the beginning. +• Videos may end 1 second early. +• No opus audio codec." • Audio track menu is missing. - • Audio track menu is missing. + "• Audio track menu is missing. +• Stable volume is not available." • Video may not play. Force iOS AVC (H.264) iOS video codec is AVC (H.264). @@ -1683,6 +1752,7 @@ AVC (H.264) has a maximum resolution of 1080p, and video playback will use more Revancify Blue Revancify Red YouTube + YouTube (Minimal header) Stock Excluded Included diff --git a/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/src/main/resources/youtube/settings/xml/revanced_prefs.xml index f627560bc7..833cac28ab 100644 --- a/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -5,7 +5,6 @@ + + + + + + + + @@ -152,7 +176,7 @@ SETTINGS: MINIPLAYER_TYPE_MODERN --> + + + SETTINGS: HIDE_NAVIGATION_COMPONENTS --> - + + SETTINGS: CHANGE_START_PAGE --> @@ -232,12 +259,6 @@ SETTINGS: LAYOUT_SWITCH --> - - @@ -590,7 +616,8 @@ - PREFERENCE_SCREEN: VIDEO --> + + PREFERENCE_SCREEN: VIDEO --> @@ -602,12 +629,58 @@ + + + + + + + + + + + PREFERENCE_SCREEN: RETURN_YOUTUBE_DISLIKE --> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PREFERENCE_SCREEN: SPONSOR_BLOCK --> @@ -753,6 +826,7 @@ + diff --git a/src/main/resources/youtube/translations/ar/strings.xml b/src/main/resources/youtube/translations/ar/strings.xml index 9e48899b25..78ca457f3d 100644 --- a/src/main/resources/youtube/translations/ar/strings.xml +++ b/src/main/resources/youtube/translations/ar/strings.xml @@ -6,10 +6,11 @@ ReVanced Extended بحث %s + إعادة التعيين إلى القيم الافتراضية. تعديلات تجريبية هل ترغب في المتابعة؟ إعادة التشغيل لتحميل التخطيط بشكل طبيعي - تحديث و إعادة تشغيل + تحديث وإعادة تشغيل عادي اسم حزمة تنزيل الفيديو اسم الحزمة لتطبيق التنزيل الخارجي المثبت لديك، مثل NewPipe أو YTDLnis. @@ -23,13 +24,6 @@ إخفاء إعلانات ملء الشاشة تم إخفاء إعلانات ملء الشاشة. يتم عرض إعلانات ملء الشاشة. - إغلاق الإعلانات بملء الشاشة - يتم إغلاق الإعلانات بملء الشاشة من خلال زر الإغلاق. - "يتم حظر الإعلانات بملء الشاشة. - -التأثير الجانبي: قد يتم حظر صور منشورات المجتمع في وضع ملء الشاشة." - تم حظر الإعلانات بملء الشاشة. (نوع الحوار: %s) - تم إغلاق الإعلانات بملء الشاشة. إخفاء الإعلانات العامة تم إخفاء الإعلانات العامة. يتم عرض الإعلانات العامة. @@ -79,6 +73,7 @@ لا يتم عرض ملاحظة إذا كان DeArrow غير متوفر. DeArrow API Endpoint The URL of The DeArrow Thumbnail Cache Endpoint. + URL الخاص بـ DeArrow API غير صالح. لقطات الفيديو الثابتة يتم التقاط اللقطات الثابتة من بداية أو منتصف أو نهاية كل فيديو. هذه الصور مدمجة في YouTube ولا يتم استخدام أي واجهة برمجة تطبيقات خارجية. استخدام اللقطات الثابتة السريعة @@ -100,6 +95,7 @@ إخفاء بطاقات الألبوم تم إخفاء بطاقات الألبوم. يتم عرض بطاقات الألبوم. + إخفاء الرف الدائري "يخفي الرفوف التالية: • أخبار عاجلة • متابعة المشاهدة @@ -107,13 +103,15 @@ • استمع مجددا • التسوق • مشاهده مرة أخرى" - إخفاء الرف الدائري إخفاء رف الشرائح تم إخفاء رف الشرائح. يتم عرض رف الشرائح. إخفاء الشريحة القابلة للتوسع تحت مقاطع الفيديو تم إخفاء الرقائق القابلة للتوسيع. يتم عرض الرقائق القابلة للتوسيع. + إخفاء الرفوف القابلة للتوسع + تم إخفاء الرفوف القابلة للتوسع. + يتم عرض الرفوف القابلة للتوسع. إخفاء زر التَرْجَمَة في الموجز تم إخفاء زر التَرْجَمَة. يتم عرض زر التَرْجَمَة. @@ -254,18 +252,21 @@ • مقاطع فيديو تحتوي على عبارات مثل 'شاهد الأشخاص أيضًا' أسفلها." إخفاء فيديو المشاهدات المنخفضة إخفاء مقاطع الفيديو التي حصلت على أقل من 1000 مشاهدة من موجز الصفحة الرئيسية التي تم تحميلها من القنوات غير المشترك بها. - - فلترة المـدة - إخفاء الفيديوهات بناءً على المـدة - إخفاء مقاطع الفيديو التي تقل مدتها عن المدة أو أطول منها.\n\nمشكلة معروفة: لن يؤدي ذلك إلى إخفاء مقاطع الفيديو في مقاطع الفيديو ذات الصلة بالمشغل، بل سيخفي الطابع الزمني بدلاً من ذلك. - الأطول من المـدة - سيتم إخفاء الفيديوهات التي تزيد مدتها عن هذا الرقم. - الأقصر من المـدة - سيتم إخفاء الفيديوهات التي تقل مدتها عن هذا الرقم. + إخفاء الفيديو القادم + "إخفاء الفيديوهات التي تحمل علامة \"قادم\". + +ملاحظة: يؤدي تمكين هذا أيضًا إلى إخفاء زر \"تنبيهي\"." تصفية عدد المشاهدات - إخفاء الفيديوهات الموصى بها عن طريق المشاهدات - إخفاء الفيديوهات الموصى بها التي حصلت على عدد أقل من عدد معين من المشاهدات.\n\nمشكلة معروفة: لا يتم تصفية الفيديوهات التي حصلت على 0 مشاهدة. + إخفاء فيديوهات الصفحة الرئيسية حسب عدد المشاهدات + يتم تصفية الفيديوهات في موجز الصفحة الرئيسية. + لا يتم تصفية الفيديوهات في موجز الصفحة الرئيسية. + إخفاء نتائج البحث حسب عدد المشاهدات + يتم تصفية نتائج البحث. + لا يتم تصفية نتائج البحث. + إخفاء فيديوهات الاشتراكات حسب عدد المشاهدات + يتم تصفية الفيديوهات في موجز الاشتراكات. + لا يتم تصفية الفيديوهات في موجز الاشتراكات. الأكبر من المشاهدات سيتم إخفاء الفيديوهات التي تزيد مشاهداتها عن هذا الرقم. الأقل من المشاهدات @@ -273,6 +274,19 @@ عرض المفاتيح حدد قالب اللغة الخاص بك لعدد مرات المشاهدة الموضحة أسفل كل فيديو في واجهة المستخدم. كل مفتاح (حرف/كلمة في لغتك) -> قيمة (معنى المفتاح) يجب أن يكون على سطر جديد. المفاتيح تذهب قبل علامة \"->\". إذا قمت بتغيير التطبيق أو لغة النظام، فستحتاج إلى إعادة تعيين هذا الإعداد.\n\nأمثلة:\nEnglish: 10K views = K -> 1000, views -> views\nSpanish: 10 K vistas = K -> 1000, vistas -> views K -> 1 000\nM -> 1 000 000\nB -> 1 000 000 000\nمشاهد -> مشاهد + حول تصفية عدد المشاهدات + "الصفحة الرئيسية / الاشتراكات / تتم تصفية نتائج البحث لإخفاء مقاطع الفيديو التي حصلت على عدد مشاهدات أقل أو أكبر من عدد محدد. + +القيود: + +• لا يمكن إخفاء فيديوهات Shorts. +• لا تتم تصفية الفيديوهات التي حصلت على 0 مشاهدة." + إخفاء الفيديوهات ذات الصلة + تم إخفاء الفيديوهات ذات الصلة. + يتم عرض الفيديوهات ذات الصلة. + "يحد هذا الإعداد من الحد الأقصى لعدد التخطيطات التي يمكن تحميلها على شاشة المشغل. + +إذا تغير تخطيط شاشة المشغل بسبب تغييرات على جانب الخادم، فقد يتم إخفاء التخطيطات غير المقصودة على شاشة المشغل." عام تغيير صفحة البداية @@ -292,7 +306,11 @@ الاشتراكات المحتوى الرائج المشاهدة لاحقًا - صفحة البداية غير صالحة، تتم إعادة التعيين إلى الوضع الافتراضي. + تغيير نوع صفحة البداية + "تتغير صفحة البدء دائمًا. + +التقييد: قد لا يعمل زر الرجوع الذي على شريط الأدوات." + تتغير صفحة البدء مرة واحدة فقط. تعطيل المقطع الصوتي التلقائي المفروض تم تعطيل المقطع الصوتي التلقائي المفروض. تم تمكين المقطع الصوتي التلقائي المفروض. @@ -320,14 +338,6 @@ محاكاة Dpi لاستخدام بعض تخطيطات الجوال. تمكين تخطيط الجهاز اللوحي محاكاة Dpi لاستخدام بعض تخطيطات الجهاز اللوحي. - تجاوز زر تنزيل الفيديو - يفتح زر تنزيل الفيديو الأصلي أداة التنزيل الخارجية. - يفتح زر تنزيل الفيديو أداة التنزيل الأصلية داخل التطبيق. - تجاوز زر تنزيل قائمة التشغيل - يتم دائمًا عرض زر تنزيل قائمة التشغيل الأصلية، وفي قوائم التشغيل العامة، يتم فتح أداة التنزيل الخارجية لديك. - إذا تم عرضه، فإن زر تنزيل قائمة التشغيل الأصلية يفتح أداة التنزيل الأصلية داخل التطبيق. - اسم حزمة تنزيل قائمة التشغيل - اسم الحزمة لتطبيق التنزيل الخارجي المثبت لديك، مثل YTDLnis. إصدار تطبيق وهمي تم تغيير اصدار التطبيق لم يتم تغيير اصدار التطبيق @@ -367,6 +377,30 @@ قائمة سلاسل منشئ مسار المكونات المراد تصفيتها، مفصولة بسطور جديدة. فلتر مخصص غير صالح: %s. + + أزرار الإرتباط + يتجاوز إجراء النقر على الأزرار الموجودة داخل التطبيق. + + زر التنزيل + تجاوز زر تنزيل الفيديو + يفتح زر تنزيل الفيديو الأصلي أداة التنزيل الخارجية. + يفتح زر تنزيل الفيديو أداة التنزيل الأصلية داخل التطبيق. + تجاوز زر تنزيل قائمة التشغيل + يتم دائمًا عرض زر تنزيل قائمة التشغيل الأصلية، وفي قوائم التشغيل العامة، يتم فتح أداة التنزيل الخارجية لديك. + إذا تم عرضه، فإن زر تنزيل قائمة التشغيل الأصلية يفتح أداة التنزيل الأصلية داخل التطبيق. + اسم حزمة تنزيل قائمة التشغيل + اسم الحزمة لتطبيق التنزيل الخارجي المثبت لديك، مثل YTDLnis. + + تجاوز زر موسيقى YouTube + زر موسيقى YouTube يفتح موسيقى RVX. + زر موسيقى YouTube يفتح التطبيق الأصلي. + اسم حزمة موسيقى RVX + اسم الحزمة لموسيقى RVX المثبتة. + موسيقى RVX + تحذير + %s لم يتم تثبيته. الرجاء تثبيته. + متطلب أساسي + موسيقى YouTube مطلوبًا لتجاوز إجراء الزر. انقر هنا لتنزيل موسيقى YouTube. المشغل المصغر تغيير نمط المشغل المصغر داخل التطبيق. @@ -397,10 +431,10 @@ يتم عرض التخطي للأمام والخلف. شفافية الواجهة قيمة الشفافية بين 0-100، حيث يكون 0 شفاف. - يجب أن تكون نسبة شفافية واجهة المشغل المصغر بين 0-100. إعادة التعيين إلى القيم الافتراضية. - - أزرار التنقل - إخفاء أو عرض مكونات قسم شريط التنقل. + يجب أن تكون نسبة شفافية واجهة المشغل المصغر بين 0-100. + + شريط التنقل + إخفاء أو عرض مكونات قسم شريط التنقل. تمكين أزرار التنقل الضيقة المسافة بين أزرار التنقل أضيق. المسافة بين أزرار التنقل عادية. @@ -426,14 +460,21 @@ تم إخفاء تسميات شريط التنقل. يتم عرض تسميات شريط التنقل. تبديل أزرار الإنشاء و الإشعارات - "لتبديل مواضع زر الإنشاء و زر الإشعارات عن طريق إيهام معلومات الجهاز. + "تم تبديل زر الإنشاء بـزر الإشعارات. + +ملاحظة: يؤدي تمكين هذا أيضًا إلى إخفاء إعلانات الفيديو بالقوة." + لا يتم تبديل زر الإنشاء بزر الإشعارات. + "قد يؤدي تعطيل هذا إلى تحميل المزيد من الإعلانات من الخادم. -• قد يحتاج الجهاز إلى إعادة التشغيل حتى يسري تغيير هذا الإعداد. -• يؤدي تعطيل هذا الإعداد إلى تحميل المزيد من الإعلانات من جانب الخادم. -• يجب عليك تعطيل هذا الإعداد لجعل إعلانات الفيديو مرئية." +كما لن يتم حظر الإعلانات في فيديوهات Shorts بعد الآن. + +إذا لم يتم تفعيل هذا الإعداد، فحاول التبديل إلى وضع التصفح المتخفي." تمكين شريط التنقل الشفاف شريط التنقل شفاف. شريط التنقل غير شفاف. + إخفاء شريط التنقل + تم إخفاء شريط التنقل. + يتم عرض شريط التنقل. قائمة الإعدادات إخفاء العناصر في قائمة إعدادات YouTube. @@ -490,7 +531,7 @@ المشغل شفافية واجهة المشغل المخصصة قيمة التعتيم بين 0-100، حيث 0 شفافة. - يجب أن تكون شفافية واجهة المشغل بين 0-100. إعادة التعيين إلى القيم الافتراضية. + يجب أن تكون شفافية واجهة المشغل بين 0-100. تعطيل لوحات المشغل المنبثقة تم تعطيل لوحات المشغل المنبثقة تلقائيًا. تم تمكين لوحات المشغل المنبثقة تلقائيًا. @@ -509,7 +550,7 @@ • لا يؤدي تعطيل هذا الإعداد إلى فرض تمكين تراكب السرعة بالقوة." قيمة تراكب السرعة قيمة تراكب السرعة بين 0-8.0. - يجب أن تكون قيمة تراكب السرعة بين 0-8.0. إعادة التعيين إلى القيم الافتراضية. + يجب أن تكون قيمة تراكب السرعة بين 0-8.0. إخفاء العلامة المائية للقناة تم إخفاء علامة الفيديو المائية. يتم عرض علامة الفيديو المائية. @@ -555,12 +596,12 @@ يمكن تغيير التشغيل التلقائي في إعدادات YouTube: الإعدادات ← التشغيل التلقائي ← تشغيل الفيديو التالي تلقائيًا" يتم عرض شاشة نهاية الفيديو المقترح. - إخفاء تراكب التكبير - يتم عرض تراكب التكبير. - تم إخفاء تراكب التكبير. تخطي العد التنازلي للتشغيل التلقائي إذا تم تمكين التشغيل التلقائي، فسيتم تشغيل الفيديو التالي على الفور. إذا تم تمكين التشغيل التلقائي، فسيتم تشغيل الفيديو التالي بعد انتهاء العد التنازلي. + إخفاء تراكب التكبير + يتم عرض تراكب التكبير. + تم إخفاء تراكب التكبير. أزرار الإجراء إخفاء أو عرض أزرار الإجراءات تحت الفيديو. @@ -766,7 +807,7 @@ يتم عرض زر مشاركة. هامش إجراءات سريعة أعلى تكوين التباعد من شريط التقدم إلى حاوية الإجراء السريع، بين 0-32. - يجب أن يكون الهامش العلوي للإجراءات السريعة بين 0-32. إعادة التعيين إلى القيم الافتراضية. + يجب أن يكون الهامش العلوي للإجراءات السريعة بين 0-32. تعطيل الوضع الأفقي اتجاه الفيديو هو وضع عمودي في ملء الشاشة. @@ -1006,30 +1047,6 @@ إخفاء زر التسوق تم إخفاء زر التسوق. يتم عرض زر التسوق. - إخفاء زر التسوق - تم إخفاء زر المتجر. - يتم عرض زر المتجر. - إخفاء زر Super Thanks - تم إخفاء زر Super Thanks. - يتم عرض زر Super Thanks. - إخفاء المنتجات الموسومة - تم إخفاء المنتجات المعلمة. - يتم عرض المنتجات المعلمة. - إخفاء زر الموقع - تم إخفاء زر الموقع. - يتم عرض زر الموقع. - إخفاء زر حفظ الصوت إلى قائمة التشغيل - تم إخفاء زر حفظ الصوت في قائمة التشغيل. - يتم عرض زر حفظ الصوت في قائمة التشغيل. - إخفاء زر اقتراحات البحث - تم إخفاء زر اقتراحات البحث. - يتم عرض زر اقتراحات البحث. - إخفاء زر استخدام هذا الصوت - تم إخفاء زر استخدام هذا الصوت. - يتم عرض زر استخدام هذا الصوت. - إخفاء زر استخدام القالب - تم إخفاء زر القالب. - يتم عرض زر القالب. إخفاء لوحات المعلومات تم إخفاء لوحات المعلومات. يتم عرض لوحات المعلومات. @@ -1048,6 +1065,31 @@ إخفاء تسمية رابط الفيديو الكامل تم إخفاء تسمية رابط الفيديو. يتم عرض تسمية رابط الفيديو. + + إخفاء زر حفظ الصوت إلى قائمة التشغيل + تم إخفاء زر حفظ الصوت في قائمة التشغيل. + يتم عرض زر حفظ الصوت في قائمة التشغيل. + إخفاء زر التسوق + تم إخفاء زر المتجر. + يتم عرض زر المتجر. + إخفاء زر Super Thanks + تم إخفاء زر Super Thanks. + يتم عرض زر Super Thanks. + إخفاء زر استخدام هذا الصوت + تم إخفاء زر استخدام هذا الصوت. + يتم عرض زر استخدام هذا الصوت. + إخفاء زر استخدام القالب + تم إخفاء زر القالب. + يتم عرض زر القالب. + إخفاء زر الموقع + تم إخفاء زر الموقع. + يتم عرض زر الموقع. + إخفاء زر اقتراحات البحث + تم إخفاء زر اقتراحات البحث. + يتم عرض زر اقتراحات البحث. + إخفاء المنتجات الموسومة + تم إخفاء المنتجات المعلمة. + يتم عرض المنتجات المعلمة. أزرار الإجراء إخفاء زر أعجبني @@ -1092,13 +1134,16 @@ اضغط مع الاستمرار على الطابع الزمني لتغيير حالة تكرار فيديوهات Shorts. الهامش السفلي للوحة التعريف تكوين التباعد من شريط التقدم إلى لوحة التعريف، بين 0-64. - يجب أن يكون الهامش السفلي للوحة التعريف بين 0-64. إعادة التعيين إلى القيم الافتراضية. + يجب أن يكون الهامش السفلي للوحة التعريف بين 0-64. إخفاء شريط الأدوات تم إخفاء شريط الأدوات. يتم عرض شريط الأدوات. إخفاء شريط التنقل تم إخفاء شريط التنقل. يتم عرض شريط التنقل. + نسبة ارتفاع المساحة الفارغة + يقوم بتكوين نسبة ارتفاع المساحة الفارغة المتبقية عند إخفاء شريط التنقل، بين 0 و100 (%). + يجب أن تكون نسبة الارتفاع بين 0-100 (%). استبدال معالج القناة يتم استخدام اسم القناة. يتم استخدام معالج القناة. @@ -1139,7 +1184,7 @@ الافتراضي:27 حجم واجهة إيماءة التمرير النسبة المئوية لمساحة الشاشة القابلة للتمرير السريع.\n\nملاحظة: سيؤدي هذا أيضًا إلى تغيير حجم مساحة الشاشة لإيماءة النقر المزدوج للتقديم أو التأخير. - لا يمكن أن يزيد حجم المنطقة القابلة للتمرير السريع عن 50. إعادة التعيين إلى القيمة الافتراضية. + لا يمكن أن يزيد حجم المنطقة القابلة للتمرير السريع عن 50. مهلة واجهة إيماءة التمرير مقدار الوقت الذي تظهر فيه واجهة التمرير بعد التغيير (بجزء الثانية). @@ -1209,6 +1254,13 @@ إيهام أبعاد الجهاز "يوهم أبعاد الجهاز من أجل فتح جودة فيديو أعلى قد لا تكون متوفرة على جهازك. قد يتم توفير الجودة العالية في بعض مقاطع الفيديو التي تتطلب أبعادًا عالية للجهاز، ولكن ليس كل مقاطع الفيديو." + تعطيل ترميز VP9 + "تم تعطيل برنامج ترميز VP9. + +• الحد الأقصى للدقة هو 1080P. +• سيستهلك تشغيل الفيديو بيانات إنترنت أكثر من VP9. +• لا يزال برنامج ترميز VP9 مستخدمًا لفيديو HDR." + تم تمكين ترميز VP9. استبدال برنامج الترميز AV1 يستبدل برنامج الترميز AV1 ببرنامج الترميز VP9. رفض استجابة برنامج الترميز AV1 @@ -1219,8 +1271,8 @@ تغيير جودة بيانات الجوّال الافتراضية إلى %s. فشل في تعيين جودة الفيديو. تغيير جودة Wi-Fi الافتراضية إلى %s. - يجب أن تكون السرعات المخصصة أقل من %sx. باستخدام القيم الافتراضية. - سرعات التشغيل المخصصة غير صالحة. باستخدام القيم الافتراضية. + يجب أن تكون السرعات المخصصة أقل من %sx. + سرعات التشغيل المخصصة غير صالحة. Return YouTube Dislike تمكين Return YouTube Dislike @@ -1421,6 +1473,7 @@ تم تغيير اسم المستخدم بنجاح. سمعتك <b>%.2f</b> لقد أنشأت <b>%s</b> مقطع + اضغط هنا لعرض المقاطع الخاصة بك. SponsorBlock Leaderboard لقد قمت بحفظ الناس من <b>%s</b> مقطع انقر هنا لرؤية الإحصائيات العالمية وأبرز المساهمين. @@ -1513,9 +1566,13 @@ TV HTML5 Web التأثيرات الجانبية للتزييف - • قد لا يتم تشغيل الأفلام أو الفيديوهات المدفوعة. + "• قد لا يتم تشغيل الأفلام أو الفيديوهات المدفوعة. +• يبدأ البث المباشر من البداية. +• قد تنتهي الفيديوهات قبل النهاية بثانية واحدة. +• لا يوجد ترميز الصوت Opus." • قائمة المقطع الصوتي مفقودة. - • قائمة المقطع الصوتي مفقودة. + "• قائمة المقطع الصوتي مفقودة. +• مستوى الصوت الثابت غير متوفر." • قد لا يتم تشغيل الفيديو. فرض iOS AVC (H.264) ترميز فيديو iOS هو AVC (H.264). @@ -1557,6 +1614,7 @@ Revancify Blue Revancify Red YouTube + YouTube (Minimal header) الإفتراضي مستبعد مضمن diff --git a/src/main/resources/youtube/translations/bg-rBG/strings.xml b/src/main/resources/youtube/translations/bg-rBG/strings.xml index 39c14c67dc..ae0ea1e36b 100644 --- a/src/main/resources/youtube/translations/bg-rBG/strings.xml +++ b/src/main/resources/youtube/translations/bg-rBG/strings.xml @@ -6,6 +6,7 @@ Настройки ReVanced Търсене %s + Възстановяване на стандартните стойности. Експериментални настройки Желаете ли да продължите? Рестартирайте, за да заредите оформлението нормално @@ -23,13 +24,6 @@ Скриване на рекламите в режим на цял екран Рекламите в режим на цял екран са скрити. Рекламите в режим на цял екран са показани. - Как да затворите реклами на цял екран - Рекламите на цял екран се затварят чрез бутона Затвори. - "Рекламите на цял екран са блокирани. - -Страничен ефект: Изображенията в публикации в общността може да бъдат блокирани на цял екран." - Рекламите на цял екран са блокирани. (Тип на диалоговия прозорец: %s) - Рекламите на цял екран са затворени. Скриване на общите реклами Общите реклами са скрити. Общите реклами се показват. @@ -75,6 +69,7 @@ Не се показва известие, ако DeArrow не е наличен. DeArrow API адрес URL адресът на крайната точка за съхранение на миниатюри DeArrow. + Невалиден DeArrow API URL. Неподвижни миниатюри Неподвижните кадри се вземат от началото / средата / края на всяко видео. Тези изображения са вградени в YouTube и не се използва външен API. Използване на бързо заснемане на кадри @@ -96,19 +91,22 @@ Скриване на албумни карти Албумните карти са скрити. Албумните карти се показват. + Скриване на рафта с Препоръчани "Скрива следните рафтове: - Извънредни новини - Продължете да гледате - Разгледайте още канали - Пазаруване - Гледайте отново" - Скриване на рафта с Препоръчани Скриване на филмовите рафтове Филмовите рафтове са скрити. Филмовите рафтове се показват. Скриване на показващи се раздели под видеоклипове Падащите менюта са скрити. Показват се. + Разширяеми секции + Разширяващите се секции са скрити. + Показват се разширяеми секции. Бутон за субтити Бутона за субтити е скрит. Бутона за субтити се показва. @@ -246,18 +244,21 @@ • От канали, за които не сте абонирани (по-малко от 1000 гледания)." Скриване на видеоклипове с малко гледания Скрийте видеоклипове с по-малко от 1000 гледания от емисията и от канали, за които сте се абонирали. - - Филтриране по продължителност - Скриване на видеоклипове въз основа на тяхната продължителност - Скрийте видеоклипове, по-кратки или по-дълги от предпочитанията ви.\n\nИзвестен проблем: Не работи с предложени видеоклипове под плейъра, но ще скрие клеймото им за време. - Продължителност, по-голяма от посочената - Видеоклиповете с по-голяма продължителност ще бъдат скрити. - Ограничение за минимална продължителност - Видеоклиповете с по-малка продължителност ще бъдат скрити. + ПРЕДСТОЯЩО видео + "Скриване на видеоклипове с етикет ПРЕДСТОЯЩО. + +Забележка: Активирането на това също ще скрие бутона Уведоми ме." Филтрирайте по брой гледания - Скриване на видеоклипове въз основа на броя гледания - Скрийте видеоклиповете с по-малко или повече гледания от предпочитанията ви.\n\nИзвестен проблем: Видеоклиповете с 0 гледания не са правилно филтрирани. + Фильтр за видео в \"Начало\" по гледания + Видеоклиповете в раздела Начало се филтрират. + Видеоклиповете в раздела Начало не се филтрират. + Филтриране на резултатите от търсенето + Резултатите от търсенето се филтрират. + Резултатите от търсенето не се филтрират. + Видеоклипове в раздела Абонаменти по показвания + Видеоклиповете в емисията за абонаменти се филтрират. + Видеоклиповете в емисията за абонаменти не се филтрират. Ограничение за максимален брой гледания Видеоклиповете с повече гледания от този брой няма да бъдат показани. Минимално ограничение за гледане @@ -265,6 +266,14 @@ Ключове за редактиране Езиков шаблон за брой изгледи (букви/дума на вашия език) -> стойност (ключова стойност) трябва да е на нов ред преди знака \"->\". Ако превключите езика на приложението или системата, трябва да нулирате тази настройка.\n\nПримери:\nEnglish: 10K views = K -> 1000, views -> views\nБългарски: 10 K показвания = K -> 1000, показвания -> views K -> 1 000\nM -> 1 000 000\nB -> 1 000 000 000\nviews -> views + Относно филтрирането на броя показвания + "Начало / Абонамент / Резултатите от търсенето се филтрират, за да скрият видеоклипове с гледания, по-малки или по-големи от определен брой." + Скриване в сродни видеоклипове + Сродните клипове са скрити. + Сродните клипове се показват. + "Тази настройка ограничава максималния брой оформления, които могат да бъдат заредени на екрана на плейъра. + +Ако оформлението на екрана на плейъра се промени поради промени от страна на сървъра, нежеланите оформления може да бъдат скрити на екрана на плейъра." Основни настройки Промяна на началната страница @@ -284,7 +293,11 @@ Абонаменти Популярни За Гледане по-късно - Невалидна начална страница, нулиране по подразбиране. + Промяна на типа на началната страница + "Началната страница винаги се променя. + +Ограничение: Бутонът за връщане назад в лентата с инструменти може да не работи." + Началната страница се променя само веднъж. Принудителните автоматични аудио пътеки са деактивирани Задължителните аудио записи са деактивирани. Задължителните аудио записи са активирани. @@ -312,14 +325,6 @@ Подлъгване на dpi за оформление като на телефон. Вкл. на режим за таблет Подлъгване на dpi за оформление като на таблет. - Действие на бутона \"Изтегляне\" за видео - Бутонът за изтегляне от YouTube отваря вашата външна програма за изтегляне. - Бутонът за изтегляне на YouTube отваря собствената програма за изтегляне на приложението. - Преназначаване на бутона за изтегляне на плейлист - Бутонът за изтегляне от YouTube отваря вашата външна програма за изтегляне. - Бутонът за изтегляне на YouTube отваря собствената програма за изтегляне на приложението. - Име на приложението за изтегляне - Име на пакета на приложението за изтегляне като NewPipe или YTDLnis. Променете версията на приложението Подправена версия Не подправена версия @@ -356,6 +361,30 @@ Списък с низове за изграждане на пътя на компонента, които да се филтрират, разделени с нов ред. Невалиден потребителски филтър: %s. + + Настройки за действие на бутоните + Замяна на действията на бутоните в приложението. + + Бутон Изтегляне + Действие на бутона \"Изтегляне\" за видео + Бутонът за изтегляне от YouTube отваря вашата външна програма за изтегляне. + Бутонът за изтегляне на YouTube отваря собствената програма за изтегляне на приложението. + Преназначаване на бутона за изтегляне на плейлист + Бутонът за изтегляне от YouTube отваря вашата външна програма за изтегляне. + Бутонът за изтегляне на YouTube отваря собствената програма за изтегляне на приложението. + Име на приложението за изтегляне + Име на пакета на приложението за изтегляне като NewPipe или YTDLnis. + + Замяна на бутона YouTube Music + Бутонът YouTube Music отваря RVX Music. + Бутонът YouTube Music отваря вграденото приложение. + Име на пакета RVX Music + Името на пакета с инсталиран RVX Music. + RVX Music + Внимание + %s не е инсталирано. Моля инсталирайте го. + Изисквания + Изисква се YouTube Music, за да замени действието на бутона. Докоснете тук, за да изтеглите YouTube Music. Минимизиран екран за възпроизвеждане Променете стила на минимизирания екран за възпроизвеждане. @@ -387,9 +416,9 @@ Прозрачност на менютата Стойност на прозрачност между 0-100, където 0 е прозрачно. Прозрачността на менюто на плейъра трябва да бъде между 0-100. Нулирайте стойностите по подразбирне. - - Бутони за навигация - Скриване или показване на елементи от лентата за навигация. + + Лента за навигация + Скриване или показване на елементи от лентата за навигация. Бутони за навигация в тесен стил Разстоянието между бутоните за навигация е по-тясно. Разстоянието между бутоните за навигация ежнормално. @@ -415,14 +444,21 @@ Навигационния панел е скрит. Навигационния панел се показва. Разменете бутоните „Създаване“ с „Известия“ - "Разменя позициите на бутона Създаване с бутона Известия чрез подправяне на информация за устройството. + "Бутонът за създаване се заменя с бутона за известия + +Забележка: Активирането на тази опция също скрива видеореклами." + Бутоните \"Създаване\" и \"Известия\" не са разменени. + "Деактивирането на тази настройка може да доведе до зареждане на повече реклами от сървъра. -• Може да се наложи устройството да се рестартира, за да влезе в сила промяната на тази настройка. -• Деактивирането на тази настройка зарежда повече реклами от страната на сървъра. -• Трябва да деактивирате тази настройка, за да направите видео рекламите видими." + Освен това рекламите може да се показват в Shorts. + +Ако деактивирането не влезе в сила, опитайте да превключите към режим „инкогнито“." Полупрозрачна лента за навигация Навигационната лента е полупрозрачна. Навигационната лента е непрозрачна. + Скриване лентата за навигация + Навигационната лента е скрита. + Навигационната лента се показва. Меню с настройки Скриване на елементи в менюто с настройки на YouTube. @@ -542,12 +578,12 @@ Автоматичното пускане може да се промени в настройките на YouTube: Настройки → Автоматично пускане → Автоматично пускане на следващия видеоклип" Препоръчаният видеоклип се показва в края на възпроизвеждането. - Интерфейс за мащабиране - Интерфейс за мащабиране ескрит. - Интерфейс за мащабиране се показва. Незабавно автоматично пускане Ако автоматичното пускане е активирано, следващият видеоклип ще се възпроизведе веднага. Ако автоматичното пускане е активирано, следващият видеоклип ще се възпроизведе след края на обратното броене. + Интерфейс за мащабиране + Интерфейс за мащабиране ескрит. + Интерфейс за мащабиране се показва. Бутони за действие Скриване или показване на бутони за действие под видеоклипове. @@ -995,30 +1031,6 @@ Бутон \"Пазаруване\" Бутона за пазаруване е скрит. Бутона за пазаруване се показва. - Скриване на бутона за пазаруване - Бутона за пазаруване е скрит. - Бутона за пазаруване се показва. - Бутон \"Специални благодарности\" - Бутон \"Специални благодарности\" е скрит. - Бутон \"Специални благодарности\" се показва. - Скриване на маркираните продукти - Маркираните продукти са скрити. - Маркираните продукти се показват. - Бутон за \"Местоположение\" - Бутон за \"Местоположение\" е скрит. - Бутон за \"Местоположение\" се показва. - Бутон за запазване на аудиото в плейлиста - Бутонът за Запазване в плейлиста е скрит. - Бутонът за Запазване в плейлиста се показва. - Бутон „Предложения за търсене“ - Бутон „Предложения за търсене“ е скрит. - Бутон „Предложения за търсене“ се показва. - Бутон „Използване на този звук“ - Бутон „Използване на този звук“ е скрит. - Бутон „Използване на този звук“ се показва. - Бутон за \"Използване на шаблон\" - Бутон за \"Използване на шаблон\" е скрит. - Бутон за \"Използване на шаблон\" се показва. Скриване на информационните панели Информационните панели са скрити. Информационните панели се показват. @@ -1037,6 +1049,35 @@ Скриване на етикет за връзка към видеоклипа Етикетът за видео връзка е скрит. Етикетът за видео връзка се показва. + + Предложени действия + Зелен бутон на екрана + Зелен бутон на екрана е скрит. + Зелен бутон на екрана се показва. + Бутон за запазване на аудиото в плейлиста + Бутонът за Запазване в плейлиста е скрит. + Бутонът за Запазване в плейлиста се показва. + Скриване на бутона за пазаруване + Бутона за пазаруване е скрит. + Бутона за пазаруване се показва. + Бутон \"Специални благодарности\" + Бутон \"Специални благодарности\" е скрит. + Бутон \"Специални благодарности\" се показва. + Бутон „Използване на този звук“ + Бутон „Използване на този звук“ е скрит. + Бутон „Използване на този звук“ се показва. + Бутон за \"Използване на шаблон\" + Бутон за \"Използване на шаблон\" е скрит. + Бутон за \"Използване на шаблон\" се показва. + Бутон за \"Местоположение\" + Бутон за \"Местоположение\" е скрит. + Бутон за \"Местоположение\" се показва. + Бутон „Предложения за търсене“ + Бутон „Предложения за търсене“ е скрит. + Бутон „Предложения за търсене“ се показва. + Скриване на маркираните продукти + Маркираните продукти са скрити. + Маркираните продукти се показват. Бутони за действие Скриване на бутона за харесване @@ -1059,6 +1100,9 @@ Бутона за Звук се показва. Анимация / Обратна връзка + Деактивиране анимацията на бутона „Харесва ми“ + Анимацията на фонтан е деактивирана над бутона „Харесва ми“. + Анимацията на фонтан е активирана над бутона „Харесва ми“. Скриване на фона на бутона за възпроизвеждане & и пауза Фонът на бутоните е скрит. Показва се фонът на бутоните. @@ -1087,6 +1131,9 @@ Скриване лентата за навигация Навигационната лента е скрита. Навигационната лента се показва. + Мярка в проценти на празното пространство + Конфигурира процента на височината на оставащото празно пространство, когато лентата за навигация е скрита, между 0 и 100 (%). + Процентът на височината трябва да е между 0-100 (%). Заменете псевдонима на канала Използва се името на канала. Използва се псевдонимът на канала. @@ -1189,6 +1236,12 @@ Уведомлението е скрито. Лъжливи параметри на устройството "Преоразмерява вашето устройство, за да покже видеоклипове с по-високо качество, които може да не са налични на вашето устройство." + Деактивирайте кодека VP9 + "Кодек VP9 е деактивиран. +• Максималната разделителна способност е 1080p. +• Възпроизвеждането на видео ще използва повече интернет данни от VP9. +• За да получите възпроизвеждане на HDR, HDR видеото все още използва кодека VP9." + VP9 кодек е включен. Сменете софтуерния кодек AV1 Заменя софтуерния кодек AV1 с кодека VP9. Отхвърлете софтуерния кодек AV1 @@ -1401,6 +1454,7 @@ Потребителското име е успешно променено. Репутацията ви е <b>%.2f</b> Създадохте <b>%s</b> части + Докоснете тук, за да видите вашите сегменти. SponsorBlock класация Спасихте хората от <b>%s</b> сегменти Докоснете за да видите статистиката и тези допринесли най-много. @@ -1490,9 +1544,9 @@ TV HTML5 Web Ефекти от замяната - • Филми или платени видеоклипове може да не се възпроизвеждат. + "• Филми или платени видеоклипове може да не се възпроизвеждат." • Липсва менюто за избор на аудио. - • Липсва менюто за избор на аудио. + "• Липсва менюто за избор на аудио." • Видеото може да не се възпроизведе. Принудително AVC (H.264) за iOS видео кодекът на iOS е AVC (H.264). @@ -1534,6 +1588,7 @@ AVC (H.264) има максимална разделителна способн Revancify синя Revancify Червена YouTube + YouTube (минимално заглавие) По подразбиране Изключване Включване diff --git a/src/main/resources/youtube/translations/de-rDE/strings.xml b/src/main/resources/youtube/translations/de-rDE/strings.xml index 8586b36518..3dd61aed96 100644 --- a/src/main/resources/youtube/translations/de-rDE/strings.xml +++ b/src/main/resources/youtube/translations/de-rDE/strings.xml @@ -21,13 +21,6 @@ Bitte lade %2$s von der Webseite herunter." Vollbildwerbung verstecken Vollbildwerbung wird versteckt. Vollbildwerbung wird angezeigt. - Vollbildanzeige schließen - Vollbildanzeigen werden über die Schaltfläche „Schließen“ geschlossen. - "Vollbildwerbung ist blockiert. - -Nebeneffekt: Community-Beitragsbilder können im Vollbildmodus blockiert werden." - Vollbildwerbung wurde blockiert. (Dialog-Typ: %s) - Vollbildwerbung wurde geschlossen. Allgemeine Werbung ausblenden Allgemeine Werbung ist ausgeblendet. Allgemeine Werbung wird angezeigt. @@ -93,13 +86,13 @@ Tippen Sie hier, um mehr über DeArrow zu erfahren." Albumkarten ausblenden Albumkarten werden ausgeblendet. Albumkarten werden angezeigt. + Karussellregal ausblenden "Versteckt folgende Abschnitte: - Aktuelle Nachrichten - Weiterschauen - Entdecke mehr Kanäle - Shopping - Erneut anschauen" - Karussellregal ausblenden Chips-Abschnitt verstecken Chips-Abschnitt wird versteckt. Chips-Abschnitt wird angezeigt @@ -238,18 +231,8 @@ Einschränkungen: • Videos mit Sätzen wie \"Menschen auch gesehen\" unten." Videos mit wenigen Aufrufen verstecken Verstecke Videos mit weniger als 1000 Aufrufen von nicht abonnierten Kanälen von der Startseite. - - Filterdauer - Videos je nach Dauer ausblenden - Verstecke Videos mit kürzer oder länger als die Dauer.\n\nBekanntes Problem: Videos werden nicht in den Player-bezogenen Videos ausgeblendet, sondern der Zeitstempel wird versteckt. - Länger als Dauer - Videos mit einer Dauer, die länger als diese Zahl ist, werden ausgeblendet. - Kürzer als Dauer - Videos mit einer Dauer, die länger als diese Zahl ist, werden ausgeblendet. Zählerfilter anzeigen - Empfohlene Videos nach Ansichten ausblenden. - Empfohlene Videos mit weniger als einer bestimmten Anzahl von Ansichten ausblenden.\n\nBekanntes Problem: Videos mit 0 Ansichten werden nicht gefiltert. Größer als Ansichten Videos mit Ansichten, die größer als diese Zahl sind, werden ausgeblendet. Weniger als Anrufe @@ -276,7 +259,6 @@ Einschränkungen: Abonnements Beliebt Später ansehen - Ungültige Startseite, zurücksetzen auf Standard. Erzwungene automatische Audiospuren sind deaktivieren Erzwungene automatische Audiospuren sind deaktiviert. Erzwungene automatische Audiospuren sind aktiviert. @@ -344,6 +326,9 @@ Manche Komponenten könnten nicht versteckt werden." Komponenten nach zeilengetrennten Namen filtern %s ist ein ungültiger benutzerdefinierter Filter. + + + Miniplayer Ändern Sie den Stil des in App minimierten Players. @@ -375,9 +360,7 @@ Manche Komponenten könnten nicht versteckt werden." Deckkraft der Überlagerung Deckkraft Wert zwischen 0-100, wobei 0 transparent ist. Die Transparenz des Miniplayers muss zwischen 0-100 liegen. Zurückgesetzt auf Standardwerte. - - Navigationstasten - Komponenten der Navigationsleiste ausblenden oder anzeigen. + Aktiviere schmale Navigationstasten Der Abstand zwischen den Navigationstasten ist schmal. Abstand zwischen den Navigationstasten ist normal. @@ -403,11 +386,6 @@ Manche Komponenten könnten nicht versteckt werden." Navigationslabel ist versteckt Navigationslabel wird angezeigt Erstellungs- und Benachrichtigungs-Buttons tauschen - "Tauscht die Positionen des Knopfes Erstellen mit der Schaltfläche \"Benachrichtigungen\" aus, indem die Geräteinformationen getäuscht werden. - -• Das Gerät muss neu gestartet werden, um diese Einstellung zu ändern. -• Deaktivieren dieser Einstellung lädt mehr Werbung von der Serverseite. -• Sie sollten diese Einstellung deaktivieren, um Video-Werbung sichtbar zu machen." Transparente Navigationsleiste aktivieren Navigationsleiste ist transparent. Navigationsleiste ist nicht transparent. @@ -525,12 +503,12 @@ Information: Autoplay kann in den YouTube-Einstellungen geändert werden: Einstellungen → Autoplay → Nächstes Video automatisch abspielen" Empfohlene Video-Endbildschirm wird angezeigt. - Zoom-Overlay ausblenden - Zoom-Overlay ist ausgeblendet. - Zoom-Overlay wird angezeigt. Autotoplay Countdown überspringen Ist Autoplay aktiviert, wird das nächste Video sofort abgespielt. Wenn Autoplay aktiviert ist, wird das nächste Video nach dem Countdown abgespielt. + Zoom-Overlay ausblenden + Zoom-Overlay ist ausgeblendet. + Zoom-Overlay wird angezeigt. Aktionsschaltflächen Aktionsschaltflächen unter Videos ausblenden oder anzeigen. @@ -887,13 +865,6 @@ Nebeneffekt: Offizielle Kopfzeilen in Suchergebnissen werden ausgeblendet."Abonnement-Button ausblenden Pausierte Overlay-Tasten ausblenden Verstecke Label für bezahlte Promotion - Shop-Schaltfläche verstecken - Super Dankeschön ausblenden - \"Super Thanks\" Button wird ausgeblendet. - \"Super Thanks\" Button wird angezeigt. - Markierte Produkte ausblenden - Markierte Produkte sind ausgeblendet. - Markierte Produkte werden angezeigt. Info-Panels ausblenden Live-Chat-Kopfzeile ausblenden Live-Chat-Kopfzeile wird ausgeblendet.\n\nZurück Button wird nicht ausgeblendet. @@ -902,6 +873,14 @@ Nebeneffekt: Offizielle Kopfzeilen in Suchergebnissen werden ausgeblendet."Videotitel ausblenden Sound-Metadaten-Label ausblenden Verstecke vollständige Video-Linkbezeichnung + + Shop-Schaltfläche verstecken + Super Dankeschön ausblenden + \"Super Thanks\" Button wird ausgeblendet. + \"Super Thanks\" Button wird angezeigt. + Markierte Produkte ausblenden + Markierte Produkte sind ausgeblendet. + Markierte Produkte werden angezeigt. Aktionsschaltflächen Verstecke \"Gefällt mir\" Button diff --git a/src/main/resources/youtube/translations/el-rGR/strings.xml b/src/main/resources/youtube/translations/el-rGR/strings.xml index 7d8c5877d0..dd760e1f68 100644 --- a/src/main/resources/youtube/translations/el-rGR/strings.xml +++ b/src/main/resources/youtube/translations/el-rGR/strings.xml @@ -6,6 +6,7 @@ ReVanced Extended Αναζήτηση %s + Επαναφέρθηκε στην προεπιλεγμένη τιμή. Πειραματικές Λειτουργίες Θέλετε να συνεχίσετε; Επανεκκίνηση ώστε να φορτωθεί σωστά η διάταξη @@ -23,13 +24,6 @@ Διαφημίσεις πλήρους οθόνης Κρυμμένες. Εμφανίζονται. - Κλείσιμο διαφημίσεων πλήρους οθόνης - Οι διαφημίσεις πλήρους οθόνης κλείνουν μέσω του κουμπιού κλεισίματος. - "Οι διαφημίσεις πλήρους οθόνης έχουν αποκλειστεί. - -Περιορισμός: Οι εικόνες δημοσιεύσεων κοινότητας ενδέχεται να μην ανοίγουν σε λειτουργία πλήρους οθόνης." - Οι διαφημίσεις πλήρους οθόνης αποκλείστηκαν. (Τύπος: %s) - Οι διαφημίσεις πλήρους οθόνης έκλεισαν. Γενικές διαφημίσεις Κρυμμένες. Εμφανίζονται. @@ -77,6 +71,7 @@ Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το DeArrow δεν είναι διαθέσιμο. Διεύθυνση API του DeArrow Η διεύθυνση URL του τελικού σημείου αποθήκευσης μικρογραφιών DeArrow. + Μη έγκυρο URL για το DeArrow API. Σχετικά με τις λήψεις ακίνητων καρέ Τα ακίνητα καρέ λαμβάνονται από την αρχή / μέση / τέλος κάθε βίντεο. Αυτές οι εικόνες είναι ενσωματωμένες στο YouTube και δεν χρησιμοποιείται εξωτερικό API. Χρήση γρήγορων λήψεων καρέ @@ -98,6 +93,7 @@ Κάρτες άλμπουμ Κρυμμένες. Εμφανίζονται. + Οριζόντιες ενότητες προτάσεων "Απόκρυψη ενοτήτων όπως: • Έκτακτη είδηση • Συνέχεια παρακολούθησης @@ -105,13 +101,15 @@ • Ακούστε ξανά • Αγορές • Παρακολουθήστε ξανά" - Οριζόντιες ενότητες προτάσεων Ενότητα σχετιζόμενων λέξεων Κρυμμένη. Εμφανίζεται. Επεκτάσιμα πλαίσια κάτω από τα βίντεο Κρυμμένα. Εμφανίζονται. + Επεκτάσιμες ενότητες + Κρυμμένες. + Εμφανίζονται. Κουμπί υπότιτλων στη ροή Κρυμμένο. Εμφανίζεται. @@ -250,18 +248,21 @@ Playlists • Βίντεο με φράσεις όπως «Άλλοι χρήστες παρακολούθησαν επίσης» στο κάτω μέρος τους." Απόκρυψη βίντεο χαμηλών προβολών Απόκρυψη των βίντεο με λιγότερες από 1,000 προβολές από τη ροή τα οποία ανήκουν σε κανάλια που δεν είστε συνδρομητές. - - Φίλτρο διάρκειας - Απόκρυψη βίντεο βάσει διάρκειάς τους - Απόκρυψη των βίντεο με διάρκεια μικρότερη ή μεγαλύτερη από την προτίμησή σας.\n\nΓνωστό θέμα: Δε λειτουργεί στα προτεινόμενα βίντεο κάτω από τον αναπαραγωγέα, θα κρύβεται όμως η χρονοσφραγίδα τους. - Μέγιστο όριο διάρκειας - Τα βίντεο με διάρκεια μεγαλύτερη από αυτόν τον αριθμό δεν θα εμφανίζονται. - Ελάχιστο όριο διάρκειας - Τα βίντεο με διάρκεια μικρότερη από αυτόν τον αριθμό δεν θα εμφανίζονται. + Απόκρυψη προσεχών βίντεο + "Απόκρυψη βίντεο με την ετικέτα «ΠΡΟΣΕΧΩΝ». + +Σημείωση: Η ενεργοποίηση αυτής της ρύθμισης θα κρύψει επίσης το κουμπί «Να λαμβάνω ειδοποιήσεις»." Φίλτρο αριθμού προβολών - Απόκρυψη βίντεο βάσει αριθμού προβολών - Απόκρυψη των βίντεο με προβολές λιγότερες ή περισσότερες από την προτίμησή σας.\n\nΓνωστό θέμα: Τα βίντεο με 0 προβολές δε φιλτράρονται σωστά. + Φιλτράρισμα καρτέλας «Αρχική» + Τα βίντεο στην καρτέλα «Αρχική» φιλτράρονται με βάση τον αριθμό προβολών. + Τα βίντεο στην καρτέλα «Αρχική» δε φιλτράρονται με βάση τον αριθμό προβολών. + Φιλτράρισμα αποτελεσμάτων αναζήτησης + Τα αποτελέσματα αναζήτησης φιλτράρονται με βάση τον αριθμό προβολών. + Τα αποτελέσματα αναζήτησης δε φιλτράρονται με βάση τον αριθμό προβολών. + Φιλτράρισμα καρτέλας «Εγγραφές» + Τα βίντεο στην καρτέλα «Εγγραφές» φιλτράρονται με βάση τον αριθμό προβολών. + Τα βίντεο στην καρτέλα «Εγγραφές» δε φιλτράρονται με βάση τον αριθμό προβολών. Μέγιστο όριο προβολών Τα βίντεο με περισσότερες προβολές από αυτόν τον αριθμό δεν θα εμφανίζονται. Ελάχιστο όριο προβολών @@ -269,6 +270,18 @@ Playlists Επεξεργασία κλειδιών Γλωσσικό πρότυπο για τον αριθμό προβολών κάτω από κάθε βίντεο. Κάθε κλειδί (λέξη ή γράμμα στη γλώσσα σας) -> τιμή (σημασία του κλειδιού) βρίσκεται σε νέα γραμμή. Τα κλειδιά μπαίνουν πριν από το \"->\". Αν αλλάξετε γλώσσα εφαρμογής ή συστήματος, θα χρειαστεί επαναρύθμιση. Παραδείγματα:\nΕλληνικά: 10 χιλιάδες προβολές = χιλ. -> 1000, προβολές -> views\nΑγγλικά: 10K views = K -> 1000, views -> views χιλ. -> 1 000\nεκ. -> 1 000 000\nδισ. -> 1 000 000 000\nπροβολές -> views + Σχετικά με το φιλτράρισμα βάσει προβολών + "Οι καρτέλες «Αρχική», «Εγγραφές» και τα αποτελέσματα αναζήτησης φιλτράρονται για απόκρυψη βίντεο με προβολές λιγότερες ή περισσότερες από τον καθορισμένο σας αριθμό. + +Περιορισμοί: +• Τα Shorts δε φιλτράρονται. +• Τα βίντεο με 0 προβολές δε φιλτράρονται." + Σχετιζόμενα βίντεο + Κρυμμένα. + Εμφανίζονται. + "Αυτή η ρύθμιση περιορίζει τον μέγιστο αριθμό διατάξεων που μπορούν να εμφανιστούν στην οθόνη αναπαραγωγής. + +Στην περίπτωση που η διάταξη της οθόνης αναπαραγωγής αλλάξει λόγω αλλαγών από πλευράς του διακομιστή, ορισμένες διατάξεις της ενδέχεται να κρυφτούν χωρίς να είναι επιθυμητό." Γενικά Αλλαγή αρχικής σελίδας @@ -288,7 +301,11 @@ Playlists Εγγραφές Τάσεις Παρακολούθηση αργότερα - Μη έγκυρη αρχική σελίδα, επαναφέρθηκε. + Αλλαγή τύπου αρχικής σελίδας + "Η αρχική σελίδα αλλάζει πάντα. + +Περιορισμός: Το κουμπί επιστροφής στη γραμμή εργαλείων ενδέχεται να μη λειτουργεί." + Η αρχική σελίδα αλλάζει μόνο μία φορά. Απενεργοποίηση υποχρεωτικών κομματιών ήχου Τα υποχρεωτικά κομμάτια ήχου είναι απενεργοποιημένα. Τα υποχρεωτικά κομμάτια ήχου είναι ενεργοποιημένα. @@ -304,7 +321,7 @@ Playlists Αιωρούμενο κουμπί μικροφώνου Κρυμμένο. Εμφανίζεται. - Γκρι διαχωριστικό + Γκρι διαχωριστικά Κρυμμένα. Εμφανίζονται. Μηνύματα αλληλεπίδρασης @@ -317,17 +334,9 @@ Playlists Παραποίηση του dpi συσκευής ώστε να χρησιμοποιηθεί η διεπαφή κινητού. Λειτουργία διεπαφής τάμπλετ Παραποίηση του dpi συσκευής ώστε να χρησιμοποιηθεί η διεπαφή τάμπλετ. - Μετατροπή κουμπιού λήψης βίντεο - Το κουμπί λήψης του YouTube ανοίγει το εξωτερικό πρόγραμμα λήψης σας. - Το κουμπί λήψης του YouTube ανοίγει το εγγενές πρόγραμμα λήψης της εφαρμογής. - Μετατροπή κουμπιού λήψης playlist - Το κουμπί λήψης λίστας αναπαραγωγής εμφανίζεται πάντα, και σε δημόσιες λίστες αναπαραγωγής ανοίγει το εξωτερικό πρόγραμμα λήψης σας. - Αν εμφανίζεται, το κουμπί λήψης λίστας αναπαραγωγής ανοίγει το εγγενές πρόγραμμα λήψης του YouTube. - Όνομα πακέτου προγράμματος λήψης playlist - Όνομα πακέτου της εγκατεστημένης σας εξωτερικής εφαρμογής λήψης (π.χ YTLDnis). Τροποποίηση έκδοσης εφαρμογής - Η έκδοση τροποποιείται - Η έκδοση δεν τροποποιείται + Η έκδοση τροποποιείται. + Η έκδοση δεν τροποποιείται. "Η έκδοση της εφαρμογής YouTube θα τροποποιηθεί σε παλιότερη. Αυτό θα αλλάξει την εμφάνιση και τα χαρακτηριστικά της εφαρμογής, αλλά ενδέχεται να εμφανιστούν άγνωστες παρενέργειες. @@ -364,6 +373,30 @@ Playlists Λίστα από συμβολοσειρές στοιχείων που θα φιλτραριστούν, διαχωρισμένες με νέες γραμμές. Μη έγκυρο φίλτρο: %s. + + Μετατροπή κουμπιών + Μετατροπή ενέργειας πατήματος των κουμπιών της εφαρμογής. + + Κουμπί «Λήψη» + Μετατροπή κουμπιού λήψης βίντεο + Το κουμπί λήψης του YouTube ανοίγει το εξωτερικό πρόγραμμα λήψης σας. + Το κουμπί λήψης του YouTube ανοίγει το εγγενές πρόγραμμα λήψης της εφαρμογής. + Μετατροπή κουμπιού λήψης playlist + Το κουμπί λήψης λίστας αναπαραγωγής εμφανίζεται πάντα, και σε δημόσιες λίστες αναπαραγωγής ανοίγει το εξωτερικό πρόγραμμα λήψης σας. + Αν εμφανίζεται, το κουμπί λήψης λίστας αναπαραγωγής ανοίγει το εγγενές πρόγραμμα λήψης του YouTube. + Όνομα πακέτου προγράμματος λήψης playlist + Όνομα πακέτου της εγκατεστημένης σας εξωτερικής εφαρμογής λήψης (π.χ YTLDnis). + + Μετατροπή κουμπιού YouTube Music + Το κουμπί YouTube Music ανοίγει τo RVX Music. + Το κουμπί YouTube Music ανοίγει την εγγενή εφαρμογή. + Όνομα πακέτου RVX Music + Όνομα πακέτου του εγκατεστημένου RVX Music. + RVX Music + Προειδοποίηση + %s δεν έχει εγκατασταθεί. Παρακαλούμε εγκαταστήστε το. + Προαπαιτούμενο + Το YouTube Music είναι απαραίτητο για την μετατροπή ενέργειας του κουμπιού. Πατήστε για να κατεβάσετε το YouTube Music. Ελαχιστοποιημένη οθόνη αναπαραγωγής Αλλαγή του στυλ της ελαχιστοποιημένης οθόνης αναπαραγωγής. @@ -394,10 +427,10 @@ Playlists Εμφανίζονται. Αδιαφάνεια φόντου παρασκηνίου Τιμή αδιαφάνειας μεταξύ 0-100, όπου το 0 είναι διαφανές. - Η αδιαφάνεια πρέπει να ναι μεταξύ 0-100, επαναφέρθηκε. - - Κουμπιά πλοήγησης - Απόκρυψη ή εμφάνιση στοιχείων της γραμμής πλοήγησης. + Η αδιαφάνεια πρέπει να ναι μεταξύ 0-100. + + Γραμμή πλοήγησης + Απόκρυψη ή εμφάνιση των στοιχείων της γραμμής πλοήγησης. Κουμπιά πλοήγησης στενού στυλ Το διάστημα μεταξύ των κουμπιών πλοήγησης είναι στενότερο. Το διάστημα μεταξύ των κουμπιών πλοήγησης δεν είναι στενότερο. @@ -423,13 +456,21 @@ Playlists Κρυμμένες. Εμφανίζονται. Εναλλαγή «Δημιουργία» με «Ειδοποιήσεις» - "Εναλλαγή θέσεων των κουμπιών «Δημιουργία» και «Ειδοποιήσεις» παραποιώντας τις πληροφορίες συσκευής. + "Γίνεται εναλλαγή θέσεων των κουμπιών «Δημιουργία» και «Ειδοποιήσεις». + +Σημείωση: Η ενεργοποίηση αυτής της ρύθμισης εξαναγκάζει επίσης την απόκρυψη των διαφημίσεων βίντεο." + Δεν γίνεται εναλλαγή θέσεων των κουμπιών «Δημιουργία» και «Ειδοποιήσεις». + "Η απενεργοποίηση αυτής της ρύθμισης μπορεί να έχει ως αποτέλεσμα την φόρτωση περισσότερων διαφημίσεων από τον διακομιστή. -• Όταν ενεργοποιηθεί, μπορεί να μη λειτουργήσει μέχρι να γίνει επανεκκίνηση της συσκευής σας. -• Η ενεργοποίηση αυτής της ρύθμισης εξαναγκάζει επίσης την απενεργοποίηση των διαφημίσεων βίντεο." +Επίσης, ενδέχεται να εμφανίζονται διαφημίσεις στα Shorts. + +Αν η απενεργοποίηση δεν τεθεί σε ισχύ, δοκιμάστε να μεταβείτε σε λειτουργία ανώνυμης περιήγησης." Ημιδιαφανή γραμμή πλοήγησης Η γραμμή πλοήγησης είναι ημιδιαφανής. Η γραμμή πλοήγησης είναι αδιαφανής. + Γραμμή πλοήγησης + Κρυμμένη. + Εμφανίζεται. Μενού ρυθμίσεων Απόκρυψη στοιχείων στο μενού ρυθμίσεων του YouTube. @@ -443,9 +484,9 @@ Playlists Γραμμή εργαλείων Απόκρυψη ή αλλαγή στοιχείων που βρίσκονται στη γραμμή εργαλείων, όπως τα κουμπιά, την γραμμή αναζήτησης, ή την επικεφαλίδα. - Αλλαγή επικεφαλίδας YouTube + Επικεφαλίδα Premium Η επικεφαλίδα Premium είναι ενεργοποιημένη. - Η αρχική επικεφαλίδα είναι ενεργοποιημένη. + Η επικεφαλίδα Premium είναι απενεργοποιημένη. Ευρεία γραμμή αναζήτησης Η ευρεία γραμμή αναζήτησης είναι ενεργοποιημένη. Η ευρεία γραμμή αναζήτησης είναι απενεργοποιημένη. @@ -486,7 +527,7 @@ Playlists Οθόνη αναπαραγωγής Αλλαγή αδιαφάνειας φόντου αναπαραγωγέα Τιμή αδιαφάνειας μεταξύ 0-100, όπου το 0 είναι διαφανές. - Η αδιαφάνεια πρέπει να είναι μεταξύ 0-100, επαναφέρθηκε. + Η αδιαφάνεια πρέπει να είναι μεταξύ 0-100. Αναδυόμενα παράθυρα του αναπαραγωγέα Κρυμμένα. Εμφανίζονται. @@ -505,7 +546,7 @@ Playlists • Σε περίπτωση απενεργοποίησης αυτής της ρύθμισης δεν εξαναγκάζεται η ενεργοποίηση της διεπαφής ταχύτητας." Τιμή διεπαφής ταχύτητας Τιμή ταχύτητας που εφαρμόζεται κατά το παρατεταμένο πάτημα, μεταξύ 0 και 8.0. - Η ταχύτητα πρέπει να ναι μεταξύ 0-8.0, επαναφέρθηκε. + Η ταχύτητα πρέπει να ναι μεταξύ 0-8.0. Υδατογράφημα καναλιού Κρυμμένο. Εμφανίζεται. @@ -552,12 +593,12 @@ Playlists Η αυτόματη αναπαραγωγή μπορεί να αλλαχτεί στις ρυθμίσεις YouTube: 'Ρυθμίσεις → Αυτόματη αναπαραγωγή → Αυτόματη αναπαραγωγή επόμενου βίντεο'" Εμφανίζεται. - Διεπαφή ζουμ - Κρυμμένη. - Εμφανίζεται. Άμεση αυτόματη αναπαραγωγή Αν είναι ενεργοποιημένη η αυτόματη αναπαραγωγή, το επόμενο βίντεο παίζει χωρίς αντίστροφη μέτρηση. Αν είναι ενεργοποιημένη η αυτόματη αναπαραγωγή, το επόμενο βίντεο παίζει αφού τελειώσει η αντίστροφη μέτρηση. + Διεπαφή ζουμ + Κρυμμένη. + Εμφανίζεται. Κουμπιά ενεργειών Απόκρυψη ή εμφάνιση κουμπιών κάτω από τα βίντεο. @@ -767,7 +808,7 @@ Playlists Εμφανίζεται. Ύψος γραμμής προόδου Αλλαγή ύψους της γραμμής προόδου, τιμές μεταξύ 0-32. - Το ύψος πρέπει να είναι μεταξύ 0-32, επαναφέρθηκε. + Το ύψος πρέπει να είναι μεταξύ 0-32. Απενεργοποίηση οριζόντιας λειτουργίας Η οριζόντια λειτουργία σε λειτουργία πλήρους οθόνης είναι απενεργοποιημένη. @@ -982,7 +1023,9 @@ Playlists Ενότητα Shorts Απόκρυψη των Shorts - "Απόκρυψη της ενότητας Shorts.\n\nΠεριορισμός: Οι τίτλοι ενοτήτων στα αποτελέσματα αναζήτησης δεν εμφανίζονται." + "Απόκρυψη της ενότητας Shorts. + +Παρενέργεια: Οι τίτλοι ενοτήτων στα αποτελέσματα αναζήτησης δεν εμφανίζονται." Απόκρυψη στην καρτέλα «Αρχική» και στα σχετικά βίντεο Κρυμμένη. Εμφανίζεται. @@ -1022,33 +1065,9 @@ Playlists Κουμπί «Τάσεις» Κρυμμένο. Εμφανίζεται. - Κουμπί «Κατάστημα» + Κουμπί «Αγορές» Κρυμμένο. Εμφανίζεται. - Κουμπί «Κατάστημα» - Κρυμμένο. - Εμφανίζεται. - Κουμπί «Αγορά Super Thanks» - Κρυμμένο. - Εμφανίζεται. - Ετικέτες προϊόντων - Κρυμμένες. - Εμφανίζονται. - Κουμπί τοποθεσίας - Κρυμμένο. - Εμφανίζεται. - Κουμπί αποθήκευσης ήχου σε λίστα αναπαραγωγής - Κρυμμένο. - Εμφανίζεται. - Κουμπί προτάσεων αναζήτησης - Κρυμμένο. - Εμφανίζεται. - Κουμπί «Χρήση αυτού του ήχου» - Κρυμμένο. - Εμφανίζεται. - Κουμπί «Χρήση προτύπου» - Κρυμμένο. - Εμφανίζεται. Πάνελ πληροφοριών Κρυμμένα. Εμφανίζονται. @@ -1067,6 +1086,35 @@ Playlists Ετικέτα συνδέσμου πλήρους βίντεο Κρυμμένη. Εμφανίζεται. + + Προτεινόμενες ενέργειες + Κουμπί «Πράσινη οθόνη» + Κρυμμένο. + Εμφανίζεται. + Κουμπί «Αποθήκευση ήχου» + Κρυμμένο. + Εμφανίζεται. + Κουμπί «Κατάστημα» + Κρυμμένο. + Εμφανίζεται. + Κουμπί «Αγορά Super Thanks» + Κρυμμένο. + Εμφανίζεται. + Κουμπί «Χρήση αυτού του ήχου» + Κρυμμένο. + Εμφανίζεται. + Κουμπί «Χρήση προτύπου» + Κρυμμένο. + Εμφανίζεται. + Κουμπί τοποθεσίας + Κρυμμένο. + Εμφανίζεται. + Κουμπί προτάσεων αναζήτησης + Κρυμμένο. + Εμφανίζεται. + Ετικέτες προϊόντων + Κρυμμένες. + Εμφανίζονται. Κουμπιά ενεργειών Κουμπί «Μου αρέσει» @@ -1089,6 +1137,9 @@ Playlists Εμφανίζεται. Εφέ / Απόκριση + Απενεργοποίηση εφέ κουμπιού «Μου αρέσει» + Το εφέ κίνησης πάνω από το κουμπί «Μου αρέσει» είναι απενεργοποιημένο. + Το εφέ κίνησης πάνω από το κουμπί «Μου αρέσει» είναι ενεργοποιημένο. Φόντο κουμπιών παύσης & αναπαραγωγής Κρυμμένο. Εμφανίζεται. @@ -1111,13 +1162,16 @@ Playlists Πατήστε παρατεταμένα την χρονοσφραγίδα για να αλλάξει η κατάσταση επανάληψης των Shorts. Βάθος γραμμής προόδου Αλλαγή βάθους της γραμμής προόδου, τιμές μεταξύ 0-64. - Το βάθος πρέπει να είναι μεταξύ 0-64, επαναφέρθηκε. + Το βάθος πρέπει να είναι μεταξύ 0-64. Γραμμή εργαλείων Κρυμμένη. Εμφανίζεται. Γραμμή πλοήγησης Η γραμμή πλοήγησης είναι κρυμμένη κατά την αναπαραγωγή Shorts. Η γραμμή πλοήγησης εμφανίζεται κατά την αναπαραγωγή Shorts. + Ποσοστό ύψους του κενού χώρου + Ρύθμιση του ποσοστού ύψους του κενού χώρου που απομένει όταν η γραμμή πλοήγησης είναι κρυμμένη, μεταξύ 0 και 100 (%). + Το ποσοστό ύψους πρέπει να είναι μεταξύ 0-100 (%). Αντικατάσταση ονόματος καναλιού Εμφανίζεται το όνομα καναλιού. Εμφανίζεται το ψευδώνυμο καναλιού. @@ -1152,7 +1206,7 @@ Playlists Το μέγεθος κειμένου στοιχείων ελέγχου του φόντου σάρωσης. Μέγεθος περιοχής οθόνης σάρωσης Ποσοστό επιφάνειας της οθόνης που μπορεί να γίνει η σάρωση.\n\nΣημείωση: Αυτό θα αλλάξει επίσης το μέγεθος της περιοχής οθόνης της χειρονομίας διπλού πατήματος για αναζήτηση. - Το μέγεθος οθόνης πρέπει να ναι μικρότερο από 50, επαναφέρθηκε. + Το μέγεθος οθόνης πρέπει να ναι μικρότερο από 50. Χρονικό όριο φόντου σάρωσης Το χρονικό διάστημα των χιλιοστών του δευτερολέπτου που είναι ορατό το φόντο σάρωσης. Απενεργοποίηση αυτόματης φωτεινότητας HDR @@ -1222,6 +1276,13 @@ Playlists Παραποίηση διαστάσεων συσκευής "Παραποίηση διαστάσεων συσκευής στη μέγιστη τιμή. Ενδέχεται να ξεκλειδωθούν υψηλότερες ποιότητες σε κάποια βίντεο που απαιτούν υψηλές διαστάσεις συσκευής, αλλά όχι σε όλα τα βίντεο." + Απενεργοποίηση κωδικοποιητή VP9 + "Ο κωδικοποιητής VP9 είναι απενεργοποιημένος. + +• Η μέγιστη ανάλυση είναι 1080p. +• Η αναπαραγωγή βίντεο θα χρησιμοποιεί περισσότερα δεδομένα Internet από τον VP9. +• Για την αναπαραγωγή βίντεο τύπου HDR, ο κωδικοποιητής VP9 εξακολουθεί να χρησιμοποιείται." + Ο κωδικοποιητής VP9 είναι ενεργοποιημένος. Αντικατάσταση κωδικοποιητή AV1 Αντικατάσταση του κωδικοποιητή λογισμικού AV1 με τον κωδικοποιητή VP9. Απόρριψη απόκρισης κωδικοποιητή AV1 @@ -1232,8 +1293,8 @@ Playlists Η προεπιλεγμένη ποιότητα δεδομένων άλλαξε σε %s. Αποτυχία ορισμού ποιότητας βίντεο. Η προεπιλεγμένη ποιότητα με Wi-Fi άλλαξε σε %s. - Οι ταχύτητες πρέπει να είναι μικρότερες από %sx, επαναφέρθηκαν. - Μη έγκυρες ταχύτητες αναπαραγωγής, επαναφέρθηκαν. + Οι ταχύτητες πρέπει να είναι μικρότερες από %sx. + Μη έγκυρες ταχύτητες αναπαραγωγής. Return YouΤube Dislike Επιστροφή του «Δεν μου αρέσει» στο YouTube @@ -1434,6 +1495,7 @@ Playlists Το όνομα χρήστη άλλαξε επιτυχώς. Η φήμη σας είναι <b>%.2f</b> Δημιουργήσατε <b>%s</b> τμήματα + Πατήστε για να δείτε τα τμήματα σας. Πίνακας κατάταξης SponsorBlock Έχετε σώσει άλλους από <b>%s</b> τμήματα Πατήστε για να δείτε τα παγκόσμια στατιστικά και τους κορυφαίους συνεισφέροντες. @@ -1463,7 +1525,7 @@ Playlists Οι ανακατευθύνσεις URL παρακάμπτονται κατά το άνοιγμα συνδέσμων. Οι ανακατευθύνσεις URL δεν παρακάμπτονται κατά το άνοιγμα συνδέσμων. Άνοιγμα ρυθμίσεων προεπιλεγμένων εφαρμογών - Για να ανοίγουν οι συνδέσμοι YouTube στο RVX, ενεργοποιήστε το «Άνοιγμα υποστηριζόμενων συνδέσμων» και ενεργοποιήστε τις υποστηριζόμενες διευθύνσεις ιστού. + Για να ανοίγουν οι συνδέσμοι YouTube στο RVX, ενεργοποιήστε το «Άνοιγμα υποστηριζόμενων συνδέσμων» και τις υποστηριζόμενες διευθύνσεις ιστού. Άνοιγμα του MicroG GmsCore Ενεργοποιήστε τις ρυθμίσεις cloud messaging για να λαμβάνετε ειδοποιήσεις. Το MicroG GmsCore δεν είναι εγκατεστημένο. Εγκαταστήστε το. @@ -1528,16 +1590,20 @@ Playlists TV HTML5 Ιστός (Web) Παρενέργειες παραποίησης - • Οι ταινίες ή τα επί πληρωμή βίντεο ενδέχεται να μην αναπαράγονται. + "• Οι ταινίες ή τα επί πληρωμή βίντεο ενδέχεται να μην αναπαράγονται. +• Οι ζωντανές μεταδόσεις ξεκινούν από την αρχή κατά την αναπαραγωγή. +• Τα βίντεο μπορεί να τελειώνουν 1 δευτερόλεπτο νωρίτερα. +• Ο κωδικοποιητής ήχου opus δεν είναι διαθέσιμος." • Το μενού «Κομμάτι ήχου» λείπει. - • Το μενού «Κομμάτι ήχου» λείπει. - • Τα βίντεο ενδέχεται να μην αναπαράγονται σωστά. + "• Το μενού «Κομμάτι ήχου» λείπει. +• Η λειτουργία «Σταθερή ένταση» δεν είναι διαθέσιμη." + • Τα βίντεο ενδέχεται να μην αναπαράγονται. Εξαναγκασμός iOS AVC (H.264) Ο κωδικοποιητής βίντεο iOS είναι ο AVC (H.264). Ο κωδικοποιητής βίντεο iOS είναι ο AVC (H.264), ο VP9 ή ο AV1. - "Ενεργοποιώντας αυτόν τον κωδικοποιητή ίσως βελτιωθεί η κατανάλωση ενέργειας και ίσως διορθωθούν κολλήματα αναπαραγωγής. + "Ενεργοποιώντας αυτόν τον κωδικοποιητή ίσως βελτιωθεί η κατανάλωση ενέργειας και ίσως διορθωθούν μικροκολλήματα αναπαραγωγής. -Ο AVC (H.264) έχει μέγιστη ανάλυση 1080p, και η αναπαραγωγή βίντεο καταναλώνει περισσότερα δεδομένα internet από τον VP9 ή τον AV1." +Ο AVC (H.264) ωστόσο έχει μέγιστη ανάλυση 1080p, και η αναπαραγωγή βίντεο καταναλώνει περισσότερα δεδομένα internet από τον VP9 ή τον AV1." Εμφάνιση στο «Στατιστικά για σπασίκλες» Το πρόγραμμα πελάτη που χρησιμοποιείται για τη λήψη δεδομένων ροής εμφανίζεται στο μενού «Στατιστικά για σπασίκλες». Το πρόγραμμα πελάτη που χρησιμοποιείται για τη λήψη δεδομένων ροής δεν εμφανίζεται στο μενού «Στατιστικά για σπασίκλες». @@ -1547,14 +1613,14 @@ Playlists Διαχείριση όλου του ιστορικού Πατήστε για άνοιγμα της διαχείρισης του ιστορικού παρακολούθησης του YouTube. Τύπος ιστορικού παρακολούθησης - Αρχικό + Αρχικός Αντικατάσταση του domain Αποκλεισμός ιστορικού παρακολούθησης Κατάσταση ιστορικού παρακολούθησης Το ιστορικό παρακολούθησης είναι αποκλεισμένο. • Ακολουθούνται οι ρυθμίσεις ιστορικού παρακολούθησης του λογαριασμού Google σας. "• Ακολουθούνται οι ρυθμίσεις ιστορικού παρακολούθησης του λογαριασμού Google σας. -• Το ιστορικό παρακολούθησης μπορεί να μη λειτουργεί λόγω του DNS ή χρήσης VPN." +• Το ιστορικό παρακολούθησης μπορεί να μη λειτουργεί λόγω του DNS σας ή χρήσης VPN." Πληροφορίες τροποποίησης @@ -1572,6 +1638,7 @@ Playlists Revancify Blue Revancify Red YouTube + YouTube (ελάχιστη επικεφαλίδα) Προεπιλογή Εξαιρέθηκε Συμπεριλήφθηκε diff --git a/src/main/resources/youtube/translations/es-rES/strings.xml b/src/main/resources/youtube/translations/es-rES/strings.xml index bf7eec87f7..936a733256 100644 --- a/src/main/resources/youtube/translations/es-rES/strings.xml +++ b/src/main/resources/youtube/translations/es-rES/strings.xml @@ -6,6 +6,7 @@ ReVanced Extended Buscar en ajustes + Restablecer valores predeterminados. Funciones experimentales ¿Quieres continuar? Reiniciar para cargar el diseño normalmente @@ -23,13 +24,6 @@ Descarga %2$s desde el sitio web." Ocultar anuncios en pantalla completa Los anuncios en pantalla completa están ocultos. Los anuncios en pantalla completa están visibles. - Cerrar anuncios en pantalla completa - Los anuncios en pantalla completa se cierran mediante el botón de cerrar. - "Los anuncios en pantalla completa están bloqueados. - -Limitación: la imagen de la publicación de la comunidad en pantalla completa puede estar bloqueada." - Se han bloqueado anuncios en pantalla completa. (Tipo de diálogo: %s) - Se han cerrado anuncios en pantalla completa. Ocultar anuncios generales Los anuncios generales están ocultos. Los anuncios generales están visibles. @@ -79,6 +73,7 @@ Pulsa aquí para saber más sobre DeArrow." No se muestra el mensaje si DeArrow no está disponible. Punto final de la API de DeArrow La URL del punto final de la caché de miniaturas de DeArrow. + URL de la API de DeArrow no válida. Capturas de vídeo fijas Las capturas fijas se toman del principio / mitad / final de cada vídeo. Estas imágenes están integradas en YouTube y no se utiliza ninguna API externa. Usar capturas fijas rápidas @@ -100,6 +95,7 @@ Pulsa aquí para saber más sobre DeArrow." Ocultar tarjetas de álbum Las tarjetas del álbum están ocultas. Las tarjetas del álbum están visibles. + Ocultar estante de carrusel "Oculta las siguientes estanterías: • Noticias de última hora • Seguir viendo @@ -107,13 +103,15 @@ Pulsa aquí para saber más sobre DeArrow." • Volver a escuchar • Compras • Volver a ver" - Ocultar estante de carrusel Ocultar estante de fichas El estante de fichas está oculto. El estante de fichas está visible. Ocultar fichas ampliables bajo los vídeos Las fichas ampliables están ocultas. Las fichas ampliables están visibles. + Ocultar estantes ampliables + Los estantes ampliables están ocultos. + Los estantes ampliables están visibles. Ocultar botón de subtítulos del feed El botón de subtítulos está oculto. El botón de subtítulos está visible. @@ -252,18 +250,21 @@ Limitaciones: • Vídeos subidos desde canales a los que no estás suscrito y que tienen menos de 1,000 visualizaciones." Ocultar vídeos con pocas visualizaciones Oculta los vídeos con menos de 1.000 visualizaciones de los feeds de inicio que hayan sido subidos desde canales a los que no estás suscrito. - - Filtro de duración - Ocultar vídeos basados en la duración - Oculta vídeos con una duración inferior o superior a la establecida.\n\nProblema conocido: no ocultará los vídeos en los vídeos relacionados en el reproductor, en su lugar ocultará la marca de tiempo. - Duración más larga - Los vídeos con una duración más larga que este número serán ocultados. - Duración más corta - Los vídeos con una duración más corta que este número serán ocultados. + Ocultar vídeo PRÓXIMO + "Oculta vídeos con la etiqueta PRÓXIMAMENTE. + +Nota: Al activar esta opción también se oculta el botón de notificarme." Filtro de contador de visualizaciones - Ocultar vídeos recomendados por visualizaciones - Oculta vídeos recomendados con menos de un número determinado de visualizaciones. + Ocultar vídeos de inicio por visualizaciones + Los vídeos en el feed de inicio están filtrados. + Los vídeos en el feed de inicio no están filtrados. + Ocultar resultados de búsqueda por visualizaciones + Los resultados de búsqueda están filtrados. + Los resultados de búsqueda no están filtrados. + Ocultar vídeos de suscripciones por visualizaciones + Los vídeos en el feed de suscripciones están filtrados. + Los vídeos en el feed de suscripciones no están filtrados. Visualizaciones mayores Los vídeos con visualizaciones mayores que este número serán ocultados. Visualizaciones menores @@ -271,6 +272,18 @@ Limitaciones: Claves de visualización Especifica tu plantilla de idioma para el número de visualizaciones mostradas debajo de cada vídeo en la interfaz de usuario. Cada clave (una letra/palabra en tu idioma) -> valor (significado de la clave) debe estar en una nueva línea. Las claves van antes del signo \"->\". Si cambias el idioma de la aplicación o del sistema tienes que restablecer este ajuste.\n\nEjemplos:\nInglés: 10K views = K -> 1000, views -> views\nEspañol: 10 K visualizaciones = K -> 1000, visualizaciones -> views K -> 1 000\nM -> 1 000 000\nB -> 1 000 000 000\nvisualizaciones -> views + Acerca del filtrado del contador de visualizaciones + "El inicio / Las suscripciones / Los resultados de búsqueda se filtran para ocultar los vídeos con visualizaciones inferiores o superiores a un número determinado. + +Limitaciones: +• Los Shorts no se pueden ocultar. +• Los vídeos con 0 visualizaciones no se filtran." + Ocultar vídeos relacionados + Los vídeos relacionados están ocultos. + Los vídeos relacionados están visibles. + "Este ajuste limita el número máximo de diseños que se pueden cargar en la pantalla del reproductor. + +Si el diseño de la pantalla del reproductor cambia debido a cambios en el servidor, es posible que se oculten diseños no deseados en la pantalla del reproductor." General Cambiar página de inicio @@ -290,7 +303,11 @@ Limitaciones: Suscripciones Tendencias Ver más tarde - Página de inicio no válida, restableciendo a la predeterminada. + Cambiar tipo de página de inicio + "La página de inicio siempre cambia. + +Limitación: Es posible que el botón Atrás de la barra de herramientas no funcione." + La página de inicio solo cambia una vez. Desactivar pistas de audio automáticas forzadas Las pistas de audio automáticas forzadas están desactivadas. Las pistas de audio automáticas forzadas están activadas. @@ -319,14 +336,6 @@ Esto no evita la restricción de edad. Solo la acepta automáticamente."Modifica los DPI para utilizar algunos diseños de teléfono. Activar diseño de tableta Modifica los DPI para utilizar algunos diseños de tableta. - Reemplazar botón de descarga de vídeo - El botón nativo de descarga de vídeo abre tu descargador externo. - El botón nativo de descarga de vídeo abre el descargador nativo de la aplicación. - Reemplazar botón de descarga de listas de reproducción - El botón nativo de descarga de listas de reproducción abre tu descargador externo. - El botón nativo de descarga de listas de reproducción abre el descargador nativo de la aplicación. - Nombre del paquete del descargador de listas de reproducción - Nombre del paquete de tu aplicación de descargas externas instalada, como YTDLnis. Falsificar versión de la app Versión falsificada Versión no falsificada @@ -366,6 +375,30 @@ Algunos componentes pueden no estar ocultos." Lista de cadenas del constructor de rutas de componentes a filtrar separadas por una nueva línea. Filtro personalizado no válido: %s. + + Botones de enganche + Reemplaza la acción de clic de los botones dentro de la aplicación. + + Botón de descarga + Reemplazar botón de descarga de vídeo + El botón nativo de descarga de vídeo abre tu descargador externo. + El botón nativo de descarga de vídeo abre el descargador nativo de la aplicación. + Reemplazar botón de descarga de listas de reproducción + El botón nativo de descarga de listas de reproducción abre tu descargador externo. + El botón nativo de descarga de listas de reproducción abre el descargador nativo de la aplicación. + Nombre del paquete del descargador de listas de reproducción + Nombre del paquete de tu aplicación de descargas externas instalada, como YTDLnis. + + Reemplazar botón de YouTube Music + El botón de YouTube Music abre RVX Music. + El botón de Youtube Music abre la app nativa. + Nombre del paquete de RVX Music + Nombre del paquete de RVX Music instalado. + RVX Music + Advertencia + %s no está instalado. Por favor, instálalo. + Requisito previo + Se requiere YouTube Music para reemplazar la acción del botón. Pulsa aquí para descargar YouTube Music. Minirreproductor Cambia el estilo del reproductor minimizado de la aplicación. @@ -397,9 +430,9 @@ Algunos componentes pueden no estar ocultos." Opacidad de superposición Valor de opacidad entre 0-100, donde 0 es transparente. La opacidad del minirreproductor debe estar entre 0-100. Restablezca a los valores predeterminados. - - Botones de navegación - Ocultar o mostrar los componentes de la sección de la barra de navegación. + + Barra de navegación + Ocultar o mostrar los componentes de la sección de la barra de navegación. Activar botones de navegación estrechos El espacio entre los botones de navegación se hace más estrecho. El espacio entre los botones de navegación no se hace más estrecho. @@ -425,14 +458,21 @@ Algunos componentes pueden no estar ocultos." La etiqueta de navegación está oculta. La etiqueta de navegación está visible. Cambiar botón de crear con el de notificaciones - "Cambia las posiciones del botón de crear y del botón de notificaciones falsificando la información del dispositivo. + "El botón de crear se cambia por el botón de notificaciones. + +Nota: Al activar esto también se ocultan forzosamente los anuncios de vídeos." + El botón de crear no se cambia por el botón de notificaciones. + "Desactivar esto podría cargar más anuncios del servidor. + +Además, los anuncios ya no se bloquearán en Shorts. -• Aunque cambies este ajuste, es posible que no surta efecto hasta que reinicies el dispositivo. -• Al desactivar este ajuste se cargan más anuncios desde el servidor. -• Debes desactivar este ajuste para que los anuncios de vídeo sean visibles." +Si este ajuste no surte efecto, prueba a cambiar al modo incógnito." Activar barra de navegación translúcida La barra de navegación es translúcida. La barra de navegación es opaca. + Ocultar barra de navegación + La barra de navegación está oculta. + La barra de navegación está visible. Menú de configuración Ocultar elementos del menú de configuración de YouTube. @@ -554,12 +594,12 @@ Nota: La reproducción automática se puede cambiar en la configuración de YouTube: \"Configuración → Reproducción automática → Reproducción automática del siguiente vídeo\"" La pantalla final del vídeo sugerido está visible. - Ocultar superposición del zoom - La superposición del zoom está oculta. - La superposición del zoom está visible. Omitir cuenta atrás de reproducción automática Si la reproducción automática está activada, el siguiente vídeo se reproducirá sin cuenta atrás. Si la reproducción automática está activada, el siguiente vídeo se reproducirá después de que termine la cuenta atrás. + Ocultar superposición del zoom + La superposición del zoom está oculta. + La superposición del zoom está visible. Botones de acción Ocultar o mostrar botones de acción bajo los vídeos. @@ -1006,30 +1046,6 @@ Limitación: las cabeceras oficiales en los resultados de búsqueda estarán ocu Ocultar botón de compras El botón de compras está oculto. El botón de compras está visible. - Ocultar botón de comprar - El botón de comprar está oculto. - El botón de comprar está visible. - Ocultar botón de súper gracias - El botón de súper gracias está oculto. - El botón de súper gracias está visible. - Ocultar productos etiquetados - Los productos etiquetados están ocultos. - Los productos etiquetados están visibles. - Ocultar botón de ubicación - El botón de ubicación está oculto. - El botón de ubicación está visible. - Ocultar botón de guardar sonido - El botón de guardar sonido está oculto. - El botón de guardar sonido está visible. - Ocultar botón de sugerencias de búsqueda - El botón de sugerencias de búsqueda está oculto. - El botón de sugerencias de búsqueda está visible. - Ocultar botón de utilizar este sonido - El botón de utilizar este sonido está oculto. - El botón de utilizar este sonido está visible. - Ocultar botón de utilizar plantilla - El botón de utilizar plantilla está oculto. - El botón de utilizar plantilla está visible. Ocultar paneles de información Los paneles de información están ocultos. Los paneles de información están visibles. @@ -1048,6 +1064,35 @@ Limitación: las cabeceras oficiales en los resultados de búsqueda estarán ocu Ocultar etiqueta de enlace de vídeo completo La etiqueta de enlace de vídeo está oculta. La etiqueta de enlace de vídeo está visible. + + Acciones sugeridas + Ocultar botón de pantalla verde + El botón de la pantalla verde está oculto. + El botón de la pantalla verde está visible. + Ocultar botón de guardar sonido + El botón de guardar sonido está oculto. + El botón de guardar sonido está visible. + Ocultar botón de comprar + El botón de comprar está oculto. + El botón de comprar está visible. + Ocultar botón de súper gracias + El botón de súper gracias está oculto. + El botón de súper gracias está visible. + Ocultar botón de utilizar este sonido + El botón de utilizar este sonido está oculto. + El botón de utilizar este sonido está visible. + Ocultar botón de utilizar plantilla + El botón de utilizar plantilla está oculto. + El botón de utilizar plantilla está visible. + Ocultar botón de ubicación + El botón de ubicación está oculto. + El botón de ubicación está visible. + Ocultar botón de sugerencias de búsqueda + El botón de sugerencias de búsqueda está oculto. + El botón de sugerencias de búsqueda está visible. + Ocultar productos etiquetados + Los productos etiquetados están ocultos. + Los productos etiquetados están visibles. Botones de acción Ocultar botón de me gusta @@ -1070,6 +1115,9 @@ Limitación: las cabeceras oficiales en los resultados de búsqueda estarán ocu El botón de sonido está visible. Animación / Comentarios + Desactivar animación del botón de me gusta + La animación de la fuente está desactivada sobre el botón de me gusta. + La animación de la fuente está activada sobre el botón de me gusta. Ocultar fondo del botón de reproducir y pausar El fondo del botón está oculto. El fondo del botón está visible. @@ -1097,6 +1145,9 @@ Problema conocido: Al tratarse de una función en fase de desarrollo por parte d Ocultar barra de navegación La barra de navegación está oculta. La barra de navegación está visible. + Porcentaje de altura del espacio vacío + Configura el porcentaje de altura del espacio vacío izquierdo cuando la barra de navegación está oculta, entre 0 y 100 (%). + El porcentaje de altura debe estar entre 0-100 (%). Reemplazar nombre de usuario del canal Se utiliza el nombre del canal. Se utiliza el nombre de usuario del canal. @@ -1200,6 +1251,13 @@ Limitación: Es posible que este ajuste no se aplique a los vídeos que no inclu El mensaje está oculto. Falsificar dimensiones del dispositivo "Falsifica las dimensiones del dispositivo para desbloquear calidades de vídeo superiores que pueden no estar disponibles en tu dispositivo." + Desactivar códec VP9 + "El códec VP9 está desactivado. + +• La resolución máxima es 1080p. +• La reproducción de vídeo utilizará más datos de Internet que VP9. +• Para obtener reproducción HDR, el vídeo HDR sigue utilizando el códec VP9." + El códec VP9 está activado. Reemplazar códec AV1 del software Reemplaza el códec AV1 del software con el códec VP9. Rechazar respuesta del códec AV1 del software @@ -1413,6 +1471,7 @@ Pulsa aquí para obtener más información. Nombre de usuario cambiado correctamente. Tu reputación es de <b>%.2f</b> Has creado <b>%s</b> segmentos + Pulsa aquí para ver tus segmentos. Tabla de clasificación de SponsorBlock Has salvado personas de <b>%s</b> segmentos Pulsa aquí para ver las estadísticas globales y los mejores colaboradores. @@ -1505,9 +1564,9 @@ Pulsa el botón de continuar y desactiva las optimizaciones de la batería."TV HTML5 Web Efectos secundarios de falsificación - • Las películas o vídeos de pago no pueden reproducirse. + "• Las películas o vídeos de pago no pueden reproducirse." • Falta el menú de la pista de audio. - • Falta el menú de la pista de audio. + "• Falta el menú de la pista de audio." • El vídeo no puede reproducirse. Forzar iOS AVC (H.264) El códec de vídeo de iOS es AVC (H.264). @@ -1549,6 +1608,7 @@ AVC (H.264) tiene una resolución máxima de 1080p, y la reproducción de vídeo Revancify Blue Revancify Red YouTube + YouTube (cabecera mínima) Predeterminada Excluidos Incluidos diff --git a/src/main/resources/youtube/translations/fr-rFR/strings.xml b/src/main/resources/youtube/translations/fr-rFR/strings.xml index aae6383d65..882c47e932 100644 --- a/src/main/resources/youtube/translations/fr-rFR/strings.xml +++ b/src/main/resources/youtube/translations/fr-rFR/strings.xml @@ -6,6 +6,7 @@ ReVanced Extended Rechercher sur %s + Réinitialiser les valeurs par défaut. Options expérimentales Voulez-vous continuer ? Redémarrer pour charger l\'interface correctement @@ -23,13 +24,6 @@ Veuillez télécharger %2$s à partir du site web." Masquer les publicités en plein écran Les publicités en plein écran sont masquées. Les publicités en plein écran sont affichées. - Fermer les publicités en plein écran - Les publicités en plein écran sont fermées grâce au bouton \"Fermer\". - "Les publicités en plein écran sont bloquées. - -Effet secondaire : Les images des posts communautaires peuvent être bloquées en plein écran." - La publicité en plein écran a été bloquée. (Type de dialogue : %s) - La publicité en plein écran a été fermée. Masquer les publicités générales Les publicités générales sont masquées. Les publicités générales sont affichées. @@ -79,6 +73,7 @@ Cliquez ici pour en savoir plus sur DeArrow." N\'affiche pas de message si DeArrow est indisponible. Point de connexion à l\'API DeArrow L\'URL du point de connexion au cache des miniatures DeArrow. + URL de l\'API DeArrow invalide. Informations sur la capture de miniature Les miniatures peuvent être capturées du début, du milieu ou de la fin de chaque vidéo. Les miniatures sont intégrées à YouTube, aucune API externe n\'est utilisée. Utiliser des captures rapides @@ -100,6 +95,7 @@ Cliquez ici pour en savoir plus sur DeArrow." Masquer les fiches d\'album Les fiches d\'album sont masquées. Les fiches d\'album sont affichées. + Masquer les étagères à suggestions "Masque les étagères suivantes : • Actualités • Continuer à regarder @@ -107,13 +103,15 @@ Cliquez ici pour en savoir plus sur DeArrow." • Écouter à nouveau • Produits • Regarder à nouveau" - Masquer les étagères à suggestions Masquer des étagères L\'étagère \"Vous pourriez aussi aimer\" est masquée. L\'étagère \"Vous pourriez aussi aimer\" est affichée. Masquer les menus déroulants sous les vidéos Les menus déroulants sont masqués. Les menus déroulants sont affichés. + Masquer les étagères coulissantes + Les étagères coulissantes sont masqués. + Les étagères coulissantes sont affichés. Masquer les \"Sous-titres\" dans les flux Le bouton \"Sous-titres\" est masqué. Le bouton \"Sous-titres\" est affiché. @@ -254,18 +252,21 @@ Limitations : • Les vidéos avec des phrases telles que \"Les internautes ont aussi regardé cette vidéo\" en dessous." Masquer les vidéos peu vues Masque les vidéos avec moins de 1,000 vues dans le flux \"accueil\" qui ont été mis en ligne par des personnes dont vous n\'êtes pas abonnés. - - Filtre de durée - Masquer des vidéos selon la durée - Masque les vidéos inférieure ou supérieure à la durée.\n\nProblème connu : cela ne masque pas les vidéos similaires du lecteur, mais l\'horodatage sera masqué. - Supérieur à la durée - Les vidéos supérieures à cette durée sont masqués. - Inférieur à la durée - Les vidéos inférieures à cette durée sont masqués. + Masquer les vidéos À SUIVRE + "Masquer les vidéos avec l'étiquette À SUIVRE. + +Remarque : Activer ceci masque également le bouton \"M'avertir\"." Filtre du compteur de vues - Masquer les vidéos recommandées par vues - Masque les vidéos recommandées ayant un nombre spécifié inférieur au nombre de vues.\n\nProblème connu : Les vidéos avec 0 vue ne sont pas filtrés. + Masquer les vidéos de la page d\'accueil par vues + Les vidéos de la page d\'accueil sont filtrés. + Les vidéos de la page d\'accueil ne sont pas filtrés. + Masquer les résultats de recherche par vues + Les résultats de recherche sont filtrés. + Les résultats de recherche ne sont pas filtrés. + Masquer les vidéos de l\'onglet Abonnement par vues + Les vidéos dans l\'onglet Abonnement sont filtrés. + Les vidéos dans l\'onglet Abonnement ne sont pas filtrés. Supérieur au nombre de vues Les vidéos supérieurs à ce nombre de vues sont masqués. Inférieur au nombre de vues @@ -273,6 +274,18 @@ Limitations : Voir les filtres Spécifiez le modèle de langue pour le nombre de vues affiché sous chaque vidéo dans l\'application. Chaque clé (lettre/mot dans votre langue) -> (signification de la clé) doit être sur une nouvelle ligne. Les clés sont placées avant le signe \"->\". Si vous changez la langue de l\'application ou du système, vous devez réinitialiser ce paramètre.\n\nExemples:\nFrançais: 10K vues = K -> 1000, vues -> vues\nAnglais: 10K views = K -> 1000, views -> vues K -> 1000\nM -> 1 000 000\nMd -> 1 000 000 000\nviews -> vues + À propos du filtrage par nombre de vues + "Les onglets Accueil / Abonnement et les résultats de la recherche sont filtrés pour masquer les vidéos dont le nombre de vues est inférieur ou supérieur à un nombre spécifié. + +Limitations : +- Les shorts ne peuvent pas être masqués. +- Les vidéos avec 0 vue ne sont pas filtrées." + Masquer les vidéos similaires + Les vidéos similaires sont masqués. + Les vidéos similaires sont affichés. + "Ce paramètre limite le nombre maximum de mises en page pouvant être chargées sur l'écran du lecteur. + +Si la mise en page de l'écran du lecteur change en raison de modifications côté serveur, des mises en page non souhaitées peuvent être masquées sur l'écran du lecteur." Interface Modifier la page de démarrage @@ -292,7 +305,11 @@ Limitations : Abonnements Tendances Regarder plus tard - Page de démarrage invalide, valeur réinitialisée par défaut. + Modifier le type de page de démarrage + "La page d'accueil change toujours. + +Limitation : Le bouton Retour de la barre d'outils peut ne pas fonctionner." + La page d\'accueil ne change qu\'une seule fois. Désact. les pistes audio forcés Les pistes audio automatiques forcées sont désactivé. Les pistes audio automatiques forcées sont activés. @@ -321,14 +338,6 @@ Cela ne contourne pas la restriction d'âge, mais le confirme automatiquement."< Falsifie le dpi pour activer l\'interface téléphone. Activer l\'interface tablette Falsifie le dpi pour activer l\'interface tablette. - Remplacer le bouton de téléchargement de la vidéo - Le bouton \"Télécharger\" natif ouvre votre téléchargeur externe. - Le bouton \"Télécharger\" natif ouvre le téléchargeur de l\'appli. - Remplacer le bouton de téléchargement de la playlist - Le bouton de téléchargement natif de la playlist est toujours affiché, tandis que les playlists publiques utilisera votre téléchargeur externe. - Si affiché, le bouton \"Télécharger\" natif de la playlist ouvre le téléchargeur natif de l\'appli. - Nom du paquet du téléchargeur de la playlist - Nom de package du téléchargeur externe installé, telle que YTDLnis. Falsifier la version de l\'app Version falsifiée Version non falsifiée @@ -368,6 +377,30 @@ Certains composants peuvent ne pas être masqués." Filtrer la liste des noms du composant séparés par un saut de ligne. Filtre personnalisé invalide : %s. + + Boutons d\'action + Remplace l\'action des boutons in-app. + + Bouton \"Télécharger\" + Remplacer le bouton de téléchargement de la vidéo + Le bouton \"Télécharger\" natif ouvre votre téléchargeur externe. + Le bouton \"Télécharger\" natif ouvre le téléchargeur de l\'appli. + Remplacer le bouton de téléchargement de la playlist + Le bouton de téléchargement natif de la playlist est toujours affiché, tandis que les playlists publiques utilisera votre téléchargeur externe. + Si affiché, le bouton \"Télécharger\" natif de la playlist ouvre le téléchargeur natif de l\'appli. + Nom du paquet du téléchargeur de la playlist + Nom de package du téléchargeur externe installé, telle que YTDLnis. + + Remplacer le bouton \"YouTube Music\" + Le bouton \"YouTube Music\" ouvre RVX Music. + Le bouton \"YouTube Music\" ouvre l\'appli natif. + Nom du paquet de RVX Music + Nom du paquet de RVX Music installé. + RVX Music + Attention + %s n\'est pas installé. Veuillez l’installer. + Prérequis + YouTube Music est requis pour remplacer l\'action du bouton. Cliquez ici pour télécharger YouTube Music. Minilecteur Change le style du lecteur minimisé de l\'application. @@ -398,10 +431,10 @@ Certains composants peuvent ne pas être masqués." Les boutons \"Avancer\" et \"Reculer\" sont affichés. Opacité du mini lecteur Valeur d\'opacité entre 0-100, 0 étant transparent. - L\'opacité du minilecteur doit être compris entre 0-100. Valeur réinitialisée par défaut. - - Barre de navigation - Masque ou affiche les éléments de la barre de navigation. + L\'opacité du minilecteur doit être compris entre 0-100. + + Barre de navigation + Masque ou affiche les éléments de la barre de navigation. Activer les boutons de navigation compacts L\'espacement entre les boutons de la barre de navigation sont réduit. L\'espacement entre les boutons de la barre de navigation sont normaux. @@ -427,14 +460,21 @@ Certains composants peuvent ne pas être masqués." Le nom des catégories sont masqués. Le nom des catégories sont affichés. Échanger \"Créer\" et \"Notifications\" - "Échange la position des boutons \"Créer\" et \"Notifications\" en falsifiant les informations de l'appareil. + "Le bouton \"Créer\" est échangé avec le bouton \"Notification\". + +Note : Activer ceci masquera également les publicités vidéos." + Le bouton \"Créer\" n\'est pas échangé avec le bouton \"Notification\". + "Désactiver ceci pourrait charger plus de publicités depuis le serveur. -• L'appareil peut nécessiter un redémarrage pour que ce paramètre prenne effet. -• Désactiver ce paramètre charge plus de publicités depuis le serveur. -• Vous devez désactiver ce paramètre afin de rendre les publicités vidéo visibles." +Également, les publicités ne seront plus bloquées sur les Shorts. + +Si ce paramètre ne fait pas effet, essayer de passer en mode Incognito." Activer la barre de navigation translucide La barre de navigation est translucide. La barre de navigation est opaque. + Masquer la barre de navigation + La barre de navigation est masqué. + La barre de navigation est affichée. Menu paramètre Masque des éléments dans le menu paramètre YouTube. @@ -491,7 +531,7 @@ Appuyez longuement pour ouvrir les paramètres RVX." Lecteur Personnaliser l\'opacité du voile du lecteur Valeur d\'opacité entre 0-100, 0 étant transparent. - L\'opacité du voile du lecteur doit être entre 0-100. Valeur réinitialisée par défaut. + L\'opacité du voile du lecteur doit être entre 0-100. Fenêtres pop-up du lecteur automatique Les fenêtres pop-up du lecteur automatique sont désactivées. Les fenêtres pop-up du lecteur automatique sont activés. @@ -510,7 +550,7 @@ Note : • Désactiver ce paramètre ne force pas l'activation de contrôle vitesse." Valeur de \"Vitesse de lecture\" La valeur de vitesse de lecture doit être comprise entre 0-8.0. - La valeur de la vitesse de lecture doit être comprise entre 0-8.0. Valeur réinitialisée par défaut. + La valeur de la vitesse de lecture doit être comprise entre 0-8.0. Masquer le filigrane de chaine Le filigrane de chaîne est masqué. Le filigrane de chaîne est affiché. @@ -556,12 +596,12 @@ Note : La lecture automatique peut être modifiée dans les paramètres de YouTube : 'Paramètres → Lecture automatique → Lecture automatique de la vidéo suivante'" Les suggestions de vidéos à la fin sont affichés. - Masquer le voile du zoom - Le voile du zoom est masqué. - Le voile du zoom est affiché. Ignorer le compteur lecture auto Si la lecture automatique est activée, la vidéo suivante sera lue immédiatement. Si la lecture automatique est activée, la vidéo suivante sera lue après la fin du compte à rebours. + Masquer le voile du zoom + Le voile du zoom est masqué. + Le voile du zoom est affiché. Boutons sous la vidéo Masque ou affiche les boutons sous les vidéos. @@ -767,7 +807,7 @@ Limitation : Le titre de la vidéo disparaît lorsque vous cliquez dessus."Le bouton \"Partager\" est affiché. Hauteur de la barre de progression Configure l\'espacement entre la barre de progression et le conteneur d\'actions rapides, entre 0 et 32. - La hauteur de l\'action rapide doit être comprise entre 0 et 32. Valeur réinitialisée par défaut. + La hauteur de l\'action rapide doit être comprise entre 0 et 32. Désactiver le mode paysage Le passage en mode paysage en plein écran est désactivé. @@ -1007,30 +1047,6 @@ Effet secondaire : Les fiches officielles dans les résultats de recherche sont Masquer le bouton \"Produit\" Le bouton \"Produit\" est masqué. Le bouton \"Produit\" est affiché. - Masquer le bouton \"Magasin\" - Le bouton \"Magasin\" est masqué. - Le bouton \"Magasin\" est affiché. - Masquer le bouton \"Remercier\" - Le bouton \"Remercier\" est masqué. - Le bouton \"Remercier\" est affiché. - Masquer les produits associés - Les produits associés sont masqués. - Les produits associés sont affichés. - Masquer le bouton \"Localisation\" - Le bouton \"Localisation\" est masqué. - Le bouton \"Localisation\" est affiché. - Masquer le son du bouton \"Playlist\" - Le son d\'enregistrement dans la Playlist est masqué. - Le son d\'enregistrement dans la Playlist est affiché. - Masquer le bouton \"Suggestions de recherche\" - Le bouton \"Suggestions de recherche\" est masqué. - Le bouton \"Suggestions de recherche\" est affiché. - Masquer le bouton \"Utiliser ce son\" - Le bouton \"Utiliser ce son\" est masqué. - Le bouton \"Utiliser ce son\" est affiché. - Masquer le bouton \"Utiliser le modèle\" - Le bouton \"Utiliser le modèle\" est masqué. - Le bouton \"Utiliser le modèle\" est affiché. Masquer les panneaux d\'information Les panneaux d\'information sont masqués. Les panneaux d\'information sont affichés. @@ -1049,6 +1065,35 @@ Effet secondaire : Les fiches officielles dans les résultats de recherche sont Masquer le lien de la vidéo complète Le lien vers la vidéo complète est masqué. Le lien vers la vidéo complète est affiché. + + Actions suggérées + Masquer le bouton \'fond vert\' + Le bouton \'fond vert\' est masqué. + Le bouton \'fond vert\' est affiché. + Masquer le bouton \"Enregistrer le son\" + Le bouton \"Enregistrer le son\" est masqué. + Le bouton \"Enregistrer le son\" est affiché. + Masquer le bouton \"Magasin\" + Le bouton \"Magasin\" est masqué. + Le bouton \"Magasin\" est affiché. + Masquer le bouton \"Remercier\" + Le bouton \"Remercier\" est masqué. + Le bouton \"Remercier\" est affiché. + Masquer le bouton \"Utiliser ce son\" + Le bouton \"Utiliser ce son\" est masqué. + Le bouton \"Utiliser ce son\" est affiché. + Masquer le bouton \"Utiliser le modèle\" + Le bouton \"Utiliser le modèle\" est masqué. + Le bouton \"Utiliser le modèle\" est affiché. + Masquer le bouton \"Localisation\" + Le bouton \"Localisation\" est masqué. + Le bouton \"Localisation\" est affiché. + Masquer le bouton \"Suggestions de recherche\" + Le bouton \"Suggestions de recherche\" est masqué. + Le bouton \"Suggestions de recherche\" est affiché. + Masquer les produits associés + Les produits associés sont masqués. + Les produits associés sont affichés. Boutons d\'action Masquer le bouton \"J\'aime\" @@ -1071,6 +1116,9 @@ Effet secondaire : Les fiches officielles dans les résultats de recherche sont Le bouton \"Son\" est affiché. Animation / Retour d\'expérience + Désactiver l\'animation du bouton j\'aime + L\'animation en cascade est désactivé pour le bouton j\'aime. + L\'animation en cascade est activé pour le bouton j\'aime. Masquer fond du bouton Lecture & Pause Le fond du bouton est masqué. Le fond du bouton est affiché. @@ -1092,14 +1140,17 @@ Limitations : Action de l\'horodatage appui long Appuyez longuement sur l\'horodatage pour modifier l\'état de répétition des Shorts. Marge en bas du panneau Meta - Définir l\'espacement entre la barre de progression et le panneau méta, entre 0-64. - La marge en bas du panneau méta doit être entre 0-64, Valeur réinitialisée par défaut. + Configurer l\'espace de la barre de progression au panneau méta, entre 0-64. + La marge en bas du panneau méta doit être entre 0-64. Masquer la barre d\'outils La barre d\'outils est masqué. La barre d\'outils est affiché. Masquer la barre de navigation La barre de navigation est masqué. La barre de navigation est affichée. + Hauteur en pourcentage de l\'espace vide + Configure la hauteur en pourcentage de l\'espace vide à gauche lorsque la barre de navigation est cachée, entre 0 et 100 (%). + La hauteur en pourcentage doit être entre 0-100 (%). Remplacer l\'identifiant de la chaîne Le nom de la chaîne est utilisé. L\'identifiant de la chaîne est utilisé. @@ -1134,7 +1185,7 @@ Limitations : La taille du texte pendant le voile lors du geste. Taille de la zone de gestes Pourcentage de la zone de l\'écran pouvant être glissée.\n\nNote : Cela affecte également la zone du double appui pour avancer/reculer dans la vidéo. - La zone glissable ne peut pas être supérieure à 50. Valeur réinitialisée par défaut. + La zone glissable ne peut pas être supérieure à 50. Durée du voile lors des gestes La quantité de millisecondes pendant laquelle la superposition est visible. Luminosité HDR automatique @@ -1203,6 +1254,13 @@ Info : Falsifier les dimensions de l\'appareil "Falsifie les dimensions de l'appareil a la valeur maximale. Les hautes qualités peuvent être débloquées sur certaines vidéos qui requièrent des appareils ayant des dimensions élevées, mais pas sur toutes les vidéos." + Désactiver le codec VP9 + "Le codec VP9 est désactivé. + +• La résolution maximale est en 1080p. +• La lecture vidéo utilisera plus de données internet que le VP9. +• Le codec VP9 est également utilisé pour les vidéos HDR." + Le codec VP9 est activé. Remplacer le codec AV1 Remplacer le codec AV1 par le codec VP9. Rejeter la réponse du codec AV1 @@ -1213,8 +1271,8 @@ Un codec différent sera appliqué après environ 20 secondes de mise en mémoir La résolution sur les données mobiles a été modifiée par %s. Impossible de définir la qualité vidéo. La résolution sur le Wi-Fi a été modifiée par %s. - Les vitesses personnalisées doivent être inférieures à %sx. Valeur réinitialisée par défaut. - Vitesses de lecture invalides. Valeur réinitialisée par défaut. + Les vitesses personnalisées doivent être inférieures à %sx. + Valeur des vitesses de lecture invalide. Return YouTube Dislike Activer Return YouTube Dislike @@ -1415,6 +1473,7 @@ Limitation : les \"Je n'aime pas\" ne seront pas affichées si vous n'êtes pas Nom d\'utilisateur modifié avec succès. Votre réputation est de <b>%.2f</b> Vous avez créé <b>%s</b> segment(s) + Appuyez ici pour voir vos segments. Classement SponsorBlock Vous avez sauvé les utilisateurs de <b>%s</b> segments Appuyez ici pour voir les statistiques globales et les meilleurs contributeurs. @@ -1507,9 +1566,13 @@ Cliquez sur le bouton Continuer et désactivez les optimisations de la batterie. TV HTML5 Web Effets inconnus de la falsification - • Les films ou les vidéos payantes peuvent ne pas être lus. + "• Les films ou les vidéos payantes peuvent ne pas être lus. +• Les diffusions en direct commencent au début. +• Les vidéos peuvent se terminer une seconde avant. +• Pas de codec audio opus." • Le menu \"Piste Audio\" est manquant. - • Le menu \"Piste Audio\" est manquant. + "• Le menu \"Piste Audio\" est manquant. +• Le volume stable n'es pas disponible." • Les vidéos peuvent ne pas être lus. Forcer iOS AVC (H.264) Le codec vidéo d\'iOS est AVC (H.264). @@ -1551,6 +1614,7 @@ AVC (H.264) a une résolution maximale de 1080p, et la lecture vidéo utilisera Revancify Bleu Revancify Rouge Youtube + YouTube (En-tête minimal) Officiel Exclus Appliqué diff --git a/src/main/resources/youtube/translations/hu-rHU/strings.xml b/src/main/resources/youtube/translations/hu-rHU/strings.xml index b7b790842b..b8819ec90e 100644 --- a/src/main/resources/youtube/translations/hu-rHU/strings.xml +++ b/src/main/resources/youtube/translations/hu-rHU/strings.xml @@ -6,6 +6,7 @@ ReVanced Extended %s keresése + Visszaállítás az alapértelmezett értékekre. Kísérleti funkciók Szeretnéd folytatni? Indítsa újra a rendszert a normál elrendezés betöltéséhez @@ -23,13 +24,6 @@ Töltsd le a(z) %2$s weboldalról." Teljes képernyős hirdetések elrejtése A teljes képernyős hirdetések rejtve vannak. A teljes képernyős hirdetések láthatók. - Teljes képernyős hirdetések bezárása - A teljes képernyős reklámok a Bezár gombbal záródnak. - "A teljes képernyős hirdetések letiltva. - -Korlátozás: előfordulhat, hogy a közösségi bejegyzés képe a teljes képernyőn le van tiltva." - A teljes képernyős hirdetések letiltva. (Dialógus típusa: %s) - A teljes képernyős hirdetések bezáródtak. Általános hirdetések elrejtése Az általános hirdetések el vannak rejtve. Az általános hirdetések láthatóak. @@ -39,6 +33,9 @@ Korlátozás: előfordulhat, hogy a közösségi bejegyzés képe a teljes képe Fizetett promóció címke elrejtése A fizetett promóció címke rejtve van A fizetett promóciós címke látható. + Promóciós figyelmeztető banner elrejtése + A promóciós figyelmeztető banner el van rejtve. + A promóciós figyelmeztető banner látható. Önpromóciós kártyák elrejtése Az önpromóciós kártyák el vannak rejtve. Az önpromóciós kártyák láthatóak. @@ -76,6 +73,7 @@ Koppints ide, ha többet szeretnél megtudni a DeArrow-ról." Nem jelenik meg üzenet, ha a DeArrow nem elérhető. DeArrow API végpont A DeArrow bélyegkép cache végpont URL-je. + Érvénytelen DeArrow API URL. Videó pillanatképek Pillanatképek minden videó elejéről/közepéről/végéről készülnek. Ezek a képek be vannak építve a YouTube-ba és nem használnak külső API-t. Gyors pillanatképek használata @@ -97,6 +95,7 @@ Koppints ide, ha többet szeretnél megtudni a DeArrow-ról." Album kártyák elrejtése Az album kártyák el vannak rejtve. Az album kártyák láthatóak. + Forduló polc elrejtése "A következő polcokat rejtse el: • Friss hírek • Folytassa a nézést @@ -104,13 +103,15 @@ Koppints ide, ha többet szeretnél megtudni a DeArrow-ról." • Hallgassa meg újra • Vásárlás • Nézze meg újra" - Forduló polc elrejtése Vágások polc elrejtése A vágások polc elrejtve. A vágások polc látható. Bővíthető vágások elrejtése a videók alatt A bővíthető vágások el vannak rejtve. A bővíthető vágások megjelennek. + Kinyitható polcok elrejtése + A kinyitható polcok el vannak rejtve. + A kinyitható polcok láthatóak. Rejtsd el a hírfolyam feliratok gombját A Feliratok gomb el van rejtve. A Feliratok gomb megjelenik. @@ -199,64 +200,88 @@ Lejátszási listák Megjelenítve a feliratkozások között. Felugró menü - Komponensek elrejtése vagy megjelenítése a hírfolyam lebegő menüjében. - Hírfolyam lebegő menü szűrő bekapcsolása - A hírfolyam lebegő menü szűrő be van kapcsolva. + Komponensek elrejtése vagy megjelenítése a hírfolyam előugró menüjében. + Hírfolyam lebegő menü szűrő engedélyezése + A hírfolyam lebegő menü szűrő engedélyezve van. A hírfolyam lebegő menü szűrő ki van kapcsolva. Hírfolyam lebegő menü szűrő - Szűrendő lebegő menü neveinek listája, új sorral elválasztva. + Szűrendő lebegő menü neveinek listája, új sorokkal elválasztva. Videó szűrő A videók elrejtése kulcsszavak vagy nézettség alapján. Kulcsszó szűrő Videók elrejtése a kezdőlapon kulcsszavak alapján - A videók a kezdőlapon kulcsszavak alapján szűrve - A videók a kezdőlapon nincsenek szűrve + A videók a kezdőlapon szűrve vannak. + A videók a kezdőlapon nincsenek szűrve. Keresési eredmények elrejtése kulcsszavak alapján A keresési eredmények szűrve vannak. A keresési eredmények nincsenek szűrve. - Videók elrejtése a feliratkozások lapon kulcsszavak alapján - A videók a feliratkozások lapon kulcsszavak alapján szűrve - A videók a feliratkozások lapon nincsenek szűrve + Feliratkozott videók elrejtése kulcsszavak alapján + A feliratkozott videók kulcsszavak alapján szűrve vannak. + A feliratkozott videók nincsenek szűrve. Kommentek elrejtése kulcsszavak alapján A kommentek szűrve vannak. A kommentek nincsenek szűrve. Elrejtendő kulcsszavak - "Elrejtendő kulcsszavak és kifejezések, új sorokkal elválasztva.\n\nA szóközépen nagy betűt tartalmazó szavaknál a kis- és nagybetűknek a megfelelő helyen kell lennie (pl: iPhone, TikTok, LeBlanc)" + "Elrejteni kívánt kulcsszavak és kifejezések, új sorokkal elválasztva.\n\nA kulcsszavak lehetnek csatornanevek vagy bármilyen szöveg, amely a videók címében látható.\n\nA középen nagybetűs szavakat a kis- és nagybetűkkel együtt kell megadni (pl. iPhone, TikTok, LeBlanc)." A kulcsszó alapú szűrésről - "A Kezdőlap/Feliratkozások/Keresés eredményei a kulcsszóval megegyező tartalom elrejtésére vannak szűrve\n\nKorlátozások\n• Néhány Shorts lehet, hogy nem lesz elrejtve\n• Néhány UI elem lehet, hogy nem lesz elrejtve\n• Előfordulhat, hogy a kulcsszó keresése nem hoz eredményt" - Érvénytelen kulcsszó.\'%s\'nem használható szűrőként - A(z) %1$s kulcsszó elrejtené az összes videót. + "Kezdőlapi / Feliratkozási / Keresési eredmények szűrve vannak egyező kulcsszavak alapján. + +Korlátozások: +• A rövidfilmeket nem lehet elrejteni a csatornanév alapján. +• Előfordulhat, hogy egyes felhasználói felület-összetevők nincsenek elrejtve. +• Előfordulhat, hogy a kulcsszó keresése nem ad eredményt." + Teljes szóegyezések + Ha egy kulcsszót/kifejezést dupla idézőjelekkel vesz körül, akkor elkerülhető a videócímek és a csatornanevek részleges egyezése<br><br>Például:<br><b>\"ai\"</b> elrejti a videót: <b>Hogyan működik az AI?</b><br>de nem rejti el: <b>Mit jelent a fair use?</b> + Érvénytelen kulcsszó:\'%s. + Adjon hozzá idézőjeleket a következő kulcsszóhoz: %s. + A kulcsszónak ütköző deklarációi vannak: %s. + A keresőszó túl rövid, és idézőjeleket igényel: %s. + A kulcsszó elrejti az összes videót: %s. Ajánlott videó Ajánlott videók elrejtése - "Az alábbi ajánlott videókat rejtse el: + "Elrejti a következő ajánlott videókat: -• Videókat, amelyeknek 'Csak tagsággal' címkéje van. -• Videókat, amelyek alatt olyan kifejezések találhatóak, mint a 'Mások is megnézték'. -• Azokat a videókat, amelyeket nem feliratkozott csatornákról töltöttek fel, és kevesebb, mint 1 000 megtekintésük van." +• Csak a tagok címkével ellátott videók. +• Olyan videók, amelyek alatt olyan kifejezések szerepelnek, mint „Mások is megnézték”." Alacsony nézettségű videók elrejtése Az 1000-nél kevesebb megtekintést elért videók elrejtése a Kezdőlapon, amiket a leiratkozott csatornákról töltöttek fel. - - Időtartam szűrő - Videók elrejtése hossz alapján - Videók elrejtése rövidebb vagy hosszabb időtartam alapján.\n\nIsmert hiba: Nem fogja elrejteni a lejátszóban található kapcsolódó videókat, csak az időbélyegjét. - Hosszabb, mint - Az ennél hosszabb videók el lesznek rejtve. - Rövidebb időtartamú, mint - Az ennél rövidebb időtartamú videók el lesznek rejtve. + KÖZELGŐ videó elrejtése + "Videók elrejtése KÖZELGŐ címkével. + +Megjegyzés: Ha ezt engedélyezi, akkor az Értesíts gomb is el lesz rejtve." Megtekintések szűrő - Ajánlott videók elrejtése megtekintések alapján - Az ajánlott videók elrejtése, ha a megtekintések száma kevesebb a megadott számnál.\n\nIsmert probléma: a 0 megtekintésű videókat a rendszer nem szűri. + Videók elrejtése a kezdőlapon a nézettség alapján + A videók a kezdőlapon szűrve vannak. + A videók a kezdőlapon nincsenek szűrve. + Keresési eredmények elrejtése nézettség alapján + A keresési eredmények szűrve vannak. + A keresési eredmények nincsenek szűrve. + Feliratkozott videók elrejtése nézettség alapján + A feliratkozott videók szűrve vannak. + A feliratkozott videók nincsenek szűrve. Nagyobb a nézettsége, mint Az ennél nagyobb nézettségű videók el lesznek rejtve. Kisebb a nézettsége, mint Az ennél kisebb nézettségű videók el lesznek rejtve. - Nézettség kulcsok + Kulcsok megtekintése Adja meg nyelvi sablonját a videók alatt megjelenő nézetek számához a felhasználói felületen. Minden kulcs (a nyelvében levő betű/szó) -> érték (a kulcs jelentése) új sorban legyen. A kulcsok a \"->\" előtt helyezkednek el. Ha megváltoztatja az alkalmazás vagy a rendszer nyelvét, akkor vissza kell állítania ezt a beállítást.\n\nPéldák:\nAngol: 10K views = K -> 1000, views -> megtekintések\nSpanyol: 10 K vistas = K -> 1000, vistas -> megtekintések K -> 1 000\nM -> 1 000 000\nB -> 1 000 000 000\nviews -> megtekintések + Szűrés nézettség alapján névjegy + "Kezdőlap / Feliratkozás / Keresés eredményei szűrve vannak, hogy elrejtse a meghatározott számnál kisebb vagy nagyobb nézettségű videókat. + +Korlátozások: +• A Shortokat nem lehet elrejteni. +• A 0 megtekintésű videók nincsenek kiszűrve." + Kapcsolódó videók elrejtése + A kapcsolódó videók el vannak rejtve. + A kapcsolódó videók láthatóak. + "Ez a beállítás korlátozza a lejátszó képernyőjére betölthető elrendezések maximális számát. + +Ha a lejátszó képernyőjének elrendezése a szerveroldali változtatások miatt megváltozik, akkor esetleg a nem kívánt elrendezések elrejthetők a lejátszó képernyőjén." Általános Kezdőlap megváltoztatása @@ -276,7 +301,11 @@ Lejátszási listák Feliratkozások Felkapott Megnézem később - Érvénytelen kezdőlap, visszaállítás az eredetire. + Kiinduló lap megváltoztatása + "A kiinduló lap mindig változik. + +Korlátozás: Előfordulhat, hogy az eszköztár Vissza gombja nem működik." + A kiinduló lap csak egyszer változik. Kényszerített automatikus hangsávok letiltása A kényszerített automatikus hangsávok le vannak tiltva. A kényszerített automatikus hangsávok engedélyezve vannak. @@ -305,14 +334,6 @@ Ez nem kerüli meg a korhatárt. Csak automatikusan fogadja el." Hamisítja a dpi-t néhány telefonos elrendezés használatához. Tablet felület engedélyezése Hamisítja a dpi-t néhány táblagépes elrendezés használatához. - Videó letöltési gomb felülbírálása - Az eredeti letöltés gomb megnyitja a külső letöltőt. - Az eredeti letöltés gomb megnyitja az eredeti, beépített letöltőt. - Lejátszási lista letöltése gomb felülbírálása - Az eredeti lejátszási lista letöltése gomb megnyitja a külső letöltőt. - Az eredeti lejátszási lista letöltése gomb megnyitja az eredeti, beépített letöltőt. - Lejátszási lista letöltő csomag neve - A telepített külső letöltő alkalmazás csomagneve, például YTDLnis. Alkalmazásverzió hamisítása Verzió hamisítás Verzió nincs hamisítva @@ -328,9 +349,9 @@ Ha kikapcsolja, akkor ajánlott törölni az app adatait, hogy elkerülje a UI h 17.41.37 - Régi lejátszási lista polc visszállítása 18.05.40 - Régi megjegyzési szövegdoboz visszaállítása 18.17.43 - Régi előugró lejátszói ablak visszaállítása - 18.33.40 - Visszaállítás a régi Rövidítések műveletsávra + 18.33.40 - Régi Shorts művelet sáv visszaállítása 18.38.45 - Visszaállítja a régi alapértelmezett videó minőség viselkedést - 18.48.39 - Letiltja a \'nézetek\' és \'kedvelések\' valós idejű frissítését + 18.48.39 - Letiltja a \'Megtekintések\' és a \'Kedvelések\' valós idejű frissítését Fiók menü Elemek elrejtése vagy megjelenítése a fiók menüben és a Te fülön. @@ -338,20 +359,44 @@ Ha kikapcsolja, akkor ajánlott törölni az app adatait, hogy elkerülje a UI h "Fiókmenü és az Ön lap elemeinek elrejtése. Előfordulhat, hogy egyes komponensek nincsenek elrejtve." Fiók menü szűrő - A fiók menüben szűrendő nevek listája, új sorokkal elválasztva. + A fiók menüben szűrendő menüpontok listája, új sorokkal elválasztva. Kezelő elrejtése A kezelő el van rejtve. A kezelő látható. Egyéni szűrő Komponensek elrejtése egyéni szűrőkkel. - Egyedi szűrők engedélyezése - Az egyedi szűrő engedélyezve van - Az egyedi szűrő ki van kapcsolva + Egyéni szűrő engedélyezése + Az egyéni szűrő engedélyezett. + Az egyéni szűrő le van tiltva. Egyéni szűrő A szűrendő összetevő útvonal építő karakterláncok listája új sorral elválasztva - Érvénytelen egyedi szűrő: %s + Érvénytelen egyéni szűrő: %s. + + Gombok felülbírálása + Felülbírálja az alkalmazáson belüli gombok kattintási műveletét. + + Letöltés gomb + Videó letöltési gomb felülbírálása + Az eredeti letöltés gomb megnyitja a külső letöltőt. + Az eredeti letöltés gomb megnyitja az eredeti, beépített letöltőt. + Lejátszási lista letöltése gomb felülbírálása + Az eredeti lejátszási lista letöltése gomb megnyitja a külső letöltőt. + Az eredeti lejátszási lista letöltése gomb megnyitja az eredeti, beépített letöltőt. + Lejátszási lista letöltő csomag neve + A telepített külső letöltő alkalmazás csomagneve, például YTDLnis. + + YouTube Music gomb felülbírálása + A YouTube Music gomb az RVX Music-ot indítja. + A YouTube Music gomb az erdetit app-ot nyitja meg. + RVX Music csomag név + A telepített RVX Music csomag neve. + RVX Music + Figyelmeztetés + %s nincs telepítve. Kérlek telepítsd. + Előfeltétel + A YouTube Music szükséges a gombművelet felülbírálásához. Koppints ide a YouTube Music letöltéséhez. Minilejátszó Módosítsa az alkalmazáson belüli minilejátszó stílusát. @@ -362,11 +407,11 @@ Előfordulhat, hogy egyes komponensek nincsenek elrejtve." Modern 1 Modern 2 Modern 3 - Dupla koppintás művelet - "A dupla koppintás művelet engedélyezve. + Dupla koppintás művelet engedélyezése + "A dupla koppintás művelet engedélyezve van. -- Modern 1: A minimalizált videót nagyobb méretűre allítja. -- Modern 2, 3: A minimalizált videót bezárja." +• Koppintson duplán a kicsinyített videó nagyobb méretre való váltásához. +• Koppintson még egyszer duplán az eredeti méretre váltáshoz." Dupla koppintás művelet letiltva. Fogd és vidd engedélyezése A Fogd és vidd engedélyezve. @@ -383,10 +428,10 @@ Előfordulhat, hogy egyes komponensek nincsenek elrejtve." Átfedés átlátszósága Átlátszósági érték 0 és 100 között, ahol a 0 az átlátszó. A minilejátszó átlátszóságának 0 és 100 között kell lennie. Visszaállítás alapértelmezettre. - - Navigációs gombok - Elrejtés vagy megjelenítés a navigációs sáv szakasz komponensei. - Szűk navigációs gombok engedélyezése + + Navigációs sáv + Navigációs sáv komponenseinek láthatósága. + Keskeny navigációs gombok engedélyezése A navigációs gombok közötti távolság szűk. A navigációs gombok közötti távolság normális. Létrehozás gomb elrejtése @@ -401,29 +446,34 @@ Előfordulhat, hogy egyes komponensek nincsenek elrejtve." Értesítések gomb elrejtése Az értesítések gomb el van rejtve. Az értesítések gomb látható. - Rövidítés gomb elrejtése - A rövidítéseket gomb el van rejtve. - A rövidítéseket gomb látható. + Shorts gomb elrejtése + A Shorts gomb el van rejtve. + A Shorts gomb látható. Feliratkozás gomb elrejtése - Az előfizetések gombja el van rejtve. - Az előfizetések gombja látható. + Az előfizetések gomb el van rejtve. + Az előfizetések gomb látható. Navigációs sáv elrejtése A navigációs sáv el van rejtve. A navigációs sáv látható. - Létrehozás felcserélése az értesítésekkel - "A létrehozás gomb és az értesítés gomb helyzetét cseréli ki a készülék információinak meghamisításával. + Létrehozás és értesítések gombok felcserélése + "A Létrehozás gomb megcserélése az Értesítések gombbal.\n\nMegjegyzés: Engedélyezés esetén a videó hirdetéseket is elrejti." + A Létrehozás gomb nincs felcserélve az Értesítések gombbal. + "Ennek kikapcsolása esetleg több reklámot tölt be a szerverről. + +Tovább, a reklámok nem lesznek tiltva a Shortokban. -• A készüléket esetleg újra kell indítani a beállítás változtatásának érvényesítéséhez. -• Ha ezt a beállítást letiltja, több hirdetést tölt be a szerver oldalról. -• Ha a videó hirdetések láthatóak akarja tenni, letiltja ezt a beállítást." +Ha ez a beállítás nem működik, váltson inkognító módra." Áttetsző navigációs sáv engedélyezése A navigációs sáv áttetsző. A navigációs sáv nem áttetsző. + Navigációs sáv elrejtése + A navigációs sáv el van rejtve. + A navigációs sáv látható. Beállítások menü Elrejti a YouTube beállítások menü elemeit. YouTube beállítások menü elrejtése - Elrejt elemeket a YouTube beállítások menüben. + Elrejti a YouTube beállítások menü elemeit. YouTube beállítások menü szűrő A szűrni kívánt YouTube beállítások menü neveinek listája, új sorokkal elválasztva. "Fiók @@ -431,9 +481,9 @@ Adatmegőrzés Feliratok" Eszköztár - Elrejt vagy megváltoztat komponenseket az eszköztáron, mint például a keresősáv, eszköztárgombok és fejléc. + Elrejt vagy megváltoztat komponenseket az eszköztáron, mint például a keresősáv, eszköztár gombok és fejléc. YouTube fejléc módosítása - Prémium fejléc van engedélyezve. + A prémium fejléc van engedélyezve. Az általános fejléc van engedélyezve. Széles keresősáv engedélyezése A széles keresősáv engedélyezve van. @@ -479,6 +529,13 @@ Ide hosszan nyomva tartva megnyílik az RVX beállítások." Lejátszó felugró panelek letiltása Az automatikus lejátszó felugró panelek le vannak tiltva. Az automatikus lejátszó felugró panelek engedélyezve vannak. + Mix lejátszási listák letiltása + Az automatikus mix lejátszási listák le vannak tiltva. + "Az automatikus mix lejátszási listák engedélyezve vannak, ha az automatikus lejátszás be van kapcsolva. + +Az automatikus lejátszás a YouTube beállításaiban módosítható: +Beállítások → Automatikus lejátszás → Következő videó automatikus lejátszása" + A funkció engedélyezése letiltja az automatikus váltást a YouTube Mix szolgáltatásra, amikor zenét játszik le, miközben az automatikus lejátszás be van kapcsolva. Sebesség átfedés letiltása "Letiltja a '2x sebességgel lejátszás' funkciót, ha hosszan nyomva tartja. @@ -533,12 +590,12 @@ Megjegyzés: Az automatikus lejátszás a YouTube beállításaiban módosítható: Beállítások → Automatikus lejátszás → Következő videó automatikus lejátszása" Megjelenik a javasolt videó záróképernyője. - Nagyítás fedés elrejtése - A nagyítás fedés elrejtve. - A nagyítás fedés látható. Automatikus lejátszás visszaszámlálás átugrása Ha az automatikus lejátszás engedélyezve van, a következő videó azonnal lejátszódik. Ha az automatikus lejátszás engedélyezve van, a következő videó a visszaszámlálás befejezése után lejátszódik. + Nagyítás fedés elrejtése + A nagyítás fedés elrejtve. + A nagyítás fedés látható. Műveletgombok Videók alatti műveletgombok elrejtése vagy megjelenítése. @@ -661,6 +718,9 @@ Beállítások → Automatikus lejátszás → Következő videó automatikus le A jelentés menü megjelenik. További beállítások + \'Mozifilmes világítás\' menü elrejtése + A \'Mozifilmes világítás\' menü el van rejtve. + A \'Mozifilmes világítás\' menü látható. Segítség és visszajelzés menü elrejtése A segítség és visszajelzés menü el van rejtve. A segítség és visszajelzés menü megjelenik. @@ -676,6 +736,9 @@ Beállítások → Automatikus lejátszás → Következő videó automatikus le Prémium vezérlők menü elrejtése A Prémium vezérlők menü el van rejtve. A Prémium vezérlők menü megjelenik. + Elalvási időzítő elrejtése + Az elalvási időzítő el van rejtve. + Az elalvási időzítő látható. Rejtsd el a stabil hangerő menüt A stabil hangerő menü megjelenik. A stabil hangerő menü el van rejtve. @@ -927,6 +990,9 @@ A 'Videoleírások kibővítése' nem működik, ha a beírt sztring nem egyezik A Shorts lejátszás folytatásának kikapcsolása A Shorts lejátszás nem indul el az alkalmazás indításakor A Shorts lejátszás folytatódik az alkalmazás indításakor + Lebegő gomb elrejtése + "Az olyan lebegő gombok, mint a „Használja ezt a hangot”, el vannak rejtve a Shorts csatorna lapon." + "Az olyan lebegő gombok, mint a „Használja ezt a hangot”, a Shorts csatorna lapon láthatóak." Shorts polcok Rövid tartalmak polcának elrejtése @@ -969,18 +1035,12 @@ Korlát: A hivatalos fejlécek a keresési eredményekben el lesznek rejtve."Fizetett promóciós címke elrejtése A fizetett promóciós címke el van rejtve. A fizetett promóciós címke meg van jelenítve. - Vásárlás gomb elrejtése - A vásárlás gomb el van rejtve - A vásárlás gomb látható - Szuper köszönet gomb elrejtése - A Szuper köszönet gomb el van rejtve. - A Szuper köszönet gomb látható. - Címkézett termékek elrejtése - A címkézett termékek el vannak rejtve - A címkézett termékek megjelennek - Hang mentése a lejátszási listára gomb elrejtése - A hang mentése a lejátszási listára gomb el van rejtve - A hang mentése a lejátszási listára gomb megjelenik + Trend gomb elrejtése + A Trend gomb el van rejtve. + A Trend gomb látható. + Vásárlás gomb elrejtése + A vásárlás gomb el van rejtve + A vásárlás gomb látható. Infó panel elrejtése Az infó panel rejtett Az infó panel megjelenik @@ -999,6 +1059,31 @@ Korlát: A hivatalos fejlécek a keresési eredményekben el lesznek rejtve."Teljes videólink címke elrejtése A teljes videólink címke el van rejtve A teljes videólink címke megjelenik + + Hang mentése a lejátszási listára gomb elrejtése + A hang mentése a lejátszási listára gomb el van rejtve + A hang mentése a lejátszási listára gomb megjelenik + Vásárlás gomb elrejtése + A vásárlás gomb el van rejtve + A vásárlás gomb látható + Szuper köszönet gomb elrejtése + A Szuper köszönet gomb el van rejtve. + A Szuper köszönet gomb látható. + Ennek a zenének a használata gomb elrejtése + Az ennek a zenének a használata gomb el van rejtve. + Az ennek a zenének a használata gomb látható. + Sablon használata gomb elrejtése + A sablon használata gomb el van rejtve. + A sablon használata gomb látható. + Hely gomb elrejtése + A hely gomb el van rejtve. + A hely gomb látható. + Keresési javaslatok gomb elrejtése + A keresési javaslatok gomb el van rejtve. + A keresési javaslatok gomb látható. + Címkézett termékek elrejtése + A címkézett termékek el vannak rejtve + A címkézett termékek megjelennek Akció gombok Tetszik gomb elrejtése @@ -1048,6 +1133,9 @@ Ismert hiba: Mivel ez a funkció a Google fejlesztési szakaszában van, előfor Navigációs sáv elrejtése A navigációs sáv el van rejtve A navigációs sáv megjelenik + Az üres hely magasságának százaléka + Beállítja a navigációs sáv elrejtésekor megmaradó üres terület magasságának arányát 0 és 100 (%) között. + A magasság százalékának 0-100 (%) között kell lennie. Cserélje ki a csatorna kezelőt A csatorna név használatban van. A csatorna kezelő használatban van. @@ -1130,6 +1218,11 @@ Megjegyzés: Ezzel a képernyőterület méretét is megváltoztatja, ahol érz Régi videóminőség menü visszaállítása A régi videóminőség menü jelenik meg A régi videóminőség menü nem jelenik meg + Lejátszási sebesség zenéhez kiválasztás elrejtése + "A zene alapértelmezett lejátszási sebessége le van tiltva. + +Korlátozás: Előfordulhat, hogy ez a beállítás nem vonatkozik azokra a videókra, amelyek nem tartalmazzák a „Hallgassa meg a YouTube Musicon” bannert." + Az alapértelmezett lejátszási sebesség engedélyezett zene lejátszásnál. A Shortok alapértelmezett lejátszási sebességének engedélyezése Az alapértelmezett lejátszási sebesség vonatkozik a Shortokra. Az alapértelmezett lejátszási sebesség nem vonatkozik a Shortokra. @@ -1146,6 +1239,13 @@ Info: A felugró értesítés nem látható. Eszközméret hamisítása "Meghamisítja az eszköz méreteit annak érdekében, hogy feloldjon olyan jobb videóminőséget, amely esetleg nem érhető el az eszközön." + VP9 kodek letiltása + "A VP9 kodek le van tiltva. + +• A maximális felbontás 1080p. +• A videolejátszás több internetes adatot használ, mint a VP9. +• A HDR lejátszáshoz a HDR videó továbbra is a VP9 kodeket használja." + VP9 kodek engedélyezve van. Szoftver AV1 kodek cseréje A szoftver AV1 kodeket a VP9 kodekkel helyettesíti. Elutasítja a szoftver AV1 kodek választ @@ -1356,6 +1456,7 @@ Egy másik kodek kerül alkalmazásra kb. 20 másodperc pufferezés után."A felhasználónév sikeresen módosítva Az ön hírneve: <b>%.2f</b> <b>%s</b> szegmenst készítettél + Koppintson ide a szegmensek megtekintéséhez. SponsorBlock ranglista <b>%s</b> szegmenstől mentettél meg másokat Koppintson ide a globális statisztikák és a kiemelt közreműködők megtekintéséhez @@ -1426,6 +1527,34 @@ Egy másik kodek kerül alkalmazásra kb. 20 másodperc pufferezés után."Visszaállítás A beállítások a vágólapra másolva. + Adatfolyam meghamisítása + Az adatfolyam meghamisítása, hogy elkerülje a lejátszási problémákat. + Adatfolyam meghamisítása + Az adatfolyam hamisított. + "Az adatfolyam nincs meghamisítva. Lehet, hogy a videó lejátszás nem működik." + A beállítás kikapcsolása videólejátszási problémákat okozhat. + Alapértelmezett kliens + iOS + Android + Android beágyazott lejátszó + Android tesztcsomag + Android TV + Android VR + TV HTML5 + Web + Hamisítás mellékhatásai + "• Előfordulhat, hogy a filmeket vagy a fizetős videókat nem lehet lejátszani. +• Az élő közvetítések az elejétől kezdődnek." + • Az audiosáv menü hiányzik. + "• Az audiosáv menü hiányzik." + • A videó esetleg nem játszódik le. + Kényszerített iOS AVC (H.264) + Az iOS videokodek AVC (H.264). + Az iOS videokodek AVC (H.264), VP9 vagy AV1. + "Ennek engedélyezése javíthatja az akkumulátor élettartamát, és kijavíthatja a lejátszás akadozását.\n\nAz AVC (H.264) maximális felbontása 1080p, és a videolejátszás több internetadatot használ, mint a VP9 vagy az AV1." + Megjelenítés a statisztikában kockáknak + Az adatfolyam lekérésére használt kliens a statisztikában kockáknak látható. + Az adatfolyam lekérésére használt kliens a statisztikában kockáknak nem látható. Megtekintési előzmények A megtekintési előzményekhez kapcsolódó beállítások módosítása. @@ -1437,6 +1566,9 @@ Egy másik kodek kerül alkalmazásra kb. 20 másodperc pufferezés után."Megtekintési előzmények tiltása Megtekintési előzmények állapota • A megtekintési előzmények le vannak tiltva. + • Követi a Google-fiók megtekintési előzményeinek beállításait. + "• Követi a Google-fiók megtekintési előzményeinek beállításait. +• Előfordulhat, hogy a megtekintési előzmények nem működik a DNS vagy a VPN miatt." Patch információ @@ -1454,6 +1586,7 @@ Egy másik kodek kerül alkalmazásra kb. 20 másodperc pufferezés után."Revancify Kék Revancify Piros YouTube + YouTube (minimalista fejléc) Alap Kizárva Befoglalt diff --git a/src/main/resources/youtube/translations/it-rIT/strings.xml b/src/main/resources/youtube/translations/it-rIT/strings.xml index d4299c79dc..2d8928d616 100644 --- a/src/main/resources/youtube/translations/it-rIT/strings.xml +++ b/src/main/resources/youtube/translations/it-rIT/strings.xml @@ -6,6 +6,7 @@ ReVanced Extended Cerca %s + Ripristina valori predefiniti. Opzioni sperimentali Desideri procedere? Riavvia per caricare l\'interfaccia normalmente @@ -23,13 +24,6 @@ Si prega di scaricare %2$s dal sito web." Nascondi gli annunci a schermo intero Gli annunci a schermo intero sono nascosti. Gli annunci a schermo intero sono visibili. - Chiudi gli annunci a schermo intero - Gli annunci a schermo intero sono chiusi tramite il pulsante Chiudi. - "Gli annunci a schermo intero sono bloccati. - -Limitazione: Le immagini dei post della community a schermo intero potrebbero essere bloccate." - Gli annunci a schermo intero sono stati bloccati. (DialogType: %s) - Gli annunci a schermo intero sono stati chiusi. Nascondi gli annunci generali Gli annunci generali sono nascosti. Gli annunci generali sono visibili. @@ -79,6 +73,7 @@ Tocca qui per saperne di più su DeArrow." Notifica toast nascosta DeArrow non è disponibile. Endpoint API di DeArrow L\'URL dell\'endpoint della cache delle anteprime DeArrow. + Url API DeArrow Non Valido. Catture statiche del video Le catture statiche sono prese dall\'inizio, da metà o alla fine di ogni video. Queste immagini sono integrate in YouTube e non viene usata nessuna API esterna. Attiva catture statiche veloci @@ -100,6 +95,7 @@ Tocca qui per saperne di più su DeArrow." Nascondi le schede degli album Le schede degli album sono nascoste. Le schede degli album sono visibili. + Nascondi sezione a carosello "Nasconde le seguenti sezioni: • Ultime notizie • Continua a guardare @@ -107,13 +103,15 @@ Tocca qui per saperne di più su DeArrow." • Ascolta di nuovo • Shopping • Guarda di nuovo" - Nascondi sezione a carosello Nascondi la sezione dei chip La sezione dei chip è nascosta. La sezione dei chip è visibile. Nascondi il chip espandibile sotto i video Il chip espandibile è nascosto. Il chip espandibile è visibile. + Nascondi scaffali espandibili + Gli scaffali espandibili sono nascosti. + Gli scaffali espandibili sono mostrati. Nascondi il pulsante dei sottotitoli nel feed Il pulsante Sottotitoli è nascosto. Il pulsante Sottotitoli è mostrato. @@ -236,7 +234,7 @@ Limitazioni: - Alcuni componenti della interfaccia utente potrebbero non essere nascosti. - La ricerca di una parola chiave potrebbe non mostrare alcun risultato." Abbina parole intere - Circondare una parola chiave/frase con doppie virgolette impedirà partite parziali di titoli video e nomi di canale.<br><br>Ad esempio,<br><b>\"ai\"</b> nasconderà il video: <b>Come funziona l\'intelligenza artificiale?</b><br>ma non si nasconde: <b>Cosa significa un uso equo?</b> + Circondando una parola/frase chiave con doppi apici si impedirà corrispondenze parziali di titoli di video e nomi di canali.<br><br>Ad esempio,<br><b>\"ia\"</b> nasconderà il video: <b>Come funziona la IA?</b><br>ma non nasconderà: <b>Cosa significa imparzialità?</b> Parola chiave non valida. Non è possibile usare: \'%s\' come filtro Aggiungi le virgolette per usare parola chiave: %s. La parola chiave ha dichiarazioni in conflitto: %s. @@ -251,18 +249,21 @@ Limitazioni: • Video con frasi come 'Persone guardano anche' sotto." Nascondi video con poche visualizzazioni Nascondi video con meno di 1.000 visualizzazioni dalla home che sono stati caricati dai canali a cui non si è iscritti. - - Filtro sulla durata - Nasconde i video in base alla loro durata - Nasconde i video con più breve o più lunga durata.\n\nProblema noto: Non nasconderà i video nella sezione di video correlati, nella pagina di riproduzione dei video; invece ne nasconderà solo il timestamp. - Più lungo della durata - I video con durata superiore a questo numero saranno nascosti. - Più breve della durata - I video con durata inferiore a questo numero saranno nascosti. + Nascondi video PROSSIMAMENTE + "Nascondi video con l'etichetta PROSSIMAMENTE. + +Nota: Abilitare questo nasconde anche il pulsante Notificarmi." Filtro sul numero di visualizzazioni - Nascondi i video consigliati per il numero di visualizzazioni - Nascondi i video consigliati con meno di un numero specifico di visualizzazioni.\n\nProblema noto: I video con 0 visualizzazioni non vengono filtrati. + Nascondi video home per visualizzazioni + I video della scheda Home feed sono filtrati. + I video della scheda Home feed non sono filtrati. + Nascondi i risultati della ricerca da visualizzazioni + I risultati della ricerca sono filtrati. + I risultati di ricerca non sono filtrati. + Nascondi i video della scheda iscrizioni per visualizzazioni + I video della scheda iscrizioni feed sono filtrati. + I video della scheda iscrizioni feed non sono filtrati. Maggiore delle visualizzazioni I video con più visualizzazioni di questo numero saranno nascosti. Meno delle visualizzazioni @@ -270,6 +271,18 @@ Limitazioni: Visualizza chiavi Specifica il tuo modello linguistico per il numero di visualizzazioni mostrate sotto ogni video nell\'interfaccia utente. Ogni chiave (una lettera/parola nella tua lingua) -> valore (significato della chiave) deve essere su una nuova riga. Le chiavi vanno prima del segno \"->\". Se cambi la lingua dell\'app o di sistema, devi reimpostare questa impostazione.\n\nEsempi:\nInglese: 10K views = K -> 1000, views -> visualizzazioni\nSpagnolo: 10 K vistas = K -> 1000, vistas -> visualizzazioni K -> 1 000\nM -> 1 000 000\nB -> 1 000 000 000\nvisualizzazioni -> visualizzazioni + Informazioni sul filtro del conteggio delle visualizzazioni + "Home / iscrizione / I risultati della ricerca sono filtrati per nascondere i video con visualizzazioni inferiori o superiori a un numero specificato. + +Limitazioni: +• Gli short non possono essere nascosti. +• I video con 0 visualizzazioni non sono filtrati." + Nascondi video correlati + I video correlati sono nascosti. + I video correlati sono mostrati. + "Questa impostazione limita il numero massimo di layout che possono essere caricati sulla schermata del player. + +Se il layout della schermata del player cambia a causa di modifiche lato server, i layout non intenzionali possono essere nascosti sullo schermo del giocatore." Generale Cambia la scheda iniziale @@ -289,7 +302,11 @@ Limitazioni: Iscrizioni Tendenze Guarda più tardi - Scheda iniziale non valida, ripristinata al valore predefinito. + Cambia il tipo di pagina iniziale + "La pagina iniziale cambia sempre. + +Limitazione: Il pulsante indietro sulla barra degli strumenti potrebbe non funzionare." + La pagina iniziale cambia solo una volta. Disabilita le tracce audio automatiche forzate Le tracce audio automatiche forzate sono disattivate. Le tracce audio automatiche forzate sono abilitate. @@ -318,14 +335,6 @@ Questo non aggira la restrizione di età. La accetta solamente in automatico."Camuffa i dpi per usare alcune interfacce da telefono. Attiva l\'interfaccia da tablet Camuffa i dpi per usare alcune interfacce da tablet. - Sovrascrivi il pulsante per scaricare i video - Il pulsante nativo per scaricare il video apre il tuo scaricatore esterno. - Il pulsante nativo per scaricare il video apre lo scaricatore nativo. - Sovrascrivi il pulsante per scaricare le playlist - Il pulsante nativo di download della playlist è sempre visualizzato, e nelle playlist pubbliche, apre il tuo scaricatore esterno. - Se visualizzato, il pulsante nativo di download della playlist apre lo scaricatore nativo. - Nome del pacchetto dello scaricatore di playlist - Nome del pacchetto dello scaricatore esterno installato, come YTDLnis. Camuffa la versione dell\'app Versione camuffata Versione non camuffata @@ -365,6 +374,30 @@ Alcuni componenti potrebbero non essere nascosti" Configura i componenti da nascondere separati da nuove righe Filtro personalizzato non valido (%s) + + Pulsanti gancio + Sostituisce l\'azione del clic dei pulsanti in-app. + + Pulsante download + Sovrascrivi il pulsante per scaricare i video + Il pulsante nativo per scaricare il video apre il tuo scaricatore esterno. + Il pulsante nativo per scaricare il video apre lo scaricatore nativo. + Sovrascrivi il pulsante per scaricare le playlist + Il pulsante nativo di download della playlist è sempre visualizzato, e nelle playlist pubbliche, apre il tuo scaricatore esterno. + Se visualizzato, il pulsante nativo di download della playlist apre lo scaricatore nativo. + Nome del pacchetto dello scaricatore di playlist + Nome del pacchetto dello scaricatore esterno installato, come YTDLnis. + + Sovrascrivi il pulsante YouTube Music + Il pulsante YouTube Music apre il RVX Music. + Il pulsante YouTube Music apre l\'app nativa. + Nome pacchetto RVX Music + Nome del pacchetto di RVX Music installato. + RVX Music + Attenzione + %s Non è installato. Per favore installalo. + Prerequisito + YouTube music è necessario per sovrascrivere l\'azione del pulsante. Tocca qui per scaricare YouTube Music. Riproduttore minimizzato Cambia lo stile del riproduttore minimizzato nell\'app. @@ -396,9 +429,9 @@ Alcuni componenti potrebbero non essere nascosti" Opacità della copertura Il valore dell\'opacità è compreso tra 0 e 100, dove 0 è trasparente. L\'opacità della copertura del riproduttore minimizzato deve essere compresa tra 0 e 100. Reimpostati i valori predefiniti. - - Pulsanti di navigazione - Nascondi o mostra i componenti della sezione della barra di navigazione. + + Barra di navigazione + Nascondi o mostra i componenti della sezione della barra di navigazione. Abilita pulsanti di navigazione ristretti La spaziatura tra i pulsanti di navigazione è ristretta. La spaziatura tra i pulsanti di navigazione è normale. @@ -424,14 +457,21 @@ Alcuni componenti potrebbero non essere nascosti" Le etichette della barra di navigazione sono nascoste. Le etichette della barra di navigazione sono visibili. Scambia il pulsante Crea con il pulsante notifiche - "Scambia le posizioni del pulsante Crea con il pulsante notifiche camuffando informazioni del dispositivo. + "Il pulsante Crea è scambiato con il pulsante Notifiche. + +Nota: Abilitando questo nasconde forzatamente anche gli annunci video." + Il pulsante Crea non è scambiato con il pulsante Notifiche. + "Disabilitare questo potrebbe caricare più annunci dal server. + +Inoltre, gli annunci non saranno più bloccati negli Shorts. -• Potrebbe essere necessario riavviare il dispositivo per rendere effettiva la modifica di questa impostazione. -• Disabilitare questa impostazione carica più annunci dal lato server. -• Dovresti disabilitare questa impostazione per rendere visibili gli annunci nei video." +Se questa impostazione non ha effetto, prova a passare alla modalità Incognito." Abilita barra di navigazione traslucida La barra di navigazione è traslucida. La barra di navigazione è opaca. + Nascondi la barra di navigazione + La barra di navigazione è nascosta. + La barra di navigazione è mostrata. Menu Impostazioni Nascondi gli elementi del menu delle impostazioni di YouTube. @@ -461,7 +501,7 @@ In questo caso, per accedere alle impostazioni, utilizzare il seguente percorso: Scheda Tu → Visualizza canale → Menu → Impostazioni" Nascondi il pulsante Trasmetti Il pulsante Trasmetti è nascosto. - Il pulsante Trasmetti è mostrato. + Il pulsante Trasmetti è visibile. Nascondi il pulsante Crea Il pulsante Crea è nascosto. Il pulsante Crea è visibile. @@ -494,11 +534,11 @@ Tocca e tieni premuto per aprire le impostazioni di RVX." I pannelli popup del riproduttore automatico sono abilitati. Disabilita interruttori delle playlist miste L\'interruttore automatico delle playlist miste è disabilitato. - "L'interruttore automatico delle playlist miste è abilitato quando la riproduzione automatica è attivato. + "L'interruttore automatico delle playlist miste è abilitato quando la riproduzione automatica è attivata. -La riproduzione automatica può essere modificato nelle impostazioni di YouTube: -Impostazioni → riproduzione automatica→ riproduzione automatica video successivo" - Abilitando questa funzionalità si disabiliterà l\'interruttore automatico a YouTube Misto quando si riproduce musica mentre la riproduzione automatica è attivata. +La riproduzione automatica può essere modificata nelle impostazioni di YouTube: +Impostazioni → Riproduzione automatica→ Riproduci automatic. video successivo" + L\'attivazione di questa funzione disabilita il passaggio automatico a YouTube Mix durante la riproduzione di musica con la riproduzione automatica attiva. Disattiva la sovrapposizione della velocità quando tieni premuto "Disabilita la funzione '2x>>' tenendo premuto. @@ -553,12 +593,12 @@ Nota: La riproduzione automatica può essere modificata nelle impostazioni di YouTube: Impostazioni → Riproduzione automatica → Riproduzione automatica del video successivo" Il video suggerito della schermata finale è visibile. - Nascondi sovrimpressione dello zoom - La sovrimpressione dello zoom è nascosta. - La sovrimpressione dello zoom è visibile. Salta il conto alla rovescia della riproduzione automatica Se la riproduzione automatica è abilitata, il video successivo verrà riprodotto immediatamente. Se la riproduzione automatica è abilitata, il video successivo verrà riprodotto al termine del conto alla rovescia. + Nascondi sovrimpressione dello zoom + La sovrimpressione dello zoom è nascosta. + La sovrimpressione dello zoom è visibile. Pulsanti azione Nascondi o mostra i pulsanti di azione sotto i video. @@ -598,7 +638,7 @@ Impostazioni → Riproduzione automatica → Riproduzione automatica del video s Modalità Ambient Disabilita la modalità Ambient o bypassa le restrizioni della modalità Ambient. - Bypassa le restrizioni della modalità Ambient + Ignora le restrizioni della modalità Ambient La modalità Ambient in modalità risparmio energetico è attivata La modalità Ambient in modalità risparmio energetico è disattivata Disattiva la modalità Ambient @@ -683,7 +723,7 @@ Impostazioni → Riproduzione automatica → Riproduzione automatica del video s Impostazioni aggiuntive Nascondi il menu Modalità Ambient Il menu Modalità Ambient è nascosto. - Il menu Modalità Ambient è mostrato. + Il menu Modalità Ambient è visibile. Nascondi menu Guida & feedback Il menu Guida & feedback è nascosto. Il menu Guida & feedback è visibile. @@ -701,13 +741,13 @@ Impostazioni → Riproduzione automatica → Riproduzione automatica del video s Il menu Controlli premium è visibile. Nascondi il menu Timer del sonno Il menu del timer del sonno è nascosto. - Il menu del timer del sonno è mostrato. + Il menu del timer del sonno è visibile. Nascondi il menu Volume stabile Il menu Volume stabile è visibile. Il menu Volume stabile è nascosto. Nascondi il menu Statistiche per Nerd Il menu Statistiche per Nerd è nascosto. - Il menu Statistiche per Nerd è mostrato. + Il menu Statistiche per Nerd è visibile. Nascondi il menu Guarda in VR Il menu Guarda in VR è nascosto. Il menu Guarda in VR è mostrato. @@ -1008,30 +1048,6 @@ Effetto collaterale: il tema Cairo viene applicato anche ai punti di notifica."< Nascondi pulsante Shopping Il pulsante shopping è nascosto. Il pulsante shopping è mostrato. - Nascondi il pulsante Negozio - Il pulsante Negozio è nascosto. - Il pulsante negozio è mostrato. - Nascondi il pulsante Super Grazie - Il pulsante Super Grazie è nascosto. - Il pulsante Super Grazie è mostrato. - Nascondi prodotti taggati - Prodotti taggati sono nascosti. - Prodotti taggati sono mostrati. - Nascondi pulsante posizione - Il pulsante posizione è nascosto. - Il pulsante posizione è mostrato. - Nascondi il pulsante Salva suono nella playlist - Il pulsante Salva suono nella playlist è nascosto. - Il pulsante Salva suono nella playlist è mostrato. - Nascondi il pulsante suggerimenti di ricerca - Il pulsante suggerimenti di ricerca è nascosto. - Il pulsante suggerimenti di ricerca è mostrato. - Nascondi Usa questo pulsante sonoro - Usa il pulsante sonoro è nascosto. - Usa il pulsante sonoro è mostrato. - Nascondi il pulsante Usa modello - Il pulsante Usa modello è nascosto. - Il pulsante Usa modello è mostrato. Nascondi i pannelli informativi I pannelli informativi sono nascosti. I pannelli informativi sono mostrati. @@ -1050,6 +1066,35 @@ Effetto collaterale: il tema Cairo viene applicato anche ai punti di notifica."< Nascondi l\'etichetta completa del collegamento video L\'etichetta del collegamento video è nascosta. L\'etichetta del collegamento del video è mostrato. + + Azioni suggerite + Nascondi pulsante schermo verde + Il pulsante dello schermo verde è nascosto. + Il pulsante schermo verde è mostrato. + Nascondi il pulsante Salva suono nella playlist + Il pulsante Salva suono nella playlist è nascosto. + Il pulsante Salva suono nella playlist è mostrato. + Nascondi il pulsante Negozio + Il pulsante Negozio è nascosto. + Il pulsante negozio è mostrato. + Nascondi il pulsante Super Grazie + Il pulsante Super Grazie è nascosto. + Il pulsante Super Grazie è mostrato. + Nascondi Usa questo pulsante sonoro + Usa il pulsante sonoro è nascosto. + Usa il pulsante sonoro è mostrato. + Nascondi il pulsante Usa modello + Il pulsante Usa modello è nascosto. + Il pulsante Usa modello è mostrato. + Nascondi pulsante posizione + Il pulsante posizione è nascosto. + Il pulsante posizione è mostrato. + Nascondi il pulsante suggerimenti di ricerca + Il pulsante suggerimenti di ricerca è nascosto. + Il pulsante suggerimenti di ricerca è mostrato. + Nascondi prodotti taggati + Prodotti taggati sono nascosti. + Prodotti taggati sono mostrati. Pulsanti azione Nascondi il pulsante Mi Piace @@ -1072,6 +1117,9 @@ Effetto collaterale: il tema Cairo viene applicato anche ai punti di notifica."< Il pulsante suono è mostrato. Animazione / Feedback + Disabilita animazione pulsante Mi piace + L\'animazione della fontana è disabilitata sopra il pulsante mi piace. + L\'animazione della fontana è abilitata sopra il pulsante mi piace. Nascondi lo sfondo del pulsante Riproduci & Pausa Lo sfondo del pulsante è nascosto. Lo sfondo del pulsante è mostrato. @@ -1099,6 +1147,9 @@ Problema noto: Poiché questa è una caratteristica nella fase di sviluppo di Go Nascondi la barra di navigazione La barra di navigazione è nascosta. La barra di navigazione è mostrata. + Altezza percentuale di spazio vuoto + Configura la percentuale di altezza dello spazio vuoto rimasto quando la barra di navigazione è nascosta, tra 0 e 100 (%). + La percentuale di altezza deve essere compresa tra 0 e 100 (%). Sostituisci manico del canale Il nome del canale è usato. Il manico del canale è usato. @@ -1201,6 +1252,13 @@ Note: La notifica toast è nascosta Simula le dimensioni del dispositivo "Simula le dimensioni del dispositivo per sbloccare qualità video superiori che potrebbero non essere disponibili sul tuo dispositivo" + Disabilita codec VP9 + "Il codec VP9 è disabilitato. + +• La risoluzione massima è 1080p. +• La riproduzione video utilizzerà più dati internet di VP9. +• Per ottenere la riproduzione HDR, il video HDR utilizza ancora il codec VP9." + Codec VP9 è abilitato. Sostituire il codec AV1 software Sostituisce il codec software AV1 con il codec VP9. Rifiuta la risposta di codec AV1 software @@ -1413,6 +1471,7 @@ Limitazione: Non mi piace possono non apparire se l'utente non è registrato o i Nome utente cambiato con successo. La tua reputazione è <b>%.2f</b> Hai creato <b>%s</b> segmenti + Tocca qui per vedere i tuoi segmenti. Classifica di SponsorBlock Hai salvato le persone da <b>%s</b> segmenti Tocca qui per vedere le statistiche globali e i migliori contributori. @@ -1505,9 +1564,9 @@ Tocca il pulsante continua e disabilita le ottimizzazioni della batteria."TV HTML5 Web Effetti collaterali del camuffamento - • I film o i video a pagamento potrebbero non essere riprodotti. + "• I film o i video a pagamento potrebbero non essere riprodotti." • il menu traccia audio è mancante. - • il menu traccia audio è mancante. + "• il menu traccia audio è mancante." • Il video potrebbe non essere riprodotto. Forza iOS AVC (H.264) il codec video iOS è AVC (H.264). @@ -1549,6 +1608,7 @@ AVC (H. 264) ha una risoluzione massima di 1080p e la riproduzione video utilizz Revancify Blu Revancify Rosso YouTube + YouTube (Intestazione minima) Inventario Escluso Incluso diff --git a/src/main/resources/youtube/translations/ja-rJP/strings.xml b/src/main/resources/youtube/translations/ja-rJP/strings.xml index 9fd0a28a62..dc572aeb8d 100644 --- a/src/main/resources/youtube/translations/ja-rJP/strings.xml +++ b/src/main/resources/youtube/translations/ja-rJP/strings.xml @@ -6,6 +6,7 @@ ReVanced Extended 設定を検索 + デフォルト値にリセットしました。 実験的な機能 続行しますか? 再起動してレイアウトを正常に読み込みます @@ -23,13 +24,6 @@ 全画面広告を非表示 アプリ起動時に表示される全画面広告を非表示にします。 アプリ起動時に表示される全画面広告を非表示にします。 - 全画面広告を非表示にする方法を変更 - 現在の設定: 全画面広告の右上の「‪✕‬」ボタンを自動的に押します。 - "現在の設定: 全ての全画面広告を非表示にします。 - -注意: 全画面のコミュニティ投稿画像も非表示になるという副作用があります。" - 全画面広告をブロックしました。(ダイアログの種類: %s) - 全画面広告を閉じました。 全般的な広告を非表示 動画以外の広告を非表示にします。 動画以外の広告を非表示にします。 @@ -77,6 +71,7 @@ DeArrowの詳細については、ここをタップしてください。"DeArrow が利用できない場合、トーストを表示します。 DeArrow API エンドポイント DeArrowサムネイルキャッシュエンドポイントのURL + 無効な DeArrow API の URL です。 静止画サムネイルについて 静止画サムネイルは、各動画の最初/中間/最後の部分から取得されます。これらの画像は YouTube に直接組み込まれており、外部 API は使用されません。 高速な静止画サムネイル @@ -98,6 +93,7 @@ DeArrowの詳細については、ここをタップしてください。"アルバムカードを非表示 アーティストの概要欄の下部に表示されるアルバムカードを非表示にします。 アーティストの概要欄の下部に表示されるアルバムカードを非表示にします。 + おすすめ欄を非表示 "以下の欄を非表示にします: • ニュース速報 • 続きを見る @@ -105,13 +101,15 @@ DeArrowの詳細については、ここをタップしてください。" - おすすめ欄を非表示 チップ欄を非表示 フィードに表示される、似ている動画のチップ欄を非表示にします。 フィードに表示される、似ている動画のチップ欄を非表示にします。 展開可能なチップを非表示 検索結果の動画の下に表示される、展開可能なチップを非表示にします。 検索結果の動画の下に表示される、展開可能なチップを非表示にします。 + 展開可能な棚を非表示 + 検索結果に表示される、展開可能な棚を非表示にします。 + 検索結果に表示される、展開可能な棚を非表示にします。 字幕ボタンを非表示 フィードで字幕ボタンを非表示にします。 フィードで字幕ボタンを非表示にします。 @@ -246,21 +244,24 @@ DeArrowの詳細については、ここをタップしてください。" +• 登録していないチャンネルからアップロードされた、再生回数が 1,000 回未満の動画" 再生回数が少ない動画を非表示 登録していないチャンネルからアップロードされた、再生回数が 1,000 回未満の動画をホームフィードから非表示にします。 - - 再生時間フィルター - 動画を再生時間でフィルタリング - 設定した再生時間で動画を非表示にします。\n\n注意: 動画はプレーヤー関連の動画に表示されず、タイムスタンプを非表示にします。 - 再生時間が長い動画を非表示 - この数値より長い再生時間の動画を非表示にします。 - 再生時間が短い動画を非表示 - この数値より短い再生時間の動画を非表示にします。 + プレミア公開動画を非表示 + "「プレミア公開」ラベルがついた動画を非表示にします。 + +注意: これを有効にすると「通知する」ボタンも非表示になります。" 再生回数フィルター - おすすめ動画を再生回数でフィルタリング - 設定した再生回数未満のおすすめ動画を非表示にします。 + ホームフィードをフィルタリング + ホームフィード内の動画を再生回数でフィルタリングします。 + ホームフィード内の動画を再生回数でフィルタリングします。 + 検索結果をフィルタリング + 検索結果を再生回数でフィルタリングします。 + 検索結果を再生回数でフィルタリングします。 + 登録チャンネルをフィルタリング + 登録チャンネルの動画を再生回数でフィルタリングします。 + 登録チャンネルの動画を再生回数でフィルタリングします。 再生回数が多い動画を非表示 この数値より多い再生回数の動画を非表示にします。 再生回数が少ない動画を非表示 @@ -268,6 +269,18 @@ DeArrowの詳細については、ここをタップしてください。"キーを表示 UIの各動画の下に表示される再生回数の言語テンプレートを設定します。各キー (言語の文字/単語) -> 値 (キーの意味) は、改行して記述する必要があります。キーは「->」記号の前に記述します。言語設定を更新した場合は、この設定をリセットする必要があります。\n\n例:\n英語: 10K views = K -> 1000、views -> 回\nスペイン語: 10 K vistas = K -> 1000、vistas -> 回 万 -> 10 000\n億 -> 100 000 000\n回視聴 -> views + 再生回数のフィルタリングについて + "ホーム / 登録チャンネル / 検索結果はフィルタリングされ、設定した値よりも少ない視聴回数の動画を非表示にします。 + +注意: +• ショート動画は非表示にできません。 +• 再生回数が 0 の動画はフィルタリングされません。" + 関連動画を非表示 + ホームフィードから関連動画を非表示にします。 + ホームフィードから関連動画を非表示にします。 + "これを有効にすると、プレーヤー画面に読み込まれるレイアウトの最大数を制限します。 + +注意: サーバー側の変更によりプレーヤー画面のレイアウトが変更された場合、意図しないレイアウトがプレーヤー画面上から非表示になる可能性があります。" 全般 起動時のページを変更 @@ -287,14 +300,18 @@ DeArrowの詳細については、ここをタップしてください。"登録チャンネル トレンド 後で見る - 無効なスタートページです。デフォルトにリセットします。 + 「起動時のページを変更」の種類 + "現在の設定: 起動時のページを常に変更します。 + +注意: ツールバーの戻るボタンが機能しない場合があります。" + 現在の設定: 起動時のページを一度のみ変更します。 音声トラックの強制を無効化 音声トラックが自動で選択されるのを無効化します。 注意: この設定はショートには適用されません。 音声トラックが自動で選択されるのを無効化します。 -注意: この設定はショートには適用されません。 +注意: この設定はショート動画には適用されません。 字幕の強制を無効化 字幕の自動的な強制を無効化します。 字幕の自動的な強制を無効化します。 @@ -320,14 +337,6 @@ DeArrowの詳細については、ここをタップしてください。"スマートフォン用レイアウトの一部を使用できるように、dpi を偽装します。 タブレット用のレイアウトを有効化 タブレット用レイアウトの一部を使用できるように、dpi を偽装します。 - 「オフライン」ボタンを置き換え - 「オフライン」ボタンで外部ダウンローダーを開きます。 - 「オフライン」ボタンで外部ダウンローダーを開きます。 - プレイリストにダウンロードボタンを追加 - 「ダウンロード」ボタンで外部ダウンローダーを開きます。 - 「ダウンロード」ボタンで外部ダウンローダーを開きます。 - プレイリストの外部ダウンローダーのパッケージ名 - NewPipe や YTDLnis などの、インストールされている外部ダウンローダーアプリのパッケージ名です。 アプリのバージョンを偽装 アプリのバージョンは偽装されています。 アプリのバージョンは偽装されていません。 @@ -367,6 +376,30 @@ DeArrowの詳細については、ここをタップしてください。" フィルタリングするコンポーネントパスビルダーの文字列を並べたリスト。(改行区切り) 無効なカスタムフィルターです: %s。 + + ボタンをフック + YouTube 内の「YouTube Music」ボタンを置換します。 + + ダウンロードボタン + 「オフライン」ボタンを置換 + 「オフライン」ボタンで外部ダウンローダーを開きます。 + 「オフライン」ボタンで外部ダウンローダーを開きます。 + プレイリストにダウンロードボタンを追加 + 「ダウンロード」ボタンで外部ダウンローダーを開きます。 + 「ダウンロード」ボタンで外部ダウンローダーを開きます。 + プレイリストの外部ダウンローダーのパッケージ名 + NewPipe や YTDLnis などの、インストールされている外部ダウンローダーアプリのパッケージ名です。 + + YouTube Music ボタンを置換 + 「YouTube Music」ボタンで RVX Music を開けるようにします。 + 「YouTube Music」ボタンで RVX Music を開けるようにします。 + RVX Music のパッケージ名 + インストールされている RVX Music のパッケージ名です。 + RVX Music + 注意 + %s はインストールされていません。インストールしてください。 + 前提条件 + ボタンを置換するには YouTube Music が必要です。ここをタップして YouTube Music をダウンロードします。 ミニプレーヤー ミニプレーヤーのスタイルを変更します。 @@ -398,9 +431,9 @@ DeArrowの詳細については、ここをタップしてください。"オーバーレイの不透明度 不透明度の値は 0 ~ 100 の間で、 0 が透明です。 ミニプレーヤーのオーバーレイの不透明度は0 ~ 100の間でなければなりません。デフォルト値にリセットします。 - - ナビゲーションボタン - ナビゲーションバーセクションのコンポーネントを非表示または表示します。 + + ナビゲーションバー + ナビゲーションバーセクションのコンポーネントを非表示または表示します。 幅の狭いナビゲーションボタンを有効化 ナビゲーション ボタンの間隔は狭くなります。 ナビゲーション ボタンの間隔を狭くします。 @@ -426,13 +459,21 @@ DeArrowの詳細については、ここをタップしてください。"ナビゲーションバーのラベルを非表示にします。 ナビゲーションバーのラベルを非表示にします。 「作成」を「通知」と入れ替え - "デバイスの情報を偽装して、作成ボタンと通知ボタンの位置を入れ替えます。 -• この設定を変更しても、デバイスを再起動するまで有効にならない場合があります。 -• この設定を無効にすると、サーバーからさらに多くの広告が読み込まれます。 -• 動画広告を表示するには、この設定を無効にする必要があります。" + "「作成」ボタンと「通知」ボタンを入れ替えます。 + +注意: これを有効にすると、動画広告が強制的に非表示になります。" + 「作成」ボタンと「通知」ボタンを入れ替えます。\n\n注意: これを有効にすると、動画広告が強制的に非表示になります。 + "これを無効にすると、サーバーからさらに多くの広告が読み込まれる可能性があります。 + +また、ショートで広告がブロックされなくなります。 + +この設定が有効にならない場合は、シークレットモードに切り替えてみてください。" 半透明のナビゲーションバーを有効化 ナビゲーションバー(ホーム、登録チャンネルなどのボタン)を半透明にします。\n\n注意: この機能は Android 12 以降のみ利用可能です。 ナビゲーションバー(ホーム、登録チャンネルなどのボタン)を半透明にします。\n\n注意: この機能は Android 12 以降のみ利用可能です。 + ナビゲーションバーを非表示 + ナビゲーションバー(ホーム、登録チャンネルなどのボタン)を非表示にします。 + ナビゲーションバー(ホーム、登録チャンネルなどのボタン)を非表示にします。 設定メニュー 「YouTube 設定」メニューの要素を非表示にします。 @@ -478,7 +519,7 @@ DeArrowの詳細については、ここをタップしてください。"音声検索ボタンを非表示 「音声検索」ボタンを非表示にします。 「音声検索」ボタンを非表示にします。 - 作成ボタンを置き換え + 「作成」ボタンを置換 「作成」ボタンを「設定」ボタンに置き換えます。 ボタンに割り当てるアクションの種類 "タップすると RVX 設定が開きます。 @@ -548,12 +589,12 @@ DeArrowの詳細については、ここをタップしてください。"おすすめされる動画の終了画面を非表示 "自動再生がオフの場合、おすすめの動画は動画の終了画面で表示されません。\n\n自動再生は YouTube の設定で変更できます: 「設定 → 自動再生 → 次の動画を自動再生」" 自動再生がオフの場合、おすすめの動画は動画の終了画面で表示されません。\n\n自動再生は YouTube の設定で変更できます: 「設定 → 自動再生 → 次の動画を自動再生」 - ズームオーバーレイを非表示 - ズーム時のオーバーレイを非表示にします。 - ズーム時のオーバーレイを非表示にします。 自動再生カウントダウンをスキップ 自動再生がオンの場合、次の動画をカウントダウンなしで再生します。 自動再生がオンの場合、次の動画をカウントダウンなしで再生します。 + ズームオーバーレイを非表示 + ズーム時のオーバーレイを非表示にします。 + ズーム時のオーバーレイを非表示にします。 アクションボタン 動画下のアクションボタンを非表示または表示します。 @@ -869,7 +910,7 @@ DeArrowの詳細については、ここをタップしてください。"表示する情報の種類 現在の設定: 画質を表示します。 現在の設定: 再生速度を表示します。 - タイムスタンプを置き換え + タイムスタンプを置換 現在の設定: プレーヤー左下のタイムスタンプをタップすると、再生速度または画質のフライアウトメニューが開きます。 現在の設定: プレーヤー左下のタイムスタンプをタップすると、残り時間が表示されます。 シークバーの色のカスタマイズを有効化 @@ -999,30 +1040,6 @@ DeArrowの詳細については、ここをタップしてください。"「購入」ボタンを非表示 ショートで左下に表示される「購入」ボタンを非表示にします。 ショートで左下に表示される「購入」ボタンを非表示にします。 - 「ショップ」ボタンを非表示 - 「ショップ」ボタンを非表示にします。 - 「ショップ」ボタンを非表示にします。 - 「Super Thanks」ボタンを非表示 - 「Super Thanks」ボタンを非表示にします。 - 「Super Thanks」ボタンを非表示にします。 - タグ付き商品を非表示 - タグ付けされている商品を非表示にします。 - タグ付けされている商品を非表示にします。 - 位置情報のボタンを非表示 - 位置情報のボタンを非表示にします。 - 位置情報のボタンを非表示にします。 - 「保存」ボタンを非表示 - 音楽の「保存」ボタンを非表示にします。 - 音楽の「保存」ボタンを非表示にします。 - 検索候補のボタンを非表示 - 検索候補のボタンを非表示にします。 - 検索候補のボタンを非表示にします。 - 「このサウンドを使用する」を非表示 - ショートで楽曲ボタンを押した際に表示される「このサウンドを使用する」ボタンを非表示にします。 - ショートで楽曲ボタンを押した際に表示される「このサウンドを使用する」ボタンを非表示にします。 - 「テンプレートを使用する」を非表示 - 「テンプレートを使用する」ボタンを非表示にします。 - 「テンプレートを使用する」ボタンを非表示にします。 情報パネルを非表示 情報パネルを非表示にします。 情報パネルを非表示にします。 @@ -1041,9 +1058,38 @@ DeArrowの詳細については、ここをタップしてください。"フルの動画のリンクラベルを非表示 フルの動画のリンクのラベルを非表示にします。 フルの動画のリンクのラベルを非表示にします。 + + 推奨されるアクション + グリーンスクリーンボタンを非表示 + プレーヤーの下部に表示される「グリーンスクリーン」ボタンを非表示にします。 + プレーヤーの下部に表示される「グリーンスクリーン」ボタンを非表示にします。 + 「保存」ボタンを非表示 + 音楽の「保存」ボタンを非表示にします。 + 音楽の「保存」ボタンを非表示にします。 + 「ショップ」ボタンを非表示 + 「ショップ」ボタンを非表示にします。 + 「ショップ」ボタンを非表示にします。 + 「Super Thanks」ボタンを非表示 + 「Super Thanks」ボタンを非表示にします。 + 「Super Thanks」ボタンを非表示にします。 + 「このサウンドを使用する」を非表示 + ショートで楽曲ボタンを押した際に表示される「このサウンドを使用する」ボタンを非表示にします。 + ショートで楽曲ボタンを押した際に表示される「このサウンドを使用する」ボタンを非表示にします。 + 「テンプレートを使用する」を非表示 + 「テンプレートを使用する」ボタンを非表示にします。 + 「テンプレートを使用する」ボタンを非表示にします。 + 位置情報のボタンを非表示 + 位置情報のボタンを非表示にします。 + 位置情報のボタンを非表示にします。 + 検索候補のボタンを非表示 + 検索候補のボタンを非表示にします。 + 検索候補のボタンを非表示にします。 + タグ付き商品を非表示 + タグ付けされている商品を非表示にします。 + タグ付けされている商品を非表示にします。 プレーヤー下部(共有、クリップなど)のボタン - 「高評価」ボタンを非表示 + 高評価ボタンを非表示 「高評価」ボタンを非表示にします。 「高評価」ボタンを非表示にします。 「低評価」ボタンを非表示 @@ -1062,10 +1108,13 @@ DeArrowの詳細については、ここをタップしてください。"プレーヤー右下に表示される楽曲のボタンを非表示にします。 プレーヤー右下に表示される楽曲のボタンを非表示にします。 - アニメーション/フィードバック + アニメーション / フィードバック + 高評価ボタンのアニメーションを無効化 + 「高評価」ボタンの上部に表示されるアニメーションを無効化します。 + 「高評価」ボタンの上部に表示されるアニメーションを無効化します。 ボタンの背景を非表示 - 再生/一時停止ボタンの背景を非表示にします。 - 再生/一時停止ボタンの背景を非表示にします。 + 再生 / 一時停止ボタンの背景を非表示にします。 + 再生 / 一時停止ボタンの背景を非表示にします。 ダブルタップ時のアニメーション オリジナル いいね! @@ -1093,6 +1142,9 @@ DeArrowの詳細については、ここをタップしてください。"ナビゲーションバーを非表示 ナビゲーションバー(ホーム、登録チャンネルなどのボタン)を非表示にします。 ナビゲーションバー(ホーム、登録チャンネルなどのボタン)を非表示にします。 + スペースの高さを調整 + ナビゲーションバーが非表示になっている際に残るスペースの高さを 0~100 (%) の間で設定します。 + 高さは 0~100 (%) の間でなければなりません。 ショートのチャンネル名を復元 現在の設定: チャンネル名が表示されます。 現在の設定: チャンネルのハンドル名が表示されます。 @@ -1108,8 +1160,8 @@ DeArrowの詳細については、ここをタップしてください。"音量のスワイプコントロールを有効化します。 音量のスワイプコントロールを有効化します。 明るさの保存と復元を有効化 - 全画面表示を終了/開始した際に明るさを保存/復元します。 - 全画面表示を終了/開始した際に明るさを保存/復元します。 + 全画面表示を終了 / 開始した際に明るさを保存/復元します。 + 全画面表示を終了 / 開始した際に明るさを保存/復元します。 「長押ししてスワイプ」ジェスチャーを有効化 全画面表示時に、長押し時のみスワイプジェスチャーを有効化します。 全画面表示時に、長押し時のみスワイプジェスチャーを有効化します。 @@ -1195,7 +1247,16 @@ DeArrowの詳細については、ここをタップしてください。"デバイスの解像度を偽装 "デバイスの解像度を最大値に偽装します。 高画質は、高いデバイスの解像度を必要とする一部の動画でアンロックされる可能性がありますが、すべての動画でアンロックされるわけではありません。" - AV1 コーデックを置き換え + VP9 コーデックを無効化 + "VP9 コーデックを無効化します。 + +注意: +• 最大解像度は 1080p です。 +• 動画を再生する際には VP9 コーデックよりも多くの通信量を消費します。 +• HDR 再生を可能にするために、HDR 動画では引き続き VP9 コーデックが使用されます。" + VP9 コーデックを無効化します。\n\n注意: \n +• 最大解像度は 1080p です。\n• 動画を再生する際には VP9 コーデックよりも多くの通信量を消費します。\n • HDR 再生を可能にするために、HDR 動画では引き続き VP9 コーデックが使用されます。 + AV1 コーデックを置換 AV1 コーデックを VP9 コーデックに置き換えます。 AV1 コーデックの応答を拒否 "AV1 コーデック応答を強制的に拒否します。 @@ -1364,7 +1425,7 @@ DeArrowの詳細については、ここをタップしてください。"今後表示しない SponsorBlock は一時的に利用できません。 SponsorBlock は一時的に利用できません。(ステータス %d) - SponsorBlock は一時的に利用できません。 (API タイムアウト) + SponsorBlock は一時的に利用できません。(API がタイムアウトしました) セグメントを送信できません: %s SponsorBlock は一時的に停止しています。 セグメントを送信できません (ステータス: %1$d %2$s) @@ -1407,6 +1468,7 @@ DeArrowの詳細については、ここをタップしてください。"ユーザー名は正常に変更されました。 あなたの評価は <b>%.2f</b> です。 今までに <b>%s</b> 個のセグメントを作成しました。 + セグメントを表示するにはここをタップしてください。 SponsorBlock リーダーボード 今までに <b>%s</b> 個のセグメントから人々から守りました グローバルの統計およびトップの貢献者を表示するには、ここをタップしてください。 @@ -1499,9 +1561,13 @@ DeArrowの詳細については、ここをタップしてください。"TV HTML5 Web ストリーミングデータを偽装することによる副作用 - • 映画や有料動画は再生できない場合があります。 + "• 映画や有料動画は再生できない場合があります。 +•ライブは最初から再生されます。 +• 動画が 1 秒早く終了する場合があります。 +• Opus オーディオ コーデックは使用できません。" ・「音声トラック」メニューは表示されません。 - ・「音声トラック」メニューは表示されません。 + "・「音声トラック」メニューは表示されません。 +• 「一定音量」は使用できません。" • 動画が再生できない可能性があります。 iOS クライアントで AVC (H.264) を強制 iOS クライアントで AVC コーデック (H.264) を強制します。 @@ -1519,7 +1585,7 @@ DeArrowの詳細については、ここをタップしてください。"タップして YouTube 再生履歴の管理画面を開きます。 再生履歴の種類 オリジナル - ドメインを置き換え + ドメインを置換 再生履歴をブロック 再生履歴のステータス • 再生履歴をブロックします。 @@ -1543,6 +1609,7 @@ DeArrowの詳細については、ここをタップしてください。"Revancify Blue Revancify Red YouTube + YouTube (ロゴのみ) オリジナル 除外されています 適用されています diff --git a/src/main/resources/youtube/translations/ko-rKR/strings.xml b/src/main/resources/youtube/translations/ko-rKR/strings.xml index 636514807c..79c3a1181e 100644 --- a/src/main/resources/youtube/translations/ko-rKR/strings.xml +++ b/src/main/resources/youtube/translations/ko-rKR/strings.xml @@ -6,6 +6,7 @@ ReVanced Extended 설정 %s 검색 + 기본값으로 초기화합니다. 실험적인 기능 계속하시겠습니까? 레이아웃을 정상적으로 불러오기 위해 다시 시작합니다. @@ -23,13 +24,6 @@ 전체 화면 광고 숨기기 전체 화면 광고가 숨겨집니다. 전체 화면 광고가 표시됩니다. - 전체 화면 광고 닫기 - 닫기 버튼을 누르면 전체 화면 광고가 닫혀집니다. - "전체 화면 광고가 차단됩니다. - -알려진 문제점: 전체 화면에서 커뮤니티 게시물 이미지가 차단될 수 있습니다." - 전체 화면 광고가 차단되었습니다. (다이얼로그 타입: %s) - 전체 화면 광고가 닫혔습니다. 일반 레이아웃 광고 숨기기 일반 레이아웃 광고가 숨겨집니다. 일반 레이아웃 광고가 표시됩니다. @@ -54,9 +48,9 @@ 웹 검색 결과 숨기기 웹 검색 결과가 숨겨집니다. 웹 검색 결과가 표시됩니다. - YouTube Premium 광고 숨기기 + YouTube Premium 프로모션 숨기기 YouTube Premium 광고가 숨겨집니다. - YouTube Premium 광고가 표시됩니다. + YouTube Premium 프로모션이 표시됩니다. 대체 썸네일 홈 탭 @@ -79,6 +73,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." DeArrow를 사용할 수 없을 때, 팝업 메시지를 표시하지 않습니다. DeArrow API 엔드포인트 DeArrow 썸네일 캐시 엔드포인트 URL입니다. 이것이 무슨 역할을 하는지 모르는 경우에는 이 URL을 변경하지 마세요. + 잘못된 DeArrow API URL 입니다. 스틸 컷 썸네일에 대한 정보 스틸 컷 썸네일은 각 동영상의 시작 / 중간 / 끝 부분에서 캡쳐된 이미지입니다. 이러한 이미지는 YouTube에 내장되어 있으며 외부 API는 사용되지 않습니다. 일반화질 스틸 컷 썸네일 표시하기 @@ -100,6 +95,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 음악 앨범 카드 숨기기 검색 결과에서 음악 앨범 카드가 숨겨집니다. 검색 결과에서 음악 앨범 카드가 표시됩니다. + 좌우 슬라이드형 선반 숨기기 "다음 선반들이 숨겨집니다: • 다시 듣기 • 다시 시청하기 @@ -110,13 +106,15 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." • 맞춤 실시간 스트림 • 라이브 쇼핑 • 보건 정보 출처 ..." - 좌우 슬라이드형 선반 숨기기 더 많은 주제 탐색 선반 숨기기 더 많은 주제 탐색 선반이 숨겨집니다. 더 많은 주제 탐색 선반이 표시됩니다. - 썸네일 하단에서 동영상 관련 정보 숨기기 - 다음 정보들이 숨겨집니다:\n동영상 설명, 챕터, 주요 순간, 스크립트,\n재생목록의 동영상, 이 동영상에 나온 제품 ... - 다음 정보들이 표시됩니다:\n동영상 설명, 챕터, 주요 순간, 스크립트,\n재생목록의 동영상, 이 동영상에 나온 제품 ... + 펼쳐볼 수 있는 정보 숨기기 + 썸네일 하단에서 다음 정보들이 숨겨집니다:\n동영상 설명, 챕터, 주요 순간, 스크립트,\n재생목록의 동영상, 이 동영상에 나온 제품 ... + 썸네일 하단에서 다음 정보들이 표시됩니다:\n동영상 설명, 챕터, 주요 순간, 스크립트,\n재생목록의 동영상, 이 동영상에 나온 제품 ... + 펼쳐볼 수 있는 선반 숨기기 + 다음 선반이 숨겨집니다:\n좋아하는 장르 선택 선반 + 다음 선반이 표시됩니다:\n좋아하는 장르 선택 선반 피드 자막 버튼 숨기기 자막 버튼이 숨겨집니다. 자막 버튼이 표시됩니다. @@ -142,8 +140,8 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 영화 선반이 숨겨집니다. 영화 선반이 표시됩니다. \'알림 받기\' 버튼 숨기기 - 게시 예정 동영상 하단에서 \'알림 받기\' 버튼이 숨겨집니다. - 게시 예정 동영상 하단에서 \'알림 받기\' 버튼이 표시됩니다. + (게시) 예정 동영상 하단에서 \'알림 받기\' 버튼이 숨겨집니다. + (게시) 예정 동영상 하단에서 \'알림 받기\' 버튼이 표시됩니다. Playables(게임 룸) 선반 숨기기 Playables(게임 룸) 선반이 숨겨집니다.\n• YouTube 앱에 내장된 미니 게임\n• 일부 국가에서는 아직 서비스가 제공되지 않습니다. Playables(게임 룸) 선반이 표시됩니다.\n• YouTube 앱에 내장된 미니 게임\n• 일부 국가에서는 아직 서비스가 제공되지 않습니다. @@ -233,11 +231,11 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." • 필터링 키워드는 채널 이름 또는 동영상 제목에 표시되는 모든 텍스트가 될 수 있습니다. • 가운데 대문자가 있는 단어는 대소문자를 함께 입력해야 합니다 (예: iPhone, TikTok, LeBlanc)." - 키워드 필터링 정보 + 키워드 필터링에 대한 정보 "홈 / 구독 / 검색 결과가 필터링되어 키워드 구문과 일치하는 콘텐츠가 숨겨집니다. 알려진 문제점: -• 채널 이름으로 Shorts는 숨길 수 없습니다. +• 채널 이름으로 Shorts 동영상은 숨길 수 없습니다. • 일부 화면 구성요소는 숨겨지지 않을 수 있습니다. • 필터링 키워드를 검색하면 검색 결과가 표시되지 않을 수 있습니다." 전체 단어 일치시키기 @@ -255,25 +253,41 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." • 썸네일 하단에 '시청자가 이 동영상도 시청함'와 같은 문구가 있는 동영상" 조회수가 낮은 추천 동영상 숨기기 구독하지 않는 채널에서 업로드한 동영상 중 조회수가 1,000회 미만인 동영상이 홈 피드에서 숨겨집니다. - - 재생시간 필터 - 재생시간을 기준으로 동영상 숨기기 - 재생시간이 짧거나 긴 동영상을 숨길 수 있습니다.\n\n알려진 문제점: 관련 동영상에서는 동영상이 숨겨지지 않고, 타임스탬프만 숨겨집니다. - 재생시간이 긴 동영상 숨기기 - 입력한 숫자보다 재생시간이 긴 동영상을 숨길 수 있습니다. - 재생시간이 짧은 동영상 숨기기 - 입력한 숫자보다 재생시간이 짧은 동영상을 숨길 수 있습니다. + (게시) 예정 동영상 숨기기 + "(게시) 예정 라벨이 있는 동영상이 숨겨집니다. + +알림: 이 설정을 활성화하면 '알림 받기' 버튼도 숨겨집니다." 조회수 필터 - 조회수를 기준으로 동영상 숨기기 - 조회수가 높거나 낮은 추천 동영상을 숨길 수 있습니다.\n\n알려진 문제점: 실시간 스트림과 \'조회수 없음\' 동영상은 숨겨지지 않습니다. + 홈 피드에서 조회수 필터 활성화하기 + 홈 피드에서 조회수 필터를 활성화합니다. + 홈 피드에서 조회수 필터를 비활성화합니다. + 검색 결과에서 조회수 필터 활성화하기 + 검색 결과에서 조회수 필터를 활성화합니다. + 검색 결과에서 조회수 필터를 비활성화합니다. + 구독 피드에서 조회수 필터 활성화하기 + 구독 피드에서 조회수 필터를 활성화합니다. + 구독 피드에서 조회수 필터를 비활성화합니다. 조회수가 높은 동영상 숨기기 입력한 숫자보다 높은 조회수를 가진 동영상을 숨길 수 있습니다. 조회수가 낮은 동영상 숨기기 입력한 숫자보다 낮은 조회수를 가진 동영상을 숨길 수 있습니다. 조회수 키 - 레이아웃에서 표시되는 동영상의 조회수에 대한 언어 템플릿을 설정할 수 있습니다. \n• \'키(해당 언어의 문자/단어) -> 값(키의 의미)\'은 줄바꿈으로 구분하여 설정해야 하고, 키는 \'->\' 기호 앞에 와야 합니다. \n• 편집창은 \'하나의 언어에 대한 키만 입력\' & \'앞에는 숫자 관련 키, 마지막에는 조회수 단어 키 순으로 입력\' 해야 합니다. \n• 앱 언어 또는 시스템 언어를 변경하는 경우 이 설정을 다시 설정해야 합니다.\n\n예시) 한국어: 조회수 10만회 = 만회 -> 10000, 조회수 -> views\n영어: 10K views = K -> 1000, views -> views + 레이아웃에서 표시되는 동영상의 조회수에 대한 언어 템플릿을 설정할 수 있습니다. \n• \'키(해당 언어의 문자/단어) -> 값(키의 의미)\'은 줄바꿈으로 구분하여 설정해야 하고, 키는 \'->\' 기호 앞에 와야 합니다. \n• 편집창은 \'하나의 언어에 대한 키만 입력\' & \'앞에는 숫자 관련 키, 마지막에는 조회수 단어 키 순으로 입력\' 해야 합니다. \n• 앱 언어 또는 시스템 언어를 변경하는 경우 이 설정을 다시 설정해야 합니다.\n예) 한국어: 조회수 10만회 = 만회 -> 10000, 조회수 -> views\n영어: 10K views = K -> 1000, views -> views 천회 -> 1000\n만회 -> 10000\n억회 -> 100000000\n조회수 -> views + 조회수 필터링에 대한 정보 + "홈 / 구독 / 검색 결과가 필터링되어 조회수가 지정한 수보다 적거나 많은 동영상이 숨겨집니다. + +알려진 문제점: +• Shorts 동영상은 숨길 수 없습니다. +• 실시간 스트림과 '조회수 없음' 동영상은 숨길 수 없습니다." + 관련 동영상 숨기기 + 관련 동영상이 숨겨집니다. + 관련 동영상이 표시됩니다. + "이 설정은 플레이어 화면에서 로드할 수 있는 최대 레이아웃 수를 제한합니다. + +서버 측 변경으로 인해 플레이어 화면의 레이아웃이 변경되면 플레이어 화면에서 의도하지 않은 레이아웃이 숨겨질 수 있습니다." + 오프셋 일반 앱 시작 페이지 변경하기 @@ -293,7 +307,11 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 구독 인기 급상승 나중에 볼 동영상 - 잘못된 앱 시작 페이지이므로 기본값으로 초기화합니다. + 앱 시작 페이지 타입 변경하기 + "앱 시작 페이지가 항상 변경됩니다. + +알려진 문제점: 툴바에서 '뒤로 가기' 버튼이 작동되지 않을 수 있습니다." + 앱 시작 페이지가 한 번만 변경됩니다. 자동 오디오 트랙 비활성화하기 오디오 트랙 사용이 강제된 동영상에서 오디오 트랙을 비활성화합니다. 오디오 트랙 사용이 강제된 동영상에서 오디오 트랙을 활성화합니다. @@ -316,19 +334,11 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 스낵바(팝업 메시지바)가 숨겨집니다. 스낵바(팝업 메시지바)가 표시됩니다. 시청 경고 다이얼로그 제거하기 - "시청 경고 다이얼로그를 제거합니다.\n\n• 이 설정은 다이얼로그를 자동으로 허용하기만 하며 연령 제한(성인인증 절차)을 우회할 수 없습니다.\n• 성인인증이 필요한 동영상에서 인증을 하려 할 때, 휴대폰 번호가 필요하다고 알려주는 소형 팝업창(다이얼로그) 없이 바로 휴대폰 번호 인증 페이지가 표시됩니다." + "시청 경고 다이얼로그를 제거합니다.\n\n• 이 설정은 다이얼로그를 자동으로 허용하기만 하며 연령 제한(성인인증 절차)을 우회할 수 없습니다.\n• 즉, 성인인증이 필요한 동영상에서 인증을 하려 할 때, 휴대폰 번호가 필요하다고 알려주는 소형 팝업창(다이얼로그) 없이 바로 휴대폰 번호 인증 페이지가 표시됩니다." 휴대폰 레이아웃 활성화하기 dpi를 변경하여 일부 레이아웃을 휴대폰 레이아웃으로 활성화합니다.\n\n• \'태블릿 레이아웃으로 활성화하면 잠겨지는 RVX 설정\'이 태블릿 또는 폴더블 폰에서는 기본적으로 잠겨져 있으므로, 이 설정을 사용하고 싶으면 휴대폰 레이아웃으로 활성화해야 합니다. 태블릿 레이아웃 활성화하기 - dpi를 변경하여 일부 레이아웃을 태블릿 레이아웃으로 활성화합니다.\n\n이 설정을 활성화하면 다음 RVX 설정이 잠겨질 수 있습니다:\n• 최신 동영상 버튼 숨기기\n• 믹스 재생목록 숨기기\n• 커뮤니티 게시물 설정\n• 전체 화면의 일부 설정 (빠른 작업) - 동영상 오프라인 저장 버튼 재정의하기 - 동영상 오프라인 저장 버튼으로 외부 다운로더를 실행할 수 있습니다. - 동영상 오프라인 저장 버튼으로 기본 다운로더를 실행할 수 있습니다. (YouTube Premium 기능) - 재생목록 오프라인 저장 버튼 재정의하기 - 기본 재생목록 오프라인 저장 버튼이 항상 표시되어 있으며, 공개 재생목록에서는 그 버튼으로 외부 다운로더를 실행할 수 있습니다. - 기본 재생목록 오프라인 저장 버튼이 표시되어 있으면, 그 버튼으로 기본 다운로더를 실행할 수 있습니다. (YouTube Premium 기능) - 재생목록 외부 다운로더 앱 패키지명 - YTDLnis와 같은 설치된 외부 다운로더 앱 패키지명을 설정하세요. + dpi를 변경하여 일부 레이아웃을 태블릿 레이아웃으로 활성화합니다.\n\n이 설정을 활성화하면 다음 RVX 설정이 잠겨질 수 있습니다:\n• 커뮤니티 게시물 숨기기\n• 믹스 재생목록 숨기기\n• 최신 동영상 버튼 숨기기\n• 관련 동영상 오버레이 숨기기 앱 버전 변경하기 앱 버전을 변경합니다. 앱 버전을 변경하지 않습니다. @@ -364,6 +374,30 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 필터링할 컴포넌트 패스 빌더 문자열을 줄바꿈으로 구분하여 설정합니다.\n• 컴포넌트 패스 빌더 문자열은 숨겨질 레이아웃 구성요소를 식별하는 기술적인 이름입니다. 잘못된 필터 값입니다: %s + + 버튼 재정의 + \'앱 내에 있는 버튼을 터치 시 실행 동작\'을 재정의할 수 있습니다. + + 오프라인 저장 버튼 + 동영상 오프라인 저장 버튼 재정의하기 + 동영상 오프라인 저장 버튼으로 외부 다운로더를 실행할 수 있습니다. + 동영상 오프라인 저장 버튼으로 기본 다운로더를 실행할 수 있습니다. (YouTube Premium 기능) + 재생목록 오프라인 저장 버튼 재정의하기 + 기본 재생목록 오프라인 저장 버튼이 항상 표시되어 있으며, 공개 재생목록에서는 그 버튼으로 외부 다운로더를 실행할 수 있습니다. + 기본 재생목록 오프라인 저장 버튼이 표시되어 있으면, 그 버튼으로 기본 다운로더를 실행할 수 있습니다. (YouTube Premium 기능) + 재생목록 외부 다운로더 앱 패키지명 + YTDLnis와 같은 설치된 외부 다운로더 앱 패키지명을 설정하세요. + + YouTube Music 버튼 재정의하기 + YouTube Music 버튼으로 RVX Music을 실행할 수 있습니다. + YouTube Music 버튼으로 순정 YouTube Music을 실행할 수 있습니다. + RVX Music 앱 패키지명 + 이 기기에 설치된 RVX Music 앱 패키지명을 설정하세요. + RVX Music + 경고 + %s 이 설치되어 있지 않습니다. 설치하세요. + 전제 조건 + 버튼 동작을 재정의하려면 YouTube Music이 필요합니다. 여기를 눌러서 YouTube Music을 다운로드하세요. 미니 플레이어 앱 내에서 최소화된 플레이어의 스타일을 변경할 수 있습니다. @@ -394,10 +428,10 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." \'되감기\' & \'빨리 감기\' 버튼이 표시됩니다. 미니 플레이어 오버레이 불투명도 불투명도 값은 0-100 사이이며, 0은 투명입니다. - 불투명도 값은 0-100 사이어야 하므로 기본값으로 초기화합니다. - - 하단바 버튼 - 하단바 섹션에서 구성요소를 숨기거나 표시할 수 있습니다. + 미니 플레이어 오버레이 불투명도 값은 0-100 사이어야 합니다. + + 하단바 + 하단바에서 섹션 구성요소를 숨기거나 표시할 수 있습니다. 좁은 하단바 버튼 활성화하기 하단바 버튼 사이의 간격이 좁아집니다. 하단바 버튼 사이의 간격이 좁아지지 않습니다. @@ -423,14 +457,21 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 하단바 버튼 라벨이 숨겨집니다. 하단바 버튼 라벨이 표시됩니다. 만들기 버튼과 알림 버튼 위치 교환하기 - "기기 정보를 변경하여 만들기 버튼과 알림 버튼의 위치를 교환합니다. -• 이 설정을 변경하더라도 기기를 다시 시작할 때까지 적용되지 않을 수 있습니다. -• 이 설정을 비활성화하면 서버에서 광고 필터에 등록되지 않은 광고(Shorts 광고)가 로드됩니다. -• 이 설정을 활성화하면 일부 광고가 강제로 숨겨집니다. (동영상 광고, 일반 레이아웃 광고) -• 광고 설정에 있는 일부 설정들을 비활성화하려면 이 설정도 비활성화해야 합니다." + "만들기 버튼과 알림 버튼의 위치를 교환합니다. + +알림: 이 설정을 활성화하면 동영상 광고도 강제로 숨겨집니다." + 만들기 버튼과 알림 버튼의 위치를 교환하지 않습니다. + "이 설정을 비활성화하면 서버에서 더 많은 광고가 로드될 수 있습니다. + +또한, Shorts에서 광고가 더 이상 숨겨지지 않습니다. + +이 설정이 적용되지 않는 경우에는 시크릿 모드로 전환해 보세요." 반투명 하단바 활성화하기 반투명 하단바를 활성화합니다. 불투명 하단바를 활성화합니다. + 하단바 숨기기 + 하단바가 숨겨집니다. + 하단바가 표시됩니다. 설정 메뉴 YouTube 설정 메뉴에서 구성요소를 숨길 수 있습니다. @@ -487,7 +528,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 플레이어 사용자 정의 플레이어 오버레이 불투명도 불투명도 값은 0-100 사이이며, 0은 투명입니다. - 불투명도 값은 0-100 사이어야 하므로 기본값으로 초기화합니다. + 플레이어 오버레이 불투명도 값은 0-100 사이어야 합니다. 플레이어 팝업 패널 비활성화하기 자동 플레이어 팝업 패널을 비활성화합니다.\n• 재생목록, 실시간 채팅, 제품 패널 ... 자동 플레이어 팝업 패널을 활성화합니다.\n• 재생목록, 실시간 채팅, 제품 패널 ... @@ -497,7 +538,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 자동재생은 YouTube 설정에서 변경할 수 있습니다: 설정 → 자동재생 → 다음 동영상 자동재생" - 이 설정을 활성화하면 자동재생이 켜져 있는 동안에 음악을 재생하면 YouTube 믹스 재생목록으로 자동전환되지 않습니다. + 이 설정을 활성화하면 자동재생이 켜져 있는 동안에 음악 동영상을 재생하면 YouTube 믹스 재생목록으로 자동전환되지 않습니다. 동영상 재생 속도 오버레이 비활성화하기 "화면을 길게 눌러서 '2배속 >>'을 비활성화합니다. @@ -506,7 +547,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." • 이 설정을 비활성화해도 동영상 재생 속도 오버레이가 강제로 활성화되지는 않습니다." 동영상 재생 속도 오버레이 값 동영상 재생 속도 오버레이 값은 0-8.0 사이어야 합니다. - 재생 속도 오버레이 값은 0-8.0 사이어야 하므로 기본값으로 초기화합니다. + 재생 속도 오버레이 값은 0-8.0 사이어야 합니다. 동영상 하단에서 채널 워터마크 숨기기 동영상 하단에서 채널 워터마크가 숨겨집니다. 동영상 하단에서 채널 워터마크가 표시됩니다. @@ -553,12 +594,12 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 자동재생은 YouTube 설정에서 변경할 수 있습니다: 설정 → 자동재생 → 다음 동영상 자동재생" 최종 화면에서 \'다음 재생 추천 동영상\'이 표시됩니다. - 줌 오버레이 숨기기 - 줌 오버레이가 숨겨집니다. - 줌 오버레이가 표시됩니다. 자동재생 카운트다운 건너뛰기 자동재생이 활성화되어 있으면, 카운트다운 없이 다음 동영상을 재생합니다. 자동재생이 활성화되어 있으면, 카운트다운 종료 후에 다음 동영상을 재생합니다 + 줌 오버레이 숨기기 + 줌 오버레이가 숨겨집니다. + 줌 오버레이가 표시됩니다. 액션 버튼 플레이어 하단에 있는 액션 버튼을 숨기거나 표시할 수 있습니다. @@ -764,7 +805,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 공유 버튼이 표시됩니다. 빠른 작업 상단 여백 재생바에서 빠른 작업 컨테이너까지의 간격을 0-32 사이로 지정할 수 있습니다. - 빠른 작업 상단 여백 값은 0-32 사이어야 하므로 기본값으로 초기화합니다. + 빠른 작업 상단 여백 값은 0-32 사이어야 합니다. 전체 화면에서 세로 모드 활성화하기 전체 화면에서 동영상의 방향을 세로 모드로 활성화합니다. @@ -913,8 +954,8 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 동영상 설명 동영상 설명에서 구성요소를 숨기거나 표시할 수 있습니다. 롤링 넘버 애니메이션 비활성화하기 - 다음 롤링 넘버 애니메이션을 비활성화합니다.\n• 조회수, 시청자 수 롤링 애니메이션 (플레이어 하단)\n• 좋아요 개수, 조회수 롤링 애니메이션 (동영상 설명) - 다음 롤링 넘버 애니메이션을 활성화합니다.\n• 조회수, 시청자 수 롤링 애니메이션 (플레이어 하단)\n• 좋아요 개수, 조회수 롤링 애니메이션 (동영상 설명) + 다음 롤링 넘버 애니메이션을 비활성화합니다.\n• 조회수, 시청자 수 롤링 애니메이션 (플레이어 하단)\n• 좋아요 수, 조회수 롤링 애니메이션 (동영상 설명) + 다음 롤링 넘버 애니메이션을 활성화합니다.\n• 조회수, 시청자 수 롤링 애니메이션 (플레이어 하단)\n• 좋아요 수, 조회수 롤링 애니메이션 (동영상 설명) 속성 섹션 숨기기 게임 섹션, 음악 섹션 그리고 동영상 속 장소 섹션이 숨겨집니다. 게임 섹션, 음악 섹션 그리고 동영상 속 장소 섹션이 표시됩니다. @@ -1012,30 +1053,6 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 쇼핑 버튼 숨기기 일시 정지 오버레이에서 쇼핑 버튼이 숨겨집니다. 일시 정지 오버레이에서 쇼핑 버튼이 표시됩니다. - 쇼핑 버튼 숨기기 - 플레이어 하단 정보에서 쇼핑 버튼이 숨겨집니다. - 플레이어 하단 정보에서 쇼핑 버튼이 표시됩니다. - Super Thanks 구매 버튼 숨기기 - Super Thanks 구매 버튼이 숨겨집니다. - Super Thanks 구매 버튼이 표시됩니다. - 태그된 제품 숨기기 - 태그된 제품이 숨겨집니다. - 태그된 제품이 표시됩니다. - 위치 버튼 숨기기 - 위치 버튼이 숨겨집니다. - 위치 버튼이 표시됩니다. - (재생목록에) 음악 저장 버튼 숨기기 - (재생목록에) 음악 저장 버튼이 숨겨집니다. - (재생목록에) 음악 저장 버튼이 표시됩니다. - 검색 추천 버튼 숨기기 - 검색 추천 버튼이 숨겨집니다. - 검색 추천 버튼이 표시됩니다. - \'이 사운드 사용\' 버튼 숨기기 - \'이 사운드 사용\' 버튼이 숨겨집니다. - \'이 사운드 사용\' 버튼이 표시됩니다. - 템플릿 사용 버튼 숨기기 - 템플릿 사용 버튼이 숨겨집니다. - 템플릿 사용 버튼이 표시됩니다. 정보 패널 숨기기 정보 패널이 숨겨집니다. 정보 패널이 표시됩니다. @@ -1054,6 +1071,35 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." FULL 또는 관련 동영상 링크 라벨 숨기기 동영상 링크 라벨이 숨겨집니다. 동영상 링크 라벨이 표시됩니다. + + 추천 동작 + 그린 스크린 버튼 숨기기 + 그린 스크린 버튼이 숨겨집니다. + 그린 스크린 버튼이 표시됩니다. + (재생목록에) 음악 저장 버튼 숨기기 + (재생목록에) 음악 저장 버튼이 숨겨집니다. + (재생목록에) 음악 저장 버튼이 표시됩니다. + 쇼핑 버튼 숨기기 + 플레이어 하단 정보에서 쇼핑 버튼이 숨겨집니다. + 플레이어 하단 정보에서 쇼핑 버튼이 표시됩니다. + Super Thanks 구매 버튼 숨기기 + Super Thanks 구매 버튼이 숨겨집니다. + Super Thanks 구매 버튼이 표시됩니다. + \'이 사운드 사용\' 버튼 숨기기 + \'이 사운드 사용\' 버튼이 숨겨집니다. + \'이 사운드 사용\' 버튼이 표시됩니다. + 템플릿 사용 버튼 숨기기 + 템플릿 사용 버튼이 숨겨집니다. + 템플릿 사용 버튼이 표시됩니다. + 위치 버튼 숨기기 + 위치 버튼이 숨겨집니다. + 위치 버튼이 표시됩니다. + 검색 추천 버튼 숨기기 + 검색 추천 버튼이 숨겨집니다. + 검색 추천 버튼이 표시됩니다. + 태그된 제품 숨기기 + 태그된 제품이 숨겨집니다. + 태그된 제품이 표시됩니다. 액션 버튼 좋아요 버튼 숨기기 @@ -1076,6 +1122,9 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 사운드 버튼이 표시됩니다. 애니메이션 / 피드백 + 좋아요 버튼 애니메이션 비활성화하기 + 좋아요 버튼 위에 있는 애니메이션을 비활성화합니다. + 좋아요 버튼 위에 있는 애니메이션을 활성화합니다. 재생 & 일시 정지 버튼 배경 숨기기 버튼 배경이 숨겨집니다. 버튼 배경이 표시됩니다. @@ -1098,13 +1147,16 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 타임스탬프를 길게 눌러서 Shorts 반복 상태를 변경할 수 있습니다. 메타 패널 하단 여백 재생바에서 메타 패널까지의 간격을 0-64 사이로 지정할 수 있습니다. - 메타 패널 하단 여백은 0-64 사이여야 하므로 기본값으로 초기화합니다. + 메타 패널 하단 여백은 0-64 사이여야 합니다. 툴바 숨기기 툴바가 숨겨집니다. 툴바가 표시됩니다. 하단바 숨기기 하단바가 숨겨집니다. 하단바가 표시됩니다. + 빈 공간의 높이 비율 + 하단바가 숨겨졌을 때, 남는 빈 공간의 높이 비율을 0에서 100사이로 지정할 수 있습니다 (백분율). + 높이 비율은 0-100 사이어야 합니다 (백분율). 채널 핸들 변경하기 채널 이름을 사용합니다. 채널 핸들(@채널 아이디)을 사용합니다. @@ -1139,7 +1191,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 오버레이 텍스트 크기를 지정할 수 있습니다. 스와이프 오버레이 화면 크기 스와이프를 할 수 있는 화면 영역 크기를 지정할 수 있습니다. (백분율)\n\n알림: \'두 번 탭하여 탐색\' 제스처의 화면 영역 크기도 변경됩니다. - 스와이프 영역 크기는 50 보다 클 수 없으므로 기본값으로 초기화합니다. + 스와이프 영역 크기는 50 보다 클 수 없습니다. 오버레이 타임아웃 오버레이가 표시되는 시간을 지정할 수 있습니다. (밀리초) HDR 자동 밝기 비활성화하기 @@ -1207,32 +1259,38 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 기기 크기 정보 변경하기 "기기 크기 정보를 최대값으로 변경합니다. 높은 기기 크기 정보가 필요한 일부 동영상에서는 고화질 동영상 값이 잠금 해제될 수 있지만 모든 동영상에는 적용되지 않습니다." - 소프트웨어 AV1 코덱 변경하기 - 소프트웨어 AV1 코덱을 VP9 코덱으로 변경합니다. - 소프트웨어 AV1 코덱 응답 거부하기 - "소프트웨어 AV1 코덱 응답을 강제로 거부합니다. + VP9 코덱 비활성화하기 + "VP9 코덱을 비활성화합니다. +• 재생 문제가 없는 계정이거나 iOS 클라이언트만 AV1 코덱을 지원하고 나머지 클라이언트는 VP9 코덱까지만 지원하기 때문에 iOS만 4K 동영상까지 재생될 수 있고, 나머지는 1080p까지 재생될 수 있습니다. +• AVC 코덱 동영상을 재생했을 경우에는 VP9보다 더 많은 데이터가 사용됩니다. +• HDR 동영상을 재생하기 위해 HDR 동영상에서는 VP9 또는 AV1 코덱이 사용됩니다." + VP9 코덱을 활성화합니다.\n• 예전에 업로드된 동영상에서 일부 화질 값들이 제거되어 360p와 1080p(Premium 기능)만 선택할 수 있거나 화질 메뉴를 선택할 수 없을 수 있습니다. + AV1 코덱 변경하기 + AV1 코덱을 VP9 코덱으로 변경합니다. + AV1 코덱 응답 거부하기 + "AV1 코덱 응답을 강제로 거부합니다. 약 20초정도의 버퍼링 후에 다른 코덱으로 전환됩니다." Fallback 프로세스로 인해 약 20초정도의 버퍼링이 발생합니다. 기본 동영상 재생 속도 값을 %s으로 변경합니다. 모바일 네트워크 이용 시 기본 동영상 화질 값을 %s로 변경합니다. 동영상 화질을 설정할 수 없습니다. Wi-Fi 이용 시 기본 동영상 화질 값을 %s로 변경합니다. - 사용자 정의 재생 속도는 %s 배속보다 작아야 하므로 기본값으로 초기화합니다. - 잘못된 재생 속도 값이므로 기본값으로 초기화합니다. + 사용자 정의 재생 속도는 %s 배속보다 작아야 합니다. + 잘못된 재생 속도 값입니다. Return YouTube Dislike Return YouTube Dislike 활성화하기 - 싫어요 개수를 표시합니다. - 싫어요 개수를 표시하지 않습니다. - Shorts에서 싫어요 개수 표시하기 - Shorts에서 싫어요 개수를 표시합니다. - "Shorts에서 싫어요 개수를 표시합니다. + 싫어요 수를 표시합니다. + 싫어요 수를 표시하지 않습니다. + Shorts에서 싫어요 수 표시하기 + Shorts에서 싫어요 수를 표시합니다. + "Shorts에서 싫어요 수를 표시합니다. -알려진 문제점: 사용자가 로그인을 하지 않았거나 시크릿 모드에서는 싫어요 개수가 표시되지 않을 수 있습니다." - Shorts에서 싫어요 개수를 표시하지 않습니다. - 싫어요 개수를 퍼센트로 표시하기 - 싫어요 개수를 퍼센트로 표시합니다. - 싫어요 개수를 숫자로 표시합니다. +알려진 문제점: 사용자가 로그인을 하지 않았거나 시크릿 모드에서는 싫어요 수가 표시되지 않을 수 있습니다." + Shorts에서 싫어요 수를 표시하지 않습니다. + 싫어요 수를 퍼센트로 표시하기 + 싫어요 수를 퍼센트로 표시합니다. + 싫어요 수를 숫자로 표시합니다. 좋아요 버튼에서 구분선 숨기기 좋아요 버튼에서 구분선을 표시하지 않습니다. 좋아요 버튼에서 구분선을 표시합니다. @@ -1242,11 +1300,11 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 정보 ReturnYouTubeDislike.com - 싫어요 개수의 데이터는 Return YouTube Dislike API에 의해 제공됩니다. 자세한 내용을 보려면 여기를 누르세요. - 싫어요 개수를 일시적으로 표시할 수 없습니다 (응답 시간 초과). - 싫어요 개수를 표시할 수 없습니다 (상태 코드: %d). - 싫어요 개수를 표시할 수 없습니다 (클라이언트 API 제한 도달). - 싫어요 개수를 표시할 수 없습니다 (%s). + 싫어요 수의 데이터는 Return YouTube Dislike API에 의해 제공됩니다. 자세한 내용을 보려면 여기를 누르세요. + 싫어요 수를 일시적으로 표시할 수 없습니다 (응답 시간 초과). + 싫어요 수를 표시할 수 없습니다 (상태 코드: %d). + 싫어요 수를 표시할 수 없습니다 (클라이언트 API 제한 도달). + 싫어요 수를 표시할 수 없습니다 (%s). Return YouTube Dislike를 사용하여 투표하려면 동영상을 다시 로드하세요. SponsorBlock @@ -1419,6 +1477,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 사용자 이름을 성공적으로 변경하였습니다. 사용자의 평판: <b>%.2f</b> 제출 횟수: <b>%s</b> + 구간을 보려면 여기를 누르세요. SponsorBlock 리더보드 다른 분들이 <b>%s</b>개의 구간을 건너뛸 수 있게 해주셨습니다. 글로벌 기록 또는 상위 기여자를 확인하려면 여기를 누르세요. @@ -1511,16 +1570,16 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." TV HTML5 Web 알려진 문제점 - • 영화 또는 유료 동영상이 재생되지 않을 수 있습니다. - • 오디오 트랙 메뉴가 표시되지 않습니다. - • 오디오 트랙 메뉴가 표시되지 않습니다. + "• 영화 또는 유료 동영상이 재생되지 않을 수 있습니다.\n• 되감기가 가능한 실시간 스트림이 라이브 중인 시점이 아닌 처음부터 재생될 수 있습니다.\n• 동영상이 1초 일찍 종료될 수 있습니다.\n• OPUS 오디오 코덱이 지원되지 않습니다." + • 오디오 트랙 메뉴가 표시되지 않습니다.\n• 안정적인 볼륨 메뉴가 비활성화된 채로 잠겨있습니다. + "• 오디오 트랙 메뉴가 표시되지 않습니다.\n• 안정적인 볼륨 메뉴가 비활성화된 채로 잠겨있습니다." • 동영상이 재생되지 않을 수 있습니다. iOS AVC (H.264) 강제로 활성화하기 - iOS 동영상 코덱을 AVC (H.264)로 활성화합니다.\n\n• \'일부 VP9 코덱 동영상에서 누락되었던 화질 값들\'이 표시될 수 있습니다.\n• 최대 화질 값이 1080p이므로 초고화질 동영상을 재생할 수 없습니다.\n• HDR 동영상을 재생할 수 없습니다. - iOS 동영상 코덱을 AVC (H.264), VP9 또는 AV1으로 활성화합니다.\n\n• 예전에 업로드된 동영상을 재생했는데 VP9 코덱 응답을 받았을 경우, 일부 화질 값들이 누락되어 있거나 화질 메뉴를 선택할 수 없을 수 있습니다. + iOS 동영상 코덱을 AVC (H.264)로 활성화합니다.\n\n• 일부 VP9 코덱 동영상에서 제거되었던 화질 값들이 표시될 수 있습니다.\n• 최대 화질 값이 1080p이므로 초고화질 동영상을 재생할 수 없습니다.\n• HDR 동영상을 재생할 수 없습니다 + iOS 동영상 코덱을 AVC (H.264), VP9 또는 AV1으로 활성화합니다.\n\n• 예전에 업로드된 동영상을 재생했는데 VP9 코덱 응답을 받았을 경우, 일부 화질 값들이 제거되어 360p와 1080p(Premium 기능)만 선택할 수 있거나 화질 메뉴를 선택할 수 없을 수 있습니다. "이 설정을 활성화하면 배터리 수명이 향상되고 재생 끊김 현상이 해결될 수 있습니다. -AVC (H.264)의 최대 화질 값은 1080p이며 동영상을 재생하면 VP9 또는 AV1보다 더 많은 인터넷 데이터가 사용됩니다." +AVC (H.264)의 최대 화질 값은 1080p이며 동영상을 재생하면 VP9 또는 AV1보다 더 많은 데이터가 사용됩니다." 전문 통계에서 표시하기 \'스트리밍 데이터를 가져오는 데 사용되는 클라이언트\'가 전문 통계에서 표시됩니다. \'스트리밍 데이터를 가져오는 데 사용되는 클라이언트\'가 전문 통계에서 숨겨집니다. @@ -1555,6 +1614,7 @@ AVC (H.264)의 최대 화질 값은 1080p이며 동영상을 재생하면 VP9 Revancify Blue Revancify Red YouTube + YouTube (최소화된 헤더) YouTube 제외된 패치 포함된 패치 diff --git a/src/main/resources/youtube/translations/pl-rPL/strings.xml b/src/main/resources/youtube/translations/pl-rPL/strings.xml index 145960fbfe..8d77a12eea 100644 --- a/src/main/resources/youtube/translations/pl-rPL/strings.xml +++ b/src/main/resources/youtube/translations/pl-rPL/strings.xml @@ -6,6 +6,7 @@ ReVanced Extended Wyszukaj w %s + Przywrócono domyślne wartości. Ustawienia Eksperymentalne Czy chcesz kontynuować? Uruchom ponownie, aby wczytać poprawnie układ aplikacji @@ -23,13 +24,6 @@ Pobierz %2$s ze strony internetowej." Pełnoekranowe reklamy Ukryte Widoczne - Pełnoekranowe reklamy - Zamykane poprzez przycisk zamknięcia - "Blokowane - -Ograniczenie: obrazy postów społeczności w trybie pełnoekranowym mogą być zablokowane" - Zablokowano reklamy pełnoekranowe. (Typ okna: %s) - Zamknięto reklamy pełnoekranowe. Ogólne reklamy Ukryte Widoczne @@ -77,6 +71,7 @@ Stuknij tutaj, aby dowiedzieć się więcej o DeArrow." Ukryty Punkt końcowy API DeArrow Adres URL punktu końcowego do miniaturek DeArrow + Nieprawidłowy adres URL API DeArrow. Miniaturki przechwycone z filmu Miniaturki przechwytywane z filmu są brane z początku, środka lub końca filmu. Obrazy te są wbudowane w YouTube, więc żadne zewnętrzne API nie jest używane. Szybkie przechwytywanie miniaturek z filmu @@ -98,6 +93,7 @@ Stuknij tutaj, aby dowiedzieć się więcej o DeArrow." Karty albumów Ukryte Widoczne + Półki karuzelowe "Ukrywa półki: • Z najnowszymi informacjami • Z filmami, które nie zostały dokończone @@ -105,13 +101,15 @@ Stuknij tutaj, aby dowiedzieć się więcej o DeArrow." • Z muzyką do ponownego odsłuchania • Od kupowania • Z filmami, które nie zostały obejrzane" - Półki karuzelowe Paski z kategoriami Ukryte Widoczne Produkty i rozdziały pod filmami Ukryte Widoczne + Rozszeralne półki + Ukryte + Widoczne Przycisk od napisów na stronie głównej Ukryty Widoczny @@ -252,18 +250,21 @@ Ograniczenia: • Filmy z takimi frazami jak 'Inne osoby również obejrzały' u dołu filmu" Ukryj filmy z małą ilością wyświetleń Ukrywa filmy poniżej 1000 wyświetleń ze strony głównej, które zostały przesłane z niesubskrybowanych kanałów. - - Filtrowanie według czasu trwania - Ukryj filmy na podstawie czasu trwania - Ukrywa filmy krótsze lub dłuższe od czasu trwania.\n\nZnany problem: Nie ukrywa filmów w rekomendacjach YouTube na końcach filmów - zamiast tego ukrywa czas. - Dłuższe od czasu trwania - Filmy o czasie trwania większym od tej liczby zostaną ukryte - Krótsze od czasu trwania - Filmy o czasie trwania mniejszym od tej liczby zostaną ukryte + Ukryj zaplanowane filmy + "Ukryj filmy z etykietą WKRÓTCE. + +Uwaga: Włączenie tego ustawienia ukryje też przycisk 'Powiadom mnie'." Filtr ilości wyświetleń - Ukryj rekomendowane filmy według ilości wyświetleń - Ukrywa rekomendowane filmy, które mają mniejszą ilość wyświetleń niż zadeklarowana.\n\nZnany problem: Filmy z zerem wyświetleń nie są filtrowane. + Na stronie głównej + Włączone + Wyłączone + W wynikach wyszukiwania + Włączone + Wyłączone + Na stronie subskrypcji + Włączone + Wyłączone Popularniejsze Filmy z wyświetleniami większymi niż ta liczba zostaną ukryte Mniej popularne @@ -271,6 +272,18 @@ Ograniczenia: Wyświetl klucze Określ swój szablon językowy dla liczby wyświetleń pod każdym filmem w interfejsie użytkownika. Każdy klucz (litera/słowo w twoim języku) - > wartość (znaczenie klucza) musi znajdować się w nowej linii. Klucze muszą znajdować się przed znakiem \"->\". Jeśli zmienisz język aplikacji, musisz zresetować to ustawienie.\n\nPrzykłady:\nAngielski: 10K views = K -> 1000, views -> views\nPolski: 10 tys. wyświetleń = tys -> 1000, wyświetleń -> views tys. -> 1 000\nmln -> 1 000 000\nmld - > 1 000 000 000\n wyświetleń-> views + O filtrowaniu filmów po wyświetleniach + "Strona główna / subskrypcje / wyniki wyszukiwania są filtrowane, by ukrywać filmy z ilością wyświetleń mniejszą bądź większą od określonej liczby. + +Ograniczenia: +• Shortsy nie mogą zostać ukryte +• Filmy z 0 wyświetleń nie są filtrowane" + Powiązane filmy + Ukryte + Widoczne + "Ustawienie te ogranicza maksymalną ilość układów aplikacji, jakie mogą być załadowane na ekranie odtwarzacza. + +Jeśli układ ekranu odtwarzacza zmieni się w skutek zmian po stronie serwera, niepożądane układy mogą być ukryte na ekranie odtwarzacza." Ogólne Strona startowa @@ -290,7 +303,11 @@ Ograniczenia: Subskrypcje Na czasie Do obejrzenia - Nieprawidłowa strona startowa, zresetowano do domyślnej. + Działanie zmieniania strony głównej + "Strona główna zawsze się zmienia + +Ograniczenie: Przycisk wstecz na pasku narzędzi może nie działać." + Strona główna zmienia się tylko raz Wymuszone ścieżki dźwiękowe Wyłączone Włączone @@ -319,14 +336,6 @@ Nie pomija to ograniczeń wiekowych, lecz akceptuje je automatycznie." Oszukuje dpi, aby używać układu telefonu Układ tabletu Oszukuje dpi, aby używać układu tabletu - Metoda pobierania filmów - Zewnętrzna aplikacja - Natywne pobieranie - Metoda pobierania playlist - Zewnętrzna aplikacja - Natywne pobieranie - Nazwa pakietu aplikacji od pobierania (playlisty) - Nazwa pakietu zainstalowanej zewnętrznej aplikacji od pobierania, takiej jak YTDLnis. Oszukiwanie wersji aplikacji Włączone Wyłączone @@ -366,6 +375,31 @@ Niektóre komponenty mogą nie być ukryte." Lista tekstów tworzących ścieżkę komponentów do filtrowania, która musi być oddzielona nowymi liniami. Nieprawidłowy własny filtr: %s. + + Zmień działanie przycisków + Nadpisuje działanie przycisków w aplikacji + + Przycisk od pobierania + Metoda pobierania filmów + Zewnętrzna aplikacja + Natywne pobieranie + Metoda pobierania playlist + Zewnętrzna aplikacja + Natywne pobieranie + Nazwa pakietu aplikacji od pobierania (playlisty) + Nazwa pakietu zainstalowanej zewnętrznej aplikacji od pobierania, takiej jak YTDLnis. + + Aplikacja otwierana przyciskiem do YouTube Music + RVX Music + YouTube Music + Nazwa pakietu RVX Music + Nazwa pakietu zainstalowanego RVX Music + RVX Music + Ostrzeżenie + %s nie jest zainstalowany. Proszę go zainstalować. + Wymaganie wstępne + YouTube Music jest wymagane do zmiany tego ustawienia. +Stuknij tutaj, by pobrać YouTube Music. Miniodtwarzacz Zmień styl miniodtwarzacza w aplikacji @@ -396,10 +430,10 @@ Niektóre komponenty mogą nie być ukryte." Widoczne Przezroczystość nakładki Wartość przezroczystości musi wynosić między 0 a 100, gdzie 0 oznacza przezroczystość - Przezroczystość nakładki miniodtwarzacza musi wynosić między 0 a 100. Przywrócono domyślną wartość. - - Przyciski nawigacji - Ukryj lub pokazuj przyciski nawigacji + Przezroczystość nakładki miniodtwarzacza musi wynosić między 0 a 100. + + Pasek nawigacji + Ukryj lub pokazuj przyciski nawigacji Wąskie przyciski nawigacji Odstępy między przyciskami nawigacji są mniejsze Odstępy między przyciskami nawigacji są normalne @@ -425,14 +459,21 @@ Niektóre komponenty mogą nie być ukryte." Ukryte Widoczne Zamień przyciski przesyłania i powiadomień - "Zamienia pozycję przycisku od przesyłania z przyciskiem do powiadomień oszukując informacje o urządzeniu. + "Zamienione + +Notka: włączenie tej opcji wymusza ukrywanie reklam w filmach." + Nie zamienone + "Wyłączenie tej opcji może spowodować załadowanie się większej ilości reklam z serwera. -• Nawet jeśli zmienisz to ustawienie, może się nic nie zmienić dopóki nie zrestartujesz urządzenia. -• Wyłączenie tego ustawienia powoduje zwiększenie ilości reklam. -• Powinieneś wyłączyć to ustawienie jeśli chcesz, by reklamy w filmach były widoczne." +Dodatkowo, reklamy nie będą już blokowane w Shortsach. + +Jeśli opcja nie przynosi skutku, spróbuj przełączyć się na tryb incognito." Wygląd paska nawigacji Półprzezroczysty Nieprzezroczysty + Pasek nawigacji + Ukryty + Widoczny Menu ustawień Ukryj elementy menu ustawień YouTube @@ -489,7 +530,7 @@ Stuknij i przytrzymaj, by otworzyć ustawienia RVX." Odtwarzacz Niestandardowa przezroczystość nakładki Wartość przezroczystości musi wynosić między 0 a 100, gdzie 0 oznacza przezroczystość - Przezroczystość nakładki odtwarzacza musi wynosić między 0 a 100. Przywrócono domyślną wartość. + Przezroczystość nakładki odtwarzacza musi wynosić między 0 a 100. Wyskakujące panele w odtwarzaczu Widoczne Ukryte @@ -508,7 +549,7 @@ Notki: • Wyłączenie tego ustawienia nie powoduje wymuszonego włączenia nakładki prędkości odtwarzania" Wartość nakładki prędkości odtwarzania Wartość nakładki prędkości odtwarzania między 0, a 8. - Przezroczystość nakładki prędkości odtwarzania musi wynosić między 0 a 8.0. Przywrócono domyślną wartość. + Przezroczystość nakładki prędkości odtwarzania musi wynosić między 0 a 8.0. Znaki wodne kanałów Ukryte Widoczne @@ -554,12 +595,12 @@ Notki: Autoodtwarzanie można zmienić w ustawieniach YouTube: 'Ustawienia → Autoodtwarzanie → Autoodtwarzanie następnego filmu'" Widoczne - Poświata przy powiększaniu filmu - Ukryta - Widoczna Pomiń odliczanie do automatycznego odtwarzania Jeżeli automatyczne odtwarzanie jest włączone, następny film zostanie odtworzony bez odliczania. Jeżeli automatyczne odtwarzanie jest włączone, następny film zostanie odtworzony po ukończeniu odliczania. + Poświata przy powiększaniu filmu + Ukryta + Widoczna Przyciski akcji Ukryj lub pokazuj przyciski akcji pod odtwarzaczem @@ -765,7 +806,7 @@ Ograniczenie: tytuły filmów znikają po stuknięciu w nie." Widoczny Górny margines przycisków na dole odtwarzacza Skonfiguruj odstęp od paska postępu filmu do kontenera szybkich akcji, w zakresie od 0 do 32. - Górny margines przycisków na dole odtwarzacza musi wynosić między 0 a 32. Przywrócono domyślną wartość. + Górny margines przycisków na dole odtwarzacza musi wynosić między 0 a 32. Tryb poziomy Orientacja filmu jest w trybie pionowym w trybie pełnoekranowym. @@ -1002,33 +1043,9 @@ Ograniczenie: Nagłówki z tytułami będą ukryte w wynikach wyszukiwania."Przycisk od trendów Ukryty Widoczny - Przycisk od sklepu + Przycisk od kupowania Ukryty Widoczny - Przycisk do sklepu - Ukryty - Widoczny - Przycisk od superpodziękowania - Ukryty - Widoczny - Oznaczone produkty - Ukryte - Widoczne - Przycisk od lokalizacji - Ukryty - Widoczny - Przycisk od zapisywania dźwięku do playlisty - Ukryty - Widoczny - Przycisk od sugestii wyszukiwań - Ukryty - Widoczny - Przycisk \'Użyj tego dźwięku\' - Ukryty - Widoczny - Przycisk \'Użyj tego szablonu\' - Ukryty - Widoczny Panele z informacjami Ukryte Widoczne @@ -1047,6 +1064,35 @@ Ograniczenie: Nagłówki z tytułami będą ukryte w wynikach wyszukiwania."Etykiety z linkami do całych filmów Ukryte Widoczne + + Sugerowane akcje + Przycisk od greenscreena + Ukryty + Widoczny + Przycisk od zapisywania dźwięku + Ukryty + Widoczny + Przycisk do sklepu + Ukryty + Widoczny + Przycisk od superpodziękowania + Ukryty + Widoczny + Przycisk \'Użyj tego dźwięku\' + Ukryty + Widoczny + Przycisk \'Użyj tego szablonu\' + Ukryty + Widoczny + Przycisk od lokalizacji + Ukryty + Widoczny + Przycisk od sugestii wyszukiwań + Ukryty + Widoczny + Oznaczone produkty + Ukryte + Widoczne Przyciski akcji Przycisk od łapkowania w górę @@ -1069,6 +1115,9 @@ Ograniczenie: Nagłówki z tytułami będą ukryte w wynikach wyszukiwania."Widoczny Animacje / Przesyłanie opinii + Animacja przycisku polubienia + Wyłączona + Włączona Tło przycisku odtwarzania i pauzy Ukryte Widoczne @@ -1091,13 +1140,16 @@ Ograniczenia: Naciśnij i przytrzymaj czas, aby zmienić status powtarzania Shortsów Dolny margines panelu meta Skonfiguruj odstęp od paska postępu filmu do panelu meta, w zakresie od 0 do 64. - Dolny margines panelu meta musi wynosić między 0 a 64. Przywrócono domyślne wartości. + Dolny margines panelu meta musi wynosić między 0 a 64. Pasek z narzędziami Ukryty Widoczny Pasek nawigacji Ukryty Widoczny + Wysokość pustego miejsca (procentowa) + Zmienia wysokość pustej przestrzeni po ukryciu paska nawigacyjnego, od 0 do 100 (%) + Wysokość musi wynosić od 0 do 100 (%). Wyświetlanie tytułu kanału Po nazwie Po nicku @@ -1132,7 +1184,7 @@ Ograniczenia: Rozmiar tekstu nakładki przesuwania Rozmiar obszaru przesuwania Procentowa wartość obszaru ekranu, gdzie można przesuwać.\n\nNotka: Zmieni to także rozmiar obszaru ekranu dla gestu podwójnego kliknięcia, aby przewinąć film. - Rozmiar obszaru przesuwania nie może być większy niż 50. Przywrócono domyślną wartość. + Rozmiar obszaru przesuwania nie może być większy niż 50. Limit czasu widoczności nakładki Ilość milisekund, przez które nakładka jest widoczna Automatyczna jasność w filmach z HDR @@ -1201,6 +1253,13 @@ Informacje: Oszukaj rozdzielczość urządzenia "Oszukuje rozdzielczość urządzenia do maksymalnej wartości. Wysoka jakość może być odblokowana na niektórych filmach, które wymagają wysokiej rozdzielczości urządzenia, lecz nie na wszystkich." + Wyłącz kodek VP9 + "Wyłączone + +• Maksymalną rozdzielczością filmów jest 1080p +• Odtwarzanie filmów wykorzystuje więcej danych internetowych niż VP9 +• Kodek VP9 jest używany do filmów z HDR" + Włączone Zmień kodek AV1 programu Zastąp kodek AV1 programu kodekiem VP9 Odrzucaj kodek AV1 programu @@ -1211,8 +1270,8 @@ Po około 20 sekundach ładowania kodek zostanie zmieniony." Zmieniono domyślną jakość podczas używania sieci mobilnej na %s. Nie udało się zmienić jakości obrazu. Zmieniono domyślną jakość podczas używania Wi-Fi na %s. - Niestandardowe prędkości muszą by mniejsze niż %sx. Przywrócono domyślne wartości. - Nieprawidłowe niestandardowe prędkości odtwarzania. Przywrócono domyślne wartości. + Niestandardowe prędkości muszą by mniejsze niż %sx. + Nieprawidłowe niestandardowe prędkości odtwarzania. Return YouTube Dislike Return YouTube Dislike @@ -1413,6 +1472,7 @@ Ograniczenie: Liczba łapek w dół może nie być widoczna, gdy użytkownik nie Nazwa użytkownika została zmieniona. Twoja reputacja to <b>%.2f</b> Stworzyłeś <b>%s</b> segmentów + Kliknij, by zobaczyć swoje segmenty Tablica wyników SponsorBlock Uchroniłeś ludzi przed <b>%s</b> segmentami Stuknij tutaj, aby zobaczyć globalne statystyki i najlepszych użytkowników. @@ -1505,9 +1565,13 @@ Kontynuuj i wyłącz optymalizację baterii." TV HTML5 Przeglądarka Efekty uboczne oszukiwania - • Filmy kinowe lub płatne filmy mogą się nie odtwarzać + "• Filmy kinowe lub płatne filmy mogą się nie odtwarzać +• Transmisje na żywo rozpoczynają się od początku +• Filmy mogą kończyć się o 1 sekundę wcześniej +• Kodek opus jest niedostępny" • Brakuje menu od ścieżki dźwiękowej - • Brakuje menu od ścieżki dźwiękowej + "• Brakuje menu od ścieżki dźwiękowej +• Stabilna głośność jest niedostępna" • Filmy mogą się nie odtwarzać Wymuś kodek iOS AVC (H.264) Włączone @@ -1549,6 +1613,7 @@ Kodek AVC (H.264) obsługuje maksymalnie rozdzielczość 1080p, a odtwarzanie fi Niebieska od Revancify Czerwona od Revancify YouTube + YouTube (Minimalny nagłówek) Domyślne Wykluczone Zawarte diff --git a/src/main/resources/youtube/translations/pt-rBR/strings.xml b/src/main/resources/youtube/translations/pt-rBR/strings.xml index b2944e494b..0047b192ae 100644 --- a/src/main/resources/youtube/translations/pt-rBR/strings.xml +++ b/src/main/resources/youtube/translations/pt-rBR/strings.xml @@ -6,6 +6,7 @@ ReVanced Extended Buscar %s + Redefinir para os valores padrão. Sinalizadores experimentais Você deseja continuar? Reinicie para carregar o layout normalmente @@ -23,13 +24,6 @@ Por favor, baixe %2$s do site." Ocultar anúncios em tela cheia Os anúncios em tela cheia estão ocultos. Os anúncios em tela cheia serão exibidos. - Fechar anúncios em tela cheia - Os anúncios de tela cheia são fechados através do botão Fechar. - "Os anúncios em tela cheia estão bloqueados. - -Limitação: A imagem do post da comunidade em tela cheia pode ser bloqueada." - Os anúncios em tela cheia foram bloqueados. (Tipo de diálogo: %s) - Os anúncios de tela cheia foram fechados. Ocultar anúncios gerais Os anúncios gerais estão ocultos. Os anúncios gerais serão exibidos. @@ -79,6 +73,7 @@ Toque aqui para saber mais sobre o DeArrow." Notificação flutuante não exibida se Derow não estiver disponível. DeArrow API endpoint A URL do ponto final do cache de miniaturas DeArrow. + URL da API DeArrow inválida. Capturas de vídeo estáticas As capturas estáticas são feitas do início / meio / fim de cada vídeo. Essas imagens são integradas ao YouTube e nenhuma API externa é usada. Usar capturas estáticas rápidas @@ -100,6 +95,7 @@ Toque aqui para saber mais sobre o DeArrow." Ocultar cartões de álbum Os cartões de álbum estão ocultos. Os cartões de álbum serão exibidos. + Ocultar painel de carrossel "Oculta os seguintes painéis: • Últimas notícias • Continue assistindo @@ -107,13 +103,15 @@ Toque aqui para saber mais sobre o DeArrow." • Ouça novamente • Compras • Assista novamente" - Ocultar painel de carrossel Ocultar painel de chips O painel de chips está oculto. O painel de chips será exibido. Ocultar chip expansível nos vídeos Os chips expansíveis estão ocultos. Os chips expansíveis serão exibidos. + Ocultar painéis expansíveis + Os painéis expansíveis estão ocultos. + Os painéis expansíveis serão exibidos. Ocultar botão de legendas no feed O botão de legendas está oculto. O botão de legendas será exibido. @@ -132,7 +130,7 @@ Toque aqui para saber mais sobre o DeArrow." Ocultar o botão \'Vídeos recentes\' O botão \'Vídeo recentes\' está oculto. O botão \'Vídeos recentes\' será exibido. - Ocultar playlist mix + Ocultar playlists mix A playlist mix está oculta. A playlist mix será exibida. Ocultar painel de filmes @@ -252,18 +250,21 @@ Limitações: • Vídeos enviados de canais não inscritos com menos de 1,000 visualizações" Ocultar vídeos com baixas visualizações Ocultar vídeos com menos de 1.000 visualizações do feed de início que foram enviadas de canais não inscritos. - - Filtro de duração - Ocultar vídeos baseado na duração - Ocultar vídeos com duração menor ou maior que.\n\nProblema conhecido: não ocultará vídeos nos vídeos relacionados no reprodutor, ao invés disso ocultará a marcação de tempo. - Maior que a duração - Vídeos com duração maior que este número serão ocultados. - Menor que a duração - Vídeos com duração menor que este número serão ocultados. + Ocultar PRÓXIMO vídeo + "Ocultar vídeos com a etiqueta PRÓXIMO. + +Nota: Ativar isso também oculta o botão Notificar-me." Filtro por contagem de visualização - Ocultar vídeos recomendados por visualizações - Oculte vídeos recomendados com menos de um determinado número de visualizações. + Ocultar vídeos no feed de início por visualizações + Os vídeos no feed de início são filtrados. + Os vídeos no feed de início não são filtrados. + Ocultar resultados de pesquisa por visualizações + Os resultados da pesquisa são filtrados. + Os resultados da pesquisa não são filtrados. + Ocultar vídeos no feed de inscrições por visualizações + Os vídeos no feed de inscrições são filtrados. + Os vídeos no feed de inscrições não são filtrados. Maior que as visualizações Vídeos com visualizações maiores que este número serão ocultados. Menos que as visualizações @@ -271,6 +272,18 @@ Limitações: Chaves de visualização Especifique seu modelo de idioma para o número de visualizações mostradas em cada vídeo na interface do usuário. Cada chave (uma letra/palavra no seu idioma) sinal -> (significado da chave) deve estar em uma nova linha. Chaves vão antes do sinal \"->\". Se você alternar o idioma do aplicativo ou do sistema, você terá que redefinir esta configuração.\n\nExemplos:\nInglês: 10K views = K -> 1000, views -> views\nEspanhol: 10K vistas = K -> 1000, vistas -> views K -> 1 000\nM -> 1 000 000\nB -> 1 000 000 000\nvisualizações -> visualizações + Sobre a filtragem da contagem de visualizações + "Início / Inscrições / Resultados de pesquisa são filtrados para ocultar vídeos com visualização menor ou maior que um número especificado. + +Limitações: +• Shorts não podem ser ocultos. +• Vídeos com 0 visualizações não são filtrados." + Ocultar vídeos relacionados + Os vídeos relacionados estão ocultos. + Os vídeos relacionados serão exibidos. + "Esta configuração limita o número máximo de layouts que podem ser carregados na tela do reprodutor. + +Se o layout da tela do reprodutor mudar devido a alterações no lado do servidor, layouts não intencionais podem ficar ocultos na tela do reprodutor." Geral Alterar a página inicial @@ -290,7 +303,11 @@ Limitações: Inscrições Em alta Assistir mais tarde - Página inicial inválida, redefinindo para o padrão. + Alterar tipo de página inicial + "Página inicial sempre muda. + +Limitação: O botão voltar na barra de ferramentas pode não funcionar." + A página inicial muda apenas uma vez. Desativar faixas de áudio automáticas forçadas As faixas de áudio automáticas forçadas estão desativadas. As faixas de áudio automáticas forçadas estão ativadas. @@ -319,14 +336,6 @@ Isso não ignora a restrição de idade, apenas aceita isso automaticamente."Engana o dpi para usar alguns layouts para telefone. Ativar layout de tablet Engana o dpi para usar alguns layouts para tablets. - Substituir o botão de download de vídeo - O botão de download de vídeo nativo abre seu aplicativo de download externo. - O botão de download de vídeo nativo abre o download nativo do aplicativo. - Substituir o botão de download da playlist - O botão de download de playlist nativo abre seu aplicativo de download externo. - O botão de download de playlist nativo abre o download nativo do aplicativo. - Nome do pacote do aplicativo de download de playlist - Nome do pacote do seu aplicativo de download externo instalado, como YTDLnis. Falsificar versão do aplicativo Versão falsificada Versão não falsificada @@ -366,6 +375,30 @@ Alguns componentes podem não ser ocultos" Lista de componentes a serem filtradas separadas por uma nova linha. Filtro personalizado inválido: %s + + Botões hook + Substitui a ação de clique dos botões do aplicativo. + + Botão de download + Substituir o botão de download de vídeo + O botão de download de vídeo nativo abre seu aplicativo de download externo. + O botão de download de vídeo nativo abre o download nativo do aplicativo. + Substituir o botão de download da playlist + O botão de download de playlist nativo abre seu aplicativo de download externo. + O botão de download de playlist nativo abre o download nativo do aplicativo. + Nome do pacote do aplicativo de download de playlist + Nome do pacote do seu aplicativo de download externo instalado, como YTDLnis. + + Substituir botão do YouTube Music + O botão do YouTube Music abre o RVX Music. + O botão do YouTube Music abre o aplicativo nativo. + Nome do pacote do RVX Music + Nome do pacote do RVX Music instalado. + RVX Music + Aviso + %s não está instalado. Por favor, instale-o. + Pré-requisito + O YouTube Music é necessário para substituir a ação do botão. Toque aqui para baixar o YouTube Music. Mini reprodutor Alterar o estilo do reprodutor minimizado no aplicativo. @@ -397,9 +430,9 @@ Alguns componentes podem não ser ocultos" Opacidade da sobreposição Valor da opacidade entre 0-100, onde 0 é transparente. A opacidade da sobreposição do mini reprodutor deve ser entre 0-100. Redefinir aos valores padrão. - - Botões de navegação - Ocultar ou mostrar componentes da seção de navegação. + + Barra de Navegação + Ocultar ou mostrar componentes da seção de navegação. Ativar botões estreitos de navegação O espaçamento entre os botões de navegação se torna reduzido. O espaçamento entre os botões de navegação não se torna mais reduzido. @@ -425,14 +458,21 @@ Alguns componentes podem não ser ocultos" O rótulo de navegação está oculto. O rótulo de navegação será exibido. Alternar criar com notificações - "Alterne as posições do botão de criação e do botão de notificação falsificando as informações do dispositivo. + "O botão criar é alternado com o botão notificações. + +Nota: Ativar isso também oculta anúncios de vídeo à força." + O botão criar não está alternado com o botão Notificações. + "Desativar isso pode carregar mais anúncios do servidor. -• Mesmo que você altere esta configuração, ela poderá não entrar em vigor até que você reinicie o dispositivo. -• Desativar esta configuração carrega mais anúncios do lado do servidor. -• Você deve desativar esta configuração para tornar os anúncios em vídeo visíveis." +Além disso, os anúncios não serão mais bloqueados no Shorts. + +Se essa configuração não surtir efeito, tente alternar para o modo anônimo." Ativar barra de navegação transparente A barra de navegação está transparente. A barra de navegação está opaca. + Ocultar barra de navegação + A barra de navegação está oculta. + A barra de navegação será exibida. Menu de configurações Ocultar elementos no menu de configurações do YouTube. @@ -493,6 +533,13 @@ Toque e segure para abrir as Configurações RVX." Desativar painéis popup do reprodutor Os painéis popup do reprodutor automático estão ativados. Os painéis popup do reprodutor automático estão desativados. + Desativar troca de playlists mix + A troca automática de playlists mix está desativada. + "A troca automática de playlists de mix está ativada quando a reprodução automática está ativada. + +A reprodução automática pode ser alterada nas configurações do YouTube: +Configurações → Reprodução automática → Reprodução automática do próximo vídeo" + Ativar este recurso irá desativar a mudança automática para o YouTube Mix quando a reprodução automática estiver ativada. Desativar sobreposição de velocidade "Desativar o 'Reproduzindo na velocidade 2x' enquanto segurar. @@ -547,12 +594,12 @@ Nota: A reprodução automática pode ser alterada nas configurações do YouTube: 'Configurações → Reprodução automática → Reprodução automática do próximo vídeo'" A tela de vídeo sugerido no fim é exibida. - Ocultar sobreposição de zoom - A sobreposição de zoom está oculta. - A sobreposição de zoom será exibida. Pular contagem regressiva de reprodução automática Se a reprodução automática estiver ativada, o próximo vídeo será reproduzido sem uma contagem regressiva. Se a reprodução automática estiver ativada, o próximo vídeo será reproduzido após a contagem regressiva terminar. + Ocultar sobreposição de zoom + A sobreposição de zoom está oculta. + A sobreposição de zoom será exibida. Botões de ação Ocultar ou mostrar botões de ação sob os vídeos. @@ -1000,30 +1047,6 @@ Limitação: Os cabeçalhos oficiais nos resultados da pesquisa serão ocultados Ocultar botão de Compras O botão de compras está oculto. O botão de compras será exibido. - Ocultar botão comprar - O botão comprar está oculto. - O botão comprar será exibido. - Ocultar botão de Super Valeu - O botão de Super Valeu está oculto. - O botão de Super Valeu será exibido. - Ocultar produtos marcados - Os produtos marcados estão ocultos. - Os produtos marcados serão exibidos. - Ocultar botão localização - O botão localização está oculto. - O botão de localização será exibido. - Ocultar botão salvar música na lista de reprodução - Salvar música na lista de reprodução está oculta. - Salvar música na lista de reprodução será exibida. - Ocultar botão de sugestões de pesquisa - O botão de sugestões de pesquisa está oculto. - O botão de sugestões de pesquisa será exibido. - Ocultar botão Usar este som - O botão Usar este som está oculto. - O botão Usar este som será exibido. - Ocultar botão Usar template - O botão Usar template está oculto. - O botão Usar template será exibido. Ocultar painéis de informações Os painéis de informação estão ocultos. Os painéis de informação serão exibidos. @@ -1042,6 +1065,31 @@ Limitação: Os cabeçalhos oficiais nos resultados da pesquisa serão ocultados Ocultar rótulo completo do link do vídeo O rótulo de link de vídeo está oculto. O rótulo de link de vídeo será exibido. + + Ocultar botão salvar música na lista de reprodução + Salvar música na lista de reprodução está oculta. + Salvar música na lista de reprodução será exibida. + Ocultar botão comprar + O botão comprar está oculto. + O botão comprar será exibido. + Ocultar botão de Super Valeu + O botão de Super Valeu está oculto. + O botão de Super Valeu será exibido. + Ocultar botão Usar este som + O botão Usar este som está oculto. + O botão Usar este som será exibido. + Ocultar botão Usar template + O botão Usar template está oculto. + O botão Usar template será exibido. + Ocultar botão localização + O botão localização está oculto. + O botão de localização será exibido. + Ocultar botão de sugestões de pesquisa + O botão de sugestões de pesquisa está oculto. + O botão de sugestões de pesquisa será exibido. + Ocultar produtos marcados + Os produtos marcados estão ocultos. + Os produtos marcados serão exibidos. Botões de ação Ocultar botão curtir @@ -1091,6 +1139,9 @@ Problema conhecido: como se trata de um recurso em fase de desenvolvimento pelo Ocultar barra de navegação A barra de navegação está oculta. A barra de navegação será exibida. + Porcentagem de altura do espaço vazio + Configura a porcentagem de altura do espaço vazio esquerdo quando a barra de navegação está oculta, entre 0 e 100 (%). + A porcentagem de altura deve estar entre 0-100 (%). Substitua o identificador do canal O nome do canal é usado. O identificador do canal é usado. @@ -1172,6 +1223,11 @@ Problema conhecido: como se trata de um recurso em fase de desenvolvimento pelo Restaurar menu antigo de qualidade de vídeo O menu antigo de qualidade de vídeo está sendo exibido. O menu antigo de qualidade de vídeo não está sendo exibido. + Desativar a velocidade de reprodução para música + "A velocidade de reprodução padrão é desabilitada para música. + +Limitação: esta configuração pode não se aplicar a vídeos que não incluem o banner 'Ouvir no YouTube Music'." + A velocidade de reprodução padrão está ativada para música. Ativar velocidade de reprodução padrão no Shorts A velocidade de reprodução padrão se aplica ao Shorts. A velocidade de reprodução padrão não se aplica ao Shorts. @@ -1188,6 +1244,13 @@ Problema conhecido: como se trata de um recurso em fase de desenvolvimento pelo Falsificar dimensões do dispositivo "Falsifica as dimensões do dispositivo para o valor máximo. A alta qualidade pode ser desbloqueada em alguns vídeos que exigem dimensões elevadas do dispositivo, mas não em todos os vídeos." + Desativar codec VP9 + "O codec VP9 está desativado. + +• A resolução máxima é 1080p. +• A reprodução de vídeos usará mais dados na internet do que VP9. +• Para fazer com que o HDR reproduza, o vídeo HDR ainda usa o codec VP9." + O codec VP9 está ativado. Substituir codec AV1 do software Substitua o codec AV1 do software com o codec VP9. Rejeitar resposta do codec AV1 do software @@ -1400,6 +1463,7 @@ Limitação: Dislikes pode não aparecer no modo incógnito." Nome de usuário alterado com sucesso. Sua reputação é <b>%.2f</b> Você criou <b>%s</b> segmentos + Toque aqui para ver seus segmentos. Tabela de classificação SponsorBlock Você salvou pessoas de <b>%s</b> segmentos Toque aqui para ver as estatísticas globais e os principais colaboradores. @@ -1492,9 +1556,9 @@ Toque no botão continuar e desative as otimizações da bateria." TV HTML5 Web Efeitos colaterais da falsificação - • Filmes ou vídeos pagos podem não reproduzir. + "• Filmes ou vídeos pagos podem não reproduzir." • O menu de faixa de áudio está faltando. - • O menu de faixa de áudio está faltando. + "• O menu de faixa de áudio está faltando." • O vídeo pode não reproduzir. Forçar iOS AVC (H.264) O codec de vídeo do iOS é AVC (H.264). @@ -1536,6 +1600,7 @@ AVC (H. 64) tem uma resolução máxima de 1080p, e a reprodução de vídeo usa Revancify Blue Revancify Red YouTube + YouTube (Cabeçalho Mínimo) Padrão Excluído Incluído diff --git a/src/main/resources/youtube/translations/ru-rRU/strings.xml b/src/main/resources/youtube/translations/ru-rRU/strings.xml index bcfbfc9de6..29ac9bce87 100644 --- a/src/main/resources/youtube/translations/ru-rRU/strings.xml +++ b/src/main/resources/youtube/translations/ru-rRU/strings.xml @@ -6,6 +6,7 @@ ReVanced Extended Поиск %s + К значениям по умолчанию. Экспериментальные опции Подтверждаете действие? Перезапустить для правильной загрузки интерфейса? @@ -23,14 +24,6 @@ Полноэкранная реклама Полноэкранная реклама скрыта. Полноэкранная реклама отображена. - Способ закрытия рекламы в полном экране - Реклама в полном экране закрыта кнопкой. - "Реклама в полном экране заблокирована. - -Ограничение: -Посты сообщества, в полном экране, могут быть заблокированы." - Реклама в полном экране была заблокирована. (Взаимодействие: %s) - Реклама в полном экране была закрыта. Реклама общего формата Реклама общего формата скрыта. Реклама общего формата отображена. @@ -80,6 +73,7 @@ Уведомление при недоступности DeArrow API отключено. DeArrow API URL кэша миниатюр DeArrow. + Неверный URL API для миниатюр DeArrow. О захвате кадров из видео Кадры захватываются из начала/середины/конца каждого видео. Эти кадры встроены в YouTube и внешний API не используется. Выбор качества кадров @@ -102,6 +96,7 @@ Карточки альбомов Карточки альбомов скрыты. Карточки альбомов отображены. + Скрыть рекомендуемые секции "Скрывает следующие секции: • Срочные новости • Продолжить просмотр @@ -109,13 +104,15 @@ • Слушать ещё раз • Покупки • Смотреть ещё раз" - Скрыть рекомендуемые секции Секция эпизодов Секция эпизодов скрыта. Секция эпизодов отображена. Расширенные эпизоды под видео Расширенные эпизоды скрыты. Расширенные эпизоды отображены. + Расширяемые секции + Расширяемые секции скрыты. + Расширяемые секции отображены. Кнопка \"Субтитры\" в ленте Кнопка \"Субтитры\" в ленте скрыта. Кнопка \"Субтитры\" в ленте отображена. @@ -257,18 +254,20 @@ Shorts • С каналов, на которые вы не подписаны (менее 1000 просмотров)." Скрыть видео с низкими просмотрами Скрыть видео с менее 1000 просмотров из ленты, из каналов от которых Вы отписались. - - Фильтр по продолжительности - Скрыть видео по продолжительности - Скрывает видео с продолжительностью меньше или больше заданной. - Продолжительность больше указанной - Видео с большей продолжительностью будут скрыты. - Продолжительность меньше указанной - Видео с меньшей продолжительностью будут скрыты. + Подготовленное видео + "Скрывает видео с меткой \"подготовленное\" к воспроизведению. +Примечание: Также скроется кнопка \"Уведомить\"." Фильтр по количеству просмотров - Скрыть рекомендованные видео по количеству просмотров - Скрывает рекомендованные видео с заданным количеством просмотров. + Фильтр видео в \"Главная\" по просмотрам + Фильтр видео в \"Главная\", по просмотрам, включен. + Фильтр видео в \"Главная\", по просмотрам, отключен. + Фильтр результатов поиска по просмотрам + Фильтр результатов поиска, по просмотрам, включен. + Фильтр результатов поиска, по просмотрам, отключен. + Фильтр видео в подписках по просмотрам + Фильтр видео в подписках, по просмотрам, включен. + Фильтр видео в подписках, по просмотрам, отключен. Больше по просмотрам Видео с большим количеством просмотров будут скрыты. Меньше по просмотрам @@ -285,6 +284,18 @@ Shorts Млн -> 1 000 000 Млрд -> 1 000 000 000 просмотров -> views + О фильтре подсчета просмотров + "Главная / Подписки / Результаты поиска, фильтруются, чтобы скрыть видео с просмотрами отличающиеся от введенного числа. + +Ограничения: +• Shorts нельзя скрыть. +• Видео без просмотров не обрабатываются." + Похожие видео + Похожие видео скрыты. + Похожие видео отображены. + "Этот параметр ограничивает максимальное количество макетов, которые могут быть загружены на экране плеера. + +Если макет экрана проигрывателя меняется из-за изменений на стороне сервера, незапланированные макеты могут быть скрыты на экране плеера." Основные настройки Начальная страница @@ -304,7 +315,10 @@ Shorts Подписки В тренде Смотреть позже - Недопустимая начальная страница, сброс по умолчанию. + Тип начальной страницы + "Начальная страница постоянно изменяется. +Ограничение: Кнопка возврат может не работать." + Начальная страница изменится один раз. Принудительные автоматические звуковые дорожки Принудительные автоматические звуковые дорожки отключены. Принудительные автоматические звуковые дорожки включены. @@ -332,14 +346,6 @@ Shorts Подмена DPI для телефонного интерфейса. Планшетный интерфейс Подмена DPI для планшетного интерфейса. - Действие кнопки \"Скачать\" для видео - Кнопка \"Скачать\" использует внешний загрузчик. - Кнопка \"Скачать\" использует внутренний загрузчик. - Действие кнопки \"Скачать\" для плейлиста - Кнопка \"Скачать\" для плейлиста, использует внешний загрузчик. - Кнопка \"Скачать\" для плейлиста, использует внутренний загрузчик. - Внешний загрузчик для плейлиста, название пакета - Например: NewPipe или YTDLnis, или др. (Для плейлиста). Подмена версии приложения Версия приложения подменена Версия приложения не подменена @@ -379,6 +385,31 @@ Shorts Список компонентов для скрытия\nРазделять новой строкой. Недопустимый фильтр: %s. + + Настройки действий кнопок + Переопределяет действие нажатия кнопок в приложении. + + Кнопка \"Скачать\" + Действие кнопки \"Скачать\" для видео + Кнопка \"Скачать\" использует внешний загрузчик. + Кнопка \"Скачать\" использует внутренний загрузчик. + Действие кнопки \"Скачать\" для плейлиста + Кнопка \"Скачать\" для плейлиста, использует внешний загрузчик. + Кнопка \"Скачать\" для плейлиста, использует внутренний загрузчик. + Внешний загрузчик для плейлиста, название пакета + Например: NewPipe или YTDLnis, или др. (Для плейлиста). + + Переопределить кнопку YouTube Music + Кнопка YouTube Music открывает RVX Music. + Кнопка YouTube Music открывает встроенное приложение. + Имя пакета RVX Music + Название пакета установленной RVX Music. + RVX Music + Предупреждение + %s не установлен. Установите его. + Обязательное условие + Для переопределения кнопки требуется YouTube Music. +Нажмите здесь, чтобы загрузить YouTube Music. Мини-плеер Стиль мини-плеера. @@ -411,9 +442,9 @@ Shorts Непрозрачность мини-плеера Значение непрозрачности в промежутке 0-100, где 0 это прозрачный. Непрозрачность должна быть в диапазоне 0-100. Сброс по умолчанию. - - Кнопки навигации - Настройки кнопок навигации. + + Панель навигации + Настроить компоненты панели навигации. Узкие кнопки навигации Уменьшенное расстояние между кнопками. Обычное расстояние между кнопками. @@ -439,14 +470,21 @@ Shorts Подписи кнопок навигации скрыты. Подписи кнопок навигации отображены. Подмена кнопки \"Создать\" на \"Уведомления\" - "Подмена осуществляется с помощью подмены габаритов устройства. + "Кнопки \"Создать\" и \"Уведомления\" поменяны местами. + +Примечание: Включение опции принудительно скрывает видеорекламу." + Кнопки \"Создать\" и \"Уведомления\" не поменяны местами. + "Отключение этого параметра может привести к загрузке большего количества рекламы с сервера. + +Кроме того, реклама больше не будет блокироваться в Shorts. -• Если подмена кнопки не происходит, перезагрузите устройство. -• Отключение этого параметра загружает больше рекламы со стороны сервера. -• Чтобы видеореклама была видна, следует отключить этот параметр." - Прозрачная панель навигации - Прозрачная панель навигации включена. - Прозрачная панель навигации отключена. +Если эта настройка не вступила в силу, попробуйте перейти в режим инкогнито." + Полупрозрачная панель навигации + Полупрозрачность включена. + Полупрозрачность отключена. + Панель навигации + Панель навигации скрыта. + Панель навигации отображена. Меню настроек Настройка меню YouTube. @@ -568,12 +606,12 @@ Shorts Автовоспроизведение можно изменить в настройках YouTube: Настройки -> Автовоспроизведение -> Автовоспроизведение следующего видео" Рекомендуемое видео в конце воспроизведения отображено. - Увеличение наложения - Увеличение наложения отключено. - Увеличение наложения включено. Задержка автовоспроизведения Задержка автовоспроизведения следующего видео отключена. Задержка автовоспроизведения следующего видео включена. + Увеличение наложения + Увеличение наложения отключено. + Увеличение наложения включено. Кнопки действий под видео Настройка кнопок действий под видео. @@ -1022,30 +1060,6 @@ Shorts Кнопка \"Покупки\" Кнопка \"Покупки\" скрыта. Кнопка \"Покупки\" отображена. - Кнопка \"Магазин\" - Кнопка \"Магазин\" скрыта. - Кнопка \"Магазин\" отображена. - Кнопка \"Особая благодарность\" - Кнопка \"Особая благодарность\" скрыта. - Кнопка \"Особая благодарность\" отображена. - Товары с тегом - Товары с тегом скрыты. - Товары с тегом отображены. - Кнопка \"Местоположение\" - Кнопка \"Местоположение\" скрыта. - Кнопка \"Местоположение\" отображена. - Кнопка \"Сохранить звук в плейлист\" - Кнопка \"Сохранить звук в плейлист\" скрыта. - Кнопка \"Сохранить звук в плейлист\" отображена. - Кнопка \"Подсказки поиска\" - Кнопка \"Подсказки поиска\" скрыта. - Кнопка \"Подсказки поиска\" отображена. - Кнопка \"Использовать этот звук\" - Кнопка \"Использовать этот звук\" скрыта. - Кнопка \"Использовать этот звук\" отображена. - Кнопка \"Использовать шаблон\" - Кнопка \"Использовать шаблон\" скрыта. - Кнопка \"Использовать шаблон\" отображена. Информационные панели Информационные панели скрыты. Информационные панели отображены. @@ -1068,6 +1082,31 @@ Shorts Метка ссылки на полное видео Метка видео ссылки скрыта. Метка видео ссылки отображена. + + Кнопка \"Сохранить звук в плейлист\" + Кнопка \"Сохранить звук в плейлист\" скрыта. + Кнопка \"Сохранить звук в плейлист\" отображена. + Кнопка \"Магазин\" + Кнопка \"Магазин\" скрыта. + Кнопка \"Магазин\" отображена. + Кнопка \"Особая благодарность\" + Кнопка \"Особая благодарность\" скрыта. + Кнопка \"Особая благодарность\" отображена. + Кнопка \"Использовать этот звук\" + Кнопка \"Использовать этот звук\" скрыта. + Кнопка \"Использовать этот звук\" отображена. + Кнопка \"Использовать шаблон\" + Кнопка \"Использовать шаблон\" скрыта. + Кнопка \"Использовать шаблон\" отображена. + Кнопка \"Местоположение\" + Кнопка \"Местоположение\" скрыта. + Кнопка \"Местоположение\" отображена. + Кнопка \"Подсказки поиска\" + Кнопка \"Подсказки поиска\" скрыта. + Кнопка \"Подсказки поиска\" отображена. + Товары с тегом + Товары с тегом скрыты. + Товары с тегом отображены. Кнопки действий Кнопка \"Лайк\" @@ -1119,6 +1158,9 @@ Shorts Панель навигации Панель навигации скрыта. Панель навигации отображена. + Процент высоты отступа + Настраивает высоту отступа, когда панель навигации скрыта, от 0 до 100 (%). + Высота должна быть от 0 до 100 (%). Заменить дескриптор канала Используется имя канала. Используется псевдоним канала. @@ -1223,6 +1265,13 @@ Shorts Уведомление отключено. Подмена размеров устройства "Подменяет размеры устройства, для разблокировки более высокого качества видео, которое может быть недоступно на вашем устройстве." + VP9 кодек + "VP9 кодек отключен. + +• Максимальное разрешение 1080p. +• Воспроизведение видео будет использовать больше сетевых данных, чем с VP9. +• HDR воспроизведения нет, HDR видео все еще использует VP9 кодек." + VP9 кодек включен. Заменить программный кодек AV1 Замена программного кодека AV1 на VP9. Отказ от программного кодека AV1 @@ -1436,6 +1485,7 @@ Shorts Имя пользователя успешно изменено. Ваша репутация <b>%.2f</b> Вы создали <b>%s</b> сегментов + Нажмите здесь для просмотра Ваших сегментов. Список лидеров SponsorBlock Вы избавили людей от <b>%s</b> сегментов Нажмите здесь, чтобы увидеть глобальную статистику и ведущих участников. @@ -1529,9 +1579,9 @@ Shorts TV HTML5 Веб Эффекты от подмены - • Фильмы или платные видео могут не проигрываться. + "• Фильмы или платные видео могут не проигрываться." • Меню \"Звуковая дорожка\" не доступно. - • Меню \"Звуковая дорожка\" VR не доступно. + "• Меню \"Звуковая дорожка\" VR не доступно." • Видео может не воспроизводиться. Принудительно подмена как iOS, AVC (H.264) Видео кодек подмены как iOS - AVC (H.264). @@ -1573,6 +1623,7 @@ AVC (H.264) имеет максимальное разрешение 1080p, и Revancify синяя Revancify красная Иконка YouTube + YouTube (мин. заголовок) По умолчанию Не применён Применён diff --git a/src/main/resources/youtube/translations/tr-rTR/strings.xml b/src/main/resources/youtube/translations/tr-rTR/strings.xml index 17afa69f86..681d8c1815 100644 --- a/src/main/resources/youtube/translations/tr-rTR/strings.xml +++ b/src/main/resources/youtube/translations/tr-rTR/strings.xml @@ -23,13 +23,6 @@ Lütfen web sitesinden %2$s dosyasını indirin." Tam ekran reklamlarını gizle Tam ekran reklamları gizli. Tam ekran reklamları görünür. - Tam ekran reklamlarını kapat - Tam ekran reklamlar Kapat düğmesiyle kapatılır. - "Tam ekran reklamları engellendi. - -Yan etki: Tam ekrandaki topluluk gönderisi resimleri engellenebilir." - Tam ekran reklamlar engellendi. (İletişim Türü: %s) - Tam ekran reklamlar kapatıldı. Genel reklamları gizle Genel reklamlar gizleniyor Genel reklamlar gösteriliyor @@ -39,6 +32,9 @@ Yan etki: Tam ekrandaki topluluk gönderisi resimleri engellenebilir." Ücretli tanıtım etiketini gizle Ücretli tanıtım etiketi gizli. Ücretli tanıtım etiketi gösteriliyor. + Promosyon uyarı afişini gizle + Promosyon uyarı afişi gizli. + Promosyon uyarı afişi gösteriliyor. Kanalın kendi sponsorlu kartlarını gizle Kanalın kendine sponsor kartları gizli. Kanalın kendine sponsor kartları gösteriliyor. @@ -95,8 +91,8 @@ DeArrow hakkında daha fazla bilgi edinmek için buraya dokunun." Albüm kartlarını gizle Albüm kartları gizleniyor Albüm kartları gösteriliyor - "Döner rafı ana sayfa ve keşfet sekmesinden gizler." Atlıkarınca rafını gizle + "Döner rafı ana sayfa ve keşfet sekmesinden gizler." Buna benzer daha çok video çipini gizle Silindirik öneri butonları ve menüleri gizleniyor Silindirik öneri butonları ve menüleri gösteriliyor @@ -236,18 +232,8 @@ Kısıtlamalar: • Videonun altında 'Kullanıcılar da izledi' gibi ibarelerin yer aldığı videolar." Az izlenen videoları gizle Abone olunmayan kanallardan yüklenen ana sayfa yayınlarında 1.000\'den az izlenen videoları gizleyin. - - Süre filtresi - Süresine göre videoları gizleyin - Süresi daha kısa veya daha uzun olan videoları gizleyin.\n\nBilinen problem: Oynatıcı ile bağlantılı videoları gizlemez, bunun yerine zaman damgasını gizler. - Bu süreden daha uzun - Süresi bu sayıdan daha uzun olan videolar gizlenecektir. - Bu süreden daha kısa - Süresi bu sayıdan daha kısa olan videolar gizlenecektir. İzlenme sayısı filtresi - Önerilen videoları izlenmeye göre gizle - Belirtilen görüntüleme sayısından daha az olan önerilen videoları gizleyin.\n\nBilinen sorun: 0 izleme alan videolar filtrelenmez. Görüntülemelerden büyük Görüntüleme sayısı bu sayıdan fazla olan videolar gizlenecektir. Görüntülemelerden az @@ -274,7 +260,6 @@ Kısıtlamalar: Abonelikler Trendler Daha sonra izlenecekler - Başlangıç sayfası geçersiz, varsayılana dönülüyor. Ses parçalarının kendiliğinden açılmasını kapat Ses parçalarının kendiliğinden açılması kapalı. Ses parçalarının kendiliğinden açılması etkin. @@ -340,6 +325,13 @@ Daha sonra kapatılırsa kullanıcı arayüzü hatalarını önlemek için uygul Yeni satırlarla ayrılmış olarak hangi bileşenlerin filtreleneceğini yapılandırın. Geçersiz özel filtre: %s + + + Video indirme butonunu geçersiz kıl + İndirme düğmesi harici indiricinizi açar. + Oynatma listesi indirme butonunu geçersiz kıl + Oynatma listesi indirici paket ismi + Miniplayer Uygulama içi simge durumuna küçültülmüş oynatıcının stilini değiştirin. @@ -371,9 +363,7 @@ Daha sonra kapatılırsa kullanıcı arayüzü hatalarını önlemek için uygul Kaplama opaklığı 0-100 arası opaklık değeri, 0 şeffaftır. Mini oynatıcının siyah arkaplan opaklığı 0 ila 100 arasında olmalıdır. Varsayılan değere sıfırlandı. - - Navigasyon tuşları - Gezinme çubuğu bölümü bileşenlerini gizleyin veya gösterin. + Dar gezinme düğmelerini etkinleştir Gezinme düğmeleri arasındaki boşluk daralır. Gezinme düğmeleri arasındaki boşluk daralır. @@ -399,11 +389,6 @@ Daha sonra kapatılırsa kullanıcı arayüzü hatalarını önlemek için uygul Alt gezinme çubuğundaki butonların yazıları gizleniyor. Alt gezinme çubuğundaki butonların yazıları gizlenmiyor. Oluştur düğmesini Bildirimler düğmesi ile yer değiş - "Cihazın bilgilerini taklit ederek oluştur düğmesinin ve bildirim düğmesinin konumlarını değiştirin. - -• Bu ayarı değiştirseniz bile, cihaz yeniden başlatılıncaya kadar geçerli olmayabilir. -• Bu ayarın devre dışı bırakılması, sunucu tarafından daha fazla reklamın yüklenmesine neden olur. -• Video reklamların görünür olması için bu ayarı devre dışı bırakmalısınız." Yarı saydam gezinme çubuğunu etkinleştir Gezinme çubuğu yarı saydam. Gezinme çubuğu opaktır. @@ -519,12 +504,12 @@ Not: Bu özelliği devre dışı bırakmak, eski arayüzdeki \"Videoyu sarmak i Otomatik oynatma YouTube ayarlarından değiştirilebilir: 'Ayarlar → Otomatik oynat → Sonraki videoyu otomatik oynat'" Önerilen video bitiş ekranı gizlenmiyor. - Yakınlaştırma arayüzünü gizle - Yakınlaştırma arayüzü gizli. - Yakınlaştırma arayüzü gizlenmiyor. Otomatik oynatma geri sayımını atla Otomatik oynatma açıksa sonraki video geri sayım olmadan oynatılır. Otomatik oynatma açıksa sonraki video geri sayım bitmeden oynatılır. + Yakınlaştırma arayüzünü gizle + Yakınlaştırma arayüzü gizli. + Yakınlaştırma arayüzü gizlenmiyor. Eylem düğmeleri Videonun altındaki aksiyon düğmeleri gizle veya göster. @@ -647,6 +632,9 @@ Otomatik oynatma YouTube ayarlarından değiştirilebilir: \"Bildir\" butonu gösteriliyor. Ek ayarlar + Ambiyans mod menüsünü gizle + Ambiyans modu menüsü gizli. + Ambiyans modu menüsü görünür. Yardım & geri bildirim menüsünü gizle Yardım & geri bildirim menüsü gizleniyor. Yardım & geri bildirim menüsü gösteriliyor. @@ -662,6 +650,9 @@ Otomatik oynatma YouTube ayarlarından değiştirilebilir: Premium kontrolleri menüsünü gizle Premium kontrolleri menüsü gizli. Premium kontrolleri menüsü gizlenmiyor. + Uyku zamanlayıcısı menüsünü gizle + Uyku zamanlayıcısı menüsü gizli. + Uyku zamanlayıcısı menüsü görünür. \"Sabit ses\" butonunu gizle \"Sabit ses\" butonu gösteriliyor. \"Sabit ses\" butonu gizleniyor. @@ -912,6 +903,7 @@ Yan etki: Kahire teması bildirim noktalarına da uygulanır." Shorts oynatıcıya devam edilmesini devre dışı bırak Shorts oynatıcı açılışta devam etmeyecek Shorts oynatıcı açılışta devam edecek + Butonları gizle Shorts rafları Shorts raflarını gizle @@ -954,18 +946,11 @@ Bilinen sorun: Arama sonuçlarındaki resmi başlıklar da gizlenebiliyor."Ücretli tanıtım etiketini gizle Ücretli tanıtım etiketi gizli. Ücretli tanıtım etiketi gösteriliyor. - Mağaza düğmesini gizle - Mağaza düğmesi gizli. - Mağaza düğmesi görünür. - Süper Teşekkürler butonunu gizle - Süper Teşekkürler düğmesi gizli. - Süper Teşekkürler düğmesi gizlenmiyor. - Etiketli ürünleri gizle - Etiketli ürünler gizli. - Etiketli ürünler görünür. - Sesi oynatma listesine kaydetme butonunu gizle - Sesi oynatma listesine kaydetme butonu gizleniyor - Sesi oynatma listesine kaydetme butonu gösteriliyor. + Trends butonunu gizle + Trend butonu gizli. + Trend butonu görünür. + Alışveriş butonunu gizle + Alışveriş butonu gizli. Bilgi panellerini gizle Bilgi panelleri gizleniyor. Bilgi panelleri gizlenmiyor. @@ -984,6 +969,19 @@ Bilinen sorun: Arama sonuçlarındaki resmi başlıklar da gizlenebiliyor."Tüm video bağlantısı etiketini gizle Video bağlantı etiketi gizlendi. Video bağlantı etiketi gösteriliyor + + Sesi oynatma listesine kaydetme butonunu gizle + Sesi oynatma listesine kaydetme butonu gizleniyor + Sesi oynatma listesine kaydetme butonu gösteriliyor. + Mağaza düğmesini gizle + Mağaza düğmesi gizli. + Mağaza düğmesi görünür. + Süper Teşekkürler butonunu gizle + Süper Teşekkürler düğmesi gizli. + Süper Teşekkürler düğmesi gizlenmiyor. + Etiketli ürünleri gizle + Etiketli ürünler gizli. + Etiketli ürünler görünür. Eylem düğmeleri Beğen butonunu gizle diff --git a/src/main/resources/youtube/translations/uk-rUA/strings.xml b/src/main/resources/youtube/translations/uk-rUA/strings.xml index 730e6f7f96..adb0bee6b6 100644 --- a/src/main/resources/youtube/translations/uk-rUA/strings.xml +++ b/src/main/resources/youtube/translations/uk-rUA/strings.xml @@ -6,6 +6,7 @@ Розширені Пошук %s + Скинуто. Експериментальні опції Бажаєте продовжити? Перезапустіть, щоб нормально завантажився макет @@ -23,13 +24,6 @@ Приховати повноекранну рекламу Повноекранну рекламу приховано. Повноекранну рекламу показується. - Закривати повноекранну рекламу - Повноекранну рекламу закривається за допомогою кнопки Закрити. - "Повноекранну рекламу блокується. - -Застереження: Зображення публікації спільноти в повноекранному режимі може бути блоковано." - Повноекранну рекламу заблоковано. (Тип діалогу: %s) - Повноекранну рекламу закрито. Приховати загальну рекламу Загальну рекламу приховано. Загальну рекламу показується. @@ -77,6 +71,7 @@ Тост не показується, якщо DeArrow не доступний. Кінцева точка API DeArrow URL кінцевої точки кешу мініатюр DeArrow + Некоректна URL API DeArrow. Кадри з відео Кадри береться з початку/середини/кінця кожного відео. Ці зображення вбудовані в YouTube і зовнішній API не використовується. Використовувати неточні кадри @@ -98,6 +93,7 @@ Приховати картки альбому Картки альбомів приховано. Картки альбому показується. + Приховати карусельну полицю "Приховати наступні полиці: • Важливі новини • Продовжити перегляд @@ -105,13 +101,15 @@ • Прослухати знову • Покупки • Переглянути ще раз" - Приховати карусельну полицю Приховати полицю фішок Полицю фішок приховано Полицю фішок показується Приховати розширювану фішку під відео Розширювані фішки приховано. Розширювані фішки показується. + Приховати висувні полиці + Висувні полиці приховано. + Висувні полиці показується. Приховати кнопку субтитрів в стрічці Кнопку субтитрів приховано. Кнопку субтитрів показується. @@ -252,18 +250,21 @@ • Відео з фразами на кшталт 'Людей також дивилися' внизу." Приховати відео з малою кількістю переглядів Приховати відео з менш ніж 1,000 переглядів з головної стрічки, вантажені з каналів, на які не підписані. - - Фільтр тривалості - Приховати відео за тривалістю - Приховати відео з меншою або більшою тривалістю.\n\nВідома проблема: Не приховуватимуться пов\'язані відео у плеєрі, натомість приховуватиметься мітку часу. - Довші за тривалістю - Відео з тривалістю більше цього числа приховуватимуться. - Коротші за тривалістю - Відео з тривалістю менше цього числа приховуватимуться. + Приховати відео НЕЗАБАРОМ + "Приховати відео з міткою НЕЗАБАРОМ. + +Примітка: Увімкнення також приховає кнопку Сповістити." Фільтр за кількістю переглядів - Приховати рекомендовані відео за переглядами - Приховати рекомендовані відео з меншою за вказану кількість переглядів.\n\nВідома проблема: Відео з 0 переглядами не фільтруються. + Приховати відео на головній за переглядами + Відео у головній стрічці фільтруються. + Відео у головній стрічці не фільтруються. + Приховати результати пошуку за переглядами + Результати пошуку фільтрується. + Результати пошуку не фільтрується. + Приховати відео підписок за переглядами + Відео у стрічці підписок фільтруються. + Відео у стрічці підписок не фільтруються. Більші за переглядами Відео з більше ніж ця кількість переглядів приховуватимуться. Менші за переглядами @@ -271,6 +272,18 @@ Ключі переглядів Вкажіть шаблон вашою мовою для кількості переглядів як показується під будь-яким відео в інтерфейсі користувача. Будь-який ключ (літера/слово на вашій мові) -> значення (значення ключа) повинно бути з нового рядка. Ключі йдуть перед позначкою \"->\". Якщо змінили мову програми або системи, потрібно скинути це налаштування.\n\nЗразок:\nАнглійською: 10K views = K -> 1000, views -> views\nУкраїнською: 10 тис. переглядів = тис. -> 1000, переглядів -> views тис. -> 1 000\nмлн -> 1 000 000\nмлрд -> 1 000 000 000\nпереглядів -> views + Про фільтрацію за переглядами + "Головна/Підписки/Результати пошуку фільтрується, щоб приховати відео з переглядами менше або більше вказаної кількості. + +Застереження: +• YouTube Shorts неможливо приховати. +• Відео з 0 переглядів не фільтруються." + Приховати пов’язані відео + Пов\'язані відео приховано. + Пов\'язані відео показується. + "Це налаштування обмежує максимальну кількість, які можуть вантажитися на екран плеєра. + +Якщо екран плеєра змінюється через зміни на стороні сервера, можуть бути приховані ненавмисно на екрані плеєра." Загальне Змінити початкову сторінку @@ -290,7 +303,11 @@ Підписки Тенденції Переглянути пізніше - Недійсна початкова сторінка, скидається на стандартну. + Тип зміни початкової сторінки + "Початкова сторінка змінюється завжди. + +Застереження: Кнопка Назад на панелі інструментів не працює." + Початкова сторінка змінюється лише один раз. Вимкнути примусові автоматичні звукові доріжки Примусові автоматичні звукові доріжки вимкнено. Примусові автоматичні звукові доріжки увімкнено. @@ -319,14 +336,6 @@ Підміняє DPI для використання інтерфейсу деяких телефонів Увімкнути Планшетний інтерфейс Підміняє DPI для використання інтерфейсу деяких планшетів - Перевизначити кнопку завантаження відео - Кнопка завантаження відео відкриває зовнішній завантажувач. - Кнопка завантаження відео відкриває вбудований завантажувач. - Перевизначити кнопку завантаження списку відтворення - Кнопка вбудованого завантаження списку відтворення завжди показується, і в публічних списках відтворення відкриває зовнішній завантажувач. - Кнопка вбудованого завантаження списку відтворення відкриває вбудований завантажувач, якщо показується. - Ім\'я пакета завантажувача списку відтворення - Ім\'я пакета встановленого зовнішнього завантажувача, наприклад YTDLnis. Підробити версію програми Версію підроблено Версію не підроблено @@ -366,6 +375,30 @@ Список рядків конструктора шляхів компонентів для фільтрування, розділених новим рядком Недопустимий користувацький фільтр: %s + + Заміна кнопок + Перевизначення дії натискання кнопок додатка. + + Кнопка завантаження + Перевизначити кнопку завантаження відео + Кнопка завантаження відео відкриває зовнішній завантажувач. + Кнопка завантаження відео відкриває вбудований завантажувач. + Перевизначити кнопку завантаження списку відтворення + Кнопка вбудованого завантаження списку відтворення завжди показується, і в публічних списках відтворення відкриває зовнішній завантажувач. + Кнопка вбудованого завантаження списку відтворення відкриває вбудований завантажувач, якщо показується. + Ім\'я пакета завантажувача списку відтворення + Ім\'я пакета встановленого зовнішнього завантажувача, наприклад YTDLnis. + + Перевизначити кнопку YouTube Music + Кнопка YouTube Music відкриває RVX Music. + Кнопка YouTube Music відкриває стандартний додаток. + Назва пакету RVX Music + Назва пакету встановленого RVX Music. + RVX Music + Зауваження + %s не встановлено. Будь ласка, встановіть. + Передумова + Для перевизначення дії кнопки потрібно YouTube Music. Натисніть тут, щоб завантажити YouTube Music. Мініплеєр Зміна стилю мінімізованого плеєра в додатку. @@ -396,10 +429,10 @@ Перемотування вперед та назад показується. Непрозорість затемнення Значення непрозорості в межах 0-100, де 0 це прозоро. - Непрозорість затемнення мініплеєра має бути в межах 0-100. Скинуто. - - Кнопки панелі навігації - Приховувати чи показувати секцію компонентів панелі навігації. + Непрозорість затемнення мініплеєра має бути в межах 0-100. + + Панель навігації + Приховувати чи показувати секцію компонентів панелі навігації. Увімкнути вузькі кнопки навігації Відстань між кнопками навігації зменшується. Відстань між кнопками навігації не зменшується. @@ -425,14 +458,21 @@ Навігаційну мітку приховано. Навігаційну мітку показується. Поміняти Створити зі Сповіщення - "Поміняти місцями кнопку створення та кнопку сповіщення підробленням інформації про пристрій. + "Кнопку Створити міняється з кнопкою Сповіщення. -• Навіть якщо ви зміните це налаштування, воно може не набути чинності, доки ви не перезапустите пристрій. -• Вимкнувши це налаштування, вантажиться більше реклами з боку сервера. -• Ви повинні вимкнути це налаштування, щоб зробити відеорекламу видимою." +Примітка: Увімкнення також примусово приховує відеорекламу." + Кнопку Створити не міняється з кнопкою Сповіщення. + "Вимкнення може спричинити вантаження більше реклами з сервера. + +А також, рекламу більше не блокуватиметься в Shorts. + +Якщо це налаштування не діє, спробуйте перемкнути Анонімний режим." Увімкнути напівпрозорість панелі навігації Панель навігації напівпрозора. Панель навігації непрозора. + Приховати панель навігації + Панель навігації приховано. + Панель навігації показується. Меню налаштувань Приховати елементи в меню налаштувань YouTube. @@ -489,7 +529,7 @@ Плеєр Користувацька непрозорість затемнення плеєра Значення непрозорості в межах 0-100, де 0 це прозорий - Непрозорість затемнення плеєра має бути в межах 0-100. Скинуто. + Непрозорість затемнення плеєра має бути в межах 0-100. Вимкнути висувні панелі плеєра Автовисувні панелі плеєра вимкнено. Автовисувні панелі плеєра увімкнено. @@ -508,7 +548,7 @@ • Це налаштування не вмикає примусове накладання швидкості." Значення накладання швидкості Значення накладання швидкості в межах 0-8.0. - Значення накладання швидкості повинно бути в межах 0-8.0. Скинуто. + Значення накладання швидкості повинно бути в межах 0-8.0. Приховати водяний знак каналу Водяний знак приховано. Водяний знак показується. @@ -554,12 +594,12 @@ Автовідтворення можна змінити у налаштуваннях YouTube: 'Налаштування → Автоматичне відтворення → Автовідтворення наступного відео'" Кінцевий екран з пропонованими відео показується. - Приховати накладання при масштабуванні - Накладання при масштабуванні приховано. - Накладання при масштабуванні показується. Пропустити відлік автовідтворення Якщо автовідтворення увімкнено, наступне відео відтворюватиметься без відліку. Якщо автовідтворення увімкнено, наступне відео відтворюватиметься після закінчення відліку. + Приховати накладання при масштабуванні + Накладання при масштабуванні приховано. + Накладання при масштабуванні показується. Кнопки дії Приховувати чи показувати кнопки дії під відео. @@ -765,7 +805,7 @@ Кнопку Поділитися показується. Верхній відступ швидких дій Налаштуйте відстань від панелі прогресу до контейнера швидких дій, діапазон 0-32. - Верхній відступ швидких дій повинен бути в межах 0-32. Скинуто. + Верхній відступ швидких дій повинен бути в межах 0-32. Вимкнути ландшафтний режим Орієнтація відео портретна в повноекранному режимі. @@ -1006,30 +1046,6 @@ Приховати кнопку Магазин Кнопку Магазин приховано. Кнопку Магазин показується. - Приховати кнопку Магазин - Кнопку Магазин приховано. - Кнопка Магазин показується. - Приховати кнопку супер подяки - Кнопку супер подяки приховано. - Кнопку супер подяки показується. - Приховати товари з тегами - Товари з тегами приховано. - Товари з тегами показується. - Приховати кнопку місцезнаходження - Кнопку місцезнаходження приховано. - Кнопку місцезнаходження показується. - Приховати кнопку Зберегти звук в списку відтворення - Зберегти звук в списку відтворення приховано. - Зберегти звук в списку відтворення показується. - Приховати кнопку пропозицій пошуку - Кнопку пропозицій пошуку приховано. - Кнопку пропозицій пошуку показується. - Приховати кнопку Використати цей звук - Кнопку Використати цей звук приховано. - Кнопку Використати цей звук показується. - Приховати кнопку Використати шаблон - Кнопку Використати шаблон приховано. - Кнопку Використати шаблон показується. Приховати інформаційні панелі Інформаційні панелі приховано. Інформаційні панелі показується. @@ -1048,6 +1064,35 @@ Приховати мітку посилання на повне відео Мітку посилання відео приховано. Мітку посилання відео показується. + + Пропоновані дії + Приховати кнопку Зелений екран + Кнопку Зелений екран приховано. + Кнопку Зелений екран показується. + Приховати кнопку Зберегти звук + Кнопку Зберегти звук приховано. + Кнопку Зберегти звук показується. + Приховати кнопку Магазин + Кнопку Магазин приховано. + Кнопка Магазин показується. + Приховати кнопку супер подяки + Кнопку супер подяки приховано. + Кнопку супер подяки показується. + Приховати кнопку Використати цей звук + Кнопку Використати цей звук приховано. + Кнопку Використати цей звук показується. + Приховати кнопку Використати шаблон + Кнопку Використати шаблон приховано. + Кнопку Використати шаблон показується. + Приховати кнопку місцезнаходження + Кнопку місцезнаходження приховано. + Кнопку місцезнаходження показується. + Приховати кнопку пропозицій пошуку + Кнопку пропозицій пошуку приховано. + Кнопку пропозицій пошуку показується. + Приховати товари з тегами + Товари з тегами приховано. + Товари з тегами показується. Кнопки дії Приховати кнопку Подобається @@ -1070,6 +1115,9 @@ Кнопку Зі звуком показується. Анімація / Відгук + Вимкнути анімацію кнопки Подобається + Фонтанну анімацію вимкнено над кнопкою Подобається. + Фонтанну анімацію увімкнено над кнопкою Подобається. Приховати фон кнопки Відтворити - Призупинити Фон кнопки приховано. Фон кнопки показується. @@ -1092,13 +1140,16 @@ Натисніть і утримуйте мітку часу для зміни станів повторення Shorts. Нижній відступ метапанелі Налаштуйте відстань від панелі прогресу до метапанелі, діапазон 0-64. - Нижній відступ метапанелі повинен бути в межах 0-64. Скинуто. + Нижній відступ метапанелі повинен бути в межах 0-64. Приховати панель інструментів Панель інструментів приховано. Панель інструментів показується. Приховати панель навігації Панель навігації приховано. Панель навігації показується. + Відсоток висоти порожнього простору + Налаштуйте відсоток висоти порожнього простору, що залишається, коли приховано панель навігації, між 0 і 100 (%). + Відсоток висоти повинен бути в межах 0-100 (%). Замінити ідентифікатор каналу Використовується назву каналу. Використовується ідентифікатор каналу. @@ -1133,7 +1184,7 @@ Розмір шрифту в панелі при жесті Розмір екрана накладки проведення Відсоток площі екрана для проведення.\n\nЗауваження: це також змінить розмір площі екрану для жесту перемотування подвійним натисканням. - Розмір площі для проведення не може бути більшим ніж 50. Скинуто. + Розмір площі для проведення не може бути більшим ніж 50. Час показу панелі Скільки мілісекунд панель буде показуватися Вимкнути авто яскравість HDR @@ -1201,6 +1252,13 @@ Підробити розміри пристрою "Підробка розмірів пристрою до максимального значення. Високу якість може бути розблоковано для деяких відео, які вимагають великих розмірів пристрою, але не для всіх відео." + Вимкнути кодек VP9 + "Кодек VP9 вимкнено. + +• Максимальна роздільна здатність - 1080p. +• Відтворення відео використовуватиме більше інтернет даних ніж VP9. +• Кодек VP9 все ще використовується для HDR відео." + Кодек VP9 увімкнено. Замінити програмний кодек AV1 Замінити програмний кодек AV1 кодеком VP9. Відкинути відповідь програмного кодека AV1 @@ -1211,8 +1269,8 @@ Зміна типової якості при мобільному з\'єднанні на %s. Не вдалося встановити якість відео. Зміна типової якості при Wi-Fi на %s. - Користувацькі швидкості повинні бути менше ніж %s Використовуються типові значення. - Неправильні користувацькі швидкості відтворення. Використовуються типові значення. + Користувацькі швидкості повинні бути менше ніж %sx. + Неправильні користувацькі швидкості відтворення. Повернення Дизлайків Ввімкнути повернення дизлайків YouTube @@ -1413,6 +1471,7 @@ Ім\'я користувача успішно змінено Ваша репутація — <b>%.2f</b> Ви створили <b>%s</b> сегментів + Натисніть тут для перегляду Ваших сегментів. Таблиця лідерів Спонсорблок Ви врятували людей від <b>%s</b> сегментів Натисніть тут, щоб побачити глобальну статистику та найкращих учасників @@ -1499,9 +1558,13 @@ TV HTML5 Web Побічні ефекти імітування - • Фільми чи платні відео можуть не відтворюватися. + "• Фільми чи платні відео можуть не відтворюватися. +• Прямі трансляції починаються з початку. +• Відео можуть закінчуватися на 1 секунду раніше. +• Немає аудіокодека opus." • Меню звукової доріжки відсутнє. - • Меню звукової доріжки відсутнє. + "• Меню звукової доріжки відсутнє. +• Стабілізація гучності недоступна." • Відео може не відтворюватися. Примусово AVC (H.264) iOS AVC (H.264) кодек відео iOS. @@ -1543,6 +1606,7 @@ AVC (H.264) має максимальну роздільну здатність Revancify синя Revancify червона YouTube + YouTube (Мінімальний заголовок) Стандартна Виключено Включено diff --git a/src/main/resources/youtube/translations/vi-rVN/strings.xml b/src/main/resources/youtube/translations/vi-rVN/strings.xml index 3bbeef61a8..16d550d5ce 100644 --- a/src/main/resources/youtube/translations/vi-rVN/strings.xml +++ b/src/main/resources/youtube/translations/vi-rVN/strings.xml @@ -6,12 +6,13 @@ ReVanced Extended Tìm kiếm %s + Đặt lại về giá trị mặc định. Tính năng thử nghiệm Bạn có muốn tiếp tục không? Vui lòng khởi động lại ứng dụng trong lần đầu khởi chạy để các tính năng hoạt động bình thường Làm mới và khởi động lại Bình thường - Tên gói ứng dụng trình tải xuống video + Tên gói trình tải xuống video Nhập tên gói ứng dụng trình tải xuống đã cài đặt trên thiết bị của bạn, chẳng hạn như NewPipe hoặc YTDLnis. Trình tải xuống bên ngoài Cảnh báo @@ -23,13 +24,6 @@ Ẩn quảng cáo toàn màn hình Quảng cáo toàn màn hình đã ẩn. Quảng cáo toàn màn hình được hiển thị. - Đóng quảng cáo toàn màn hình - Đóng quảng cáo toàn màn hình bằng nút Đóng. - "Quảng cáo toàn màn hình bị chặn. - -Hạn chế: Hình ảnh của bài đăng cộng đồng ở chế độ toàn màn hình có thể bị chặn." - Quảng cáo toàn màn hình đã bị chặn. (Loại hộp thoại: %s) - Quảng cáo toàn màn hình đã bị đóng. Ẩn quảng cáo chung Quảng cáo chung đã ẩn. Quảng cáo chung được hiển thị. @@ -79,6 +73,7 @@ Nhấn vào đây để tìm hiểu thêm về DeArrow." Thông báo ngắn nếu API DeArrow không khả dụng đã tắt. Điểm cuối API DeArrow URL điểm cuối của bộ nhớ đệm hình thu nhỏ DeArrow. Không thay đổi URL này trừ khi bạn biết mình đang làm gì. + URL của API DeArrow không hợp lệ. Giới thiệu về Hình thu nhỏ tự động Hình thu nhỏ tự động là ảnh tĩnh ở đầu, giữa hoặc cuối video, được tạo tự động bởi YouTube và không sử dụng bất kỳ API bên ngoài nào. Hình thu nhỏ nhanh @@ -100,20 +95,23 @@ Nhấn vào đây để tìm hiểu thêm về DeArrow." Ẩn Đĩa nhạc Đĩa nhạc đã ẩn khỏi kết quả tìm kiếm. Đĩa nhạc được hiển thị trong kết quả tìm kiếm. + Ẩn các kệ được cá nhân hoá "Ẩn các kệ sau: - • Tin nóng + • Tin nổi bật • Tiếp tục xem • Khám phá thêm kênh • Nghe lại • Mua sắm • Xem lại" - Ẩn các kệ được cá nhân hoá Ẩn kệ danh mục được đề xuất Kệ danh mục được đề xuất đã ẩn. Kệ danh mục được đề xuất được hiển thị. Ẩn bảng giới thiệu mở rộng Bảng giới thiệu mở rộng đã ẩn bên dưới video. Bảng giới thiệu mở rộng được hiển thị bên dưới video. + Ẩn kệ Mở rộng + Kệ Mở rộng đã ẩn. + Kệ Mở rộng đã hiển thị. Ẩn nút Phụ đề Nút Phụ đề đã ẩn. Nút Phụ đề được hiển thị. @@ -132,9 +130,9 @@ Nhấn vào đây để tìm hiểu thêm về DeArrow." Ẩn nút Video mới nhất Nút Video mới nhất đã ẩn. Nút Video mới nhất được hiển thị. - Ẩn Danh sách kết hợp - Danh sách kết hợp đã ẩn. - Danh sách kết hợp được hiển thị. + Ẩn Danh sách phát kết hợp + Danh sách phát kết hợp đã ẩn. + Danh sách phát kết hợp được hiển thị. Ẩn phim và chương trình truyền hình Phim và chương trình truyền hình đã ẩn. Phim và chương trình truyền hình được hiển thị. @@ -173,7 +171,7 @@ Nhấn vào đây để tìm hiểu thêm về DeArrow." Bộ lọc thẻ trên kênh đã tắt. Chỉnh sửa bộ lọc Nhập tên các thẻ trên kênh mà bạn muốn lọc được phân cách bằng dòng. - "Shorts + "Video ngắn Danh sách phát Cửa hàng" Ẩn nút Chuyển đến cửa hàng @@ -202,29 +200,29 @@ Cửa hàng" Bài đăng cộng đồng được hiển thị trên thẻ Kênh đăng ký. Trình đơn tuỳ chọn - Ẩn hoặc hiển thị thành phần của trình đơn tuỳ chọn trong bảng tin. + Ẩn hoặc hiển thị thành phần của trình đơn tuỳ chọn trên bảng tin. Bật bộ lọc trình đơn tuỳ chọn trên bảng tin Bộ lọc trình đơn tuỳ chọn trên bảng tin đã bật. Bộ lọc trình đơn tuỳ chọn trên bảng tin đã tắt. Chỉnh sửa bộ lọc - Nhập tên các mục thành phần của trình đơn tuỳ chọn mà bạn muốn lọc được phân cách bằng dòng. + Nhập tên các mục thành phần của mục tuỳ chọn mà bạn muốn lọc được phân cách bằng dòng. Bộ lọc video - Ẩn video theo từ khoá hoặc lượt xem. + Ẩn video theo từ khoá hoặc số lượt xem. Bộ lọc từ khoá Ẩn video trên thẻ Trang chủ theo từ khoá - Các Video trên thẻ Trang chủ được lọc theo từ khoá đã đặt. - Các Video trên thẻ Trang chủ không lọc theo từ khoá đã đặt. + Các Video trên thẻ Trang chủ đã được lọc theo từ khoá đã đặt. + Các Video trên thẻ Trang chủ không được lọc theo từ khoá đã đặt. Ẩn kết quả tìm kiếm theo từ khóa - Kết quả tìm kiếm được lọc theo từ khoá đã đặt. - Kết quả tìm kiếm không lọc theo từ khoá đã đặt. + Kết quả tìm kiếm đã được lọc theo từ khoá đã đặt. + Kết quả tìm kiếm không được lọc theo từ khoá đã đặt. Ẩn video trên thẻ Kênh đăng ký theo từ khoá - Các Video trên thẻ Kênh đăng ký được lọc theo từ khoá đã đặt. - Các Video trên thẻ Kênh đăng ký không lọc theo từ khoá đã đặt. + Các Video trên thẻ Kênh đăng ký đã được lọc theo từ khoá đã đặt. + Các Video trên thẻ Kênh đăng ký không được lọc theo từ khoá đã đặt. Ẩn bình luận theo từ khoá - Bình luận được lọc theo từ khoá đã đặt. - Bình luận không lọc theo từ khoá đã đặt. + Bình luận đã được lọc theo từ khoá đã đặt. + Bình luận không được lọc theo từ khoá đã đặt. Bộ lọc từ khoá "Nhập từ hoặc cụm từ cần ẩn được phân cách bằng dòng. @@ -235,7 +233,7 @@ Bộ lọc có phân biệt chữ hoa chữ thường, vì vậy bạn cần nh "Nội dung khớp với từ khoá bạn đã đặt sẽ bị ẩn trên thẻ Trang chủ/Kênh đăng ký và kết quả tìm kiếm. Hạn chế: -• Video Shorts không bị ẩn theo tên kênh. +• Video ngắn sẽ không bị ẩn theo tên kênh. • Một số thành phần giao diện người dùng có thể không bị ẩn. • Tìm kiếm từ khoá có thể không cho kết quả nào." Khớp toàn bộ từ @@ -254,18 +252,21 @@ Bộ lọc có phân biệt chữ hoa chữ thường, vì vậy bạn cần nh • Video có cụm từ như \"Mọi người cũng xem video này\" ở bên dưới hình thu nhỏ." Ẩn video có lượt xem thấp Ẩn các Video có dưới 1.000 lượt xem từ các kênh chưa đăng ký khỏi thẻ Trang chủ. - - Bộ lọc thời lượng - Ẩn video theo thời lượng - Ẩn các video có thời lượng ngắn hơn hoặc dài hơn thời lượng bạn đã đặt.\n\nSự cố đã biết: Tính năng này sẽ không ẩn video có liên quan trong trình phát mà chỉ ẩn mốc thời gian. - Dài hơn - Nhập thời lượng. Video có thời lượng dài hơn mức này sẽ bị ẩn. - Ngắn hơn - Nhập thời lượng. Video có thời lượng ngắn hơn mức này sẽ bị ẩn. + Ẩn video SẮP DIỄN RA + "Ẩn các video có nhãn \"SẮP DIỄN RA\". + +Lưu ý: Việc bật tính năng này cũng sẽ ẩn nút Thông báo cho tôi." Bộ lọc số lượt xem - Ẩn video được đề xuất theo số lượt xem - Ẩn các video đề xuất có số lượt xem ít hơn số lượt xem bạn đã đặt.\n\nSự cố đã biết: Các video chưa có lượt xem nào sẽ không bị lọc. + Ẩn video trên thẻ Trang chủ theo số lượt xem + Các Video trên thẻ Trang chủ đã được lọc theo số lượt xem đã đặt. + Các Video trên thẻ Trang chủ không được lọc theo số lượt xem đã đặt. + Ẩn kết quả tìm kiếm theo lượt xem + Kết quả tìm kiếm đã được lọc theo số lượt xem đã đặt. + Kết quả tìm kiếm không được lọc theo số lượt xem đã đặt. + Ẩn video trên thẻ Kênh đăng ký theo số lượt xem + Các Video trên thẻ Kênh đăng ký đã được lọc theo số lượt xem đã đặt. + Các Video trên thẻ Kênh đăng ký không được lọc theo số lượt xem đã đặt. Cao hơn Nhập số lượt xem. Video có số lượt xem cao hơn mức này sẽ bị ẩn. Thấp hơn @@ -273,6 +274,19 @@ Bộ lọc có phân biệt chữ hoa chữ thường, vì vậy bạn cần nh Ký tự đại diện số lượt xem Nhập kí tự đại diện cho số lượt xem được hiển thị bên dưới video theo mẫu ngôn ngữ của bạn. Mỗi kí tự đại diện -> Số lượt xem tương ứng và được phân cách bằng dòng. Nếu bạn thay đổi ngôn ngữ ứng dụng hoặc hệ thống, bạn cần phải đặt lại tuỳ chọn này.\n\nVí dụ:\n•Tiếng Việt: 10 N lượt xem = N -> 1000, lượt xem -> lượt xem.\n•Tiếng Anh: 10K views = K -> 1000, views -> lượt xem. N -> 1 000\nTr -> 1 000 000\nT -> 1 000 000 000\nlượt xem -> lượt xem + Về việc lọc theo số lượt xem + "Các Video có số lượt xem ít hoặc nhiều hơn con số bạn đã đặt sẽ bị ẩn trên thẻ Trang chủ/Kênh đăng ký/Kết quả tìm kiếm. + +Hạn chế: +• Không ẩn đối với Video ngắn. +• Các Video có 0 lượt xem cũng không bị lọc." + Ẩn các video có liên quan + Các video có liên quan đã bị ẩn. + Nút Video có liên quan được hiển thị. + "Cài đặt này giới hạn số lượng bố cục tối đa có thể được tải trên màn hình trình phát. + +Nếu bố cục của màn hình trình phát thay đổi do các thay đổi từ phía máy chủ, các bố cục không mong muốn có thể bị ẩn trên màn hình trình phát." + Độ lệch Tổng quan Thay đổi trang khởi động @@ -292,7 +306,11 @@ Bộ lọc có phân biệt chữ hoa chữ thường, vì vậy bạn cần nh Kênh đăng ký Thịnh hành Xem sau - Trang khởi động không hợp lệ. Đã đặt lại về mặc định. + Thay đổi trang khởi động + "Trang khởi động sẽ liên tục thay đổi. + +Hạn chế: Nút Quay lại trên thanh công cụ có thể không hoạt động được." + Trang khởi động chỉ thay đổi một lần. Tắt buộc tự động phát bản âm thanh Buộc tự động phát bản âm thanh đã bị tắt. Buộc tự động phát bản âm thanh đã được bật. @@ -321,14 +339,6 @@ Tuỳ chọn này chỉ tự động chấp nhận hộp thoại cảnh báo, kh Thay đổi DPI để sử dụng một số bố cục điện thoại. Bố cục máy tính bảng Thay đổi DPI để sử dụng một số bố cục máy tính bảng. - Ghi đè nút tải xuống video - Nút tải xuống video sẽ mở trình tải xuống bên ngoài của bạn. - Nút tải xuống video sẽ mở trình tải xuống gốc trong ứng dụng. - Ghi đè nút tải xuống danh sách phát - Nút tải xuống danh sách phát sẽ luôn được hiển thị, và khi thao tác sẽ mở trình tải xuống bên ngoài đối với các danh sách phát công khai. - Nếu được hiển thị, nút tải xuống danh sách phát sẽ mở trình tải xuống gốc trong ứng dụng. - Tên gói ứng dụng trình tải xuống danh sách phát - Nhập tên gói ứng dụng trình tải xuống đã cài đặt trên thiết bị của bạn, chẳng hạn như YTDLnis. Giả mạo phiên bản ứng dụng Phiên bản đã được giả mạo Phiên bản không được giả mạo @@ -368,6 +378,30 @@ Một số thành phần có thể không bị ẩn." Nhập tên các mục mà bạn muốn lọc được phân cách bằng dòng. Bộ lọc tuỳ chỉnh không hợp lệ: %s. + + Điều chỉnh nút + Ghi đè thao tác nhấn của các nút trong ứng dụng. + + Nút tải xuống + Ghi đè nút tải xuống video + Nút tải xuống video sẽ mở trình tải xuống bên ngoài của bạn. + Nút tải xuống video sẽ mở trình tải xuống tích hợp sẵn trong ứng dụng. + Ghi đè nút tải xuống danh sách phát + Nút tải xuống danh sách phát sẽ luôn được hiển thị, và khi thao tác sẽ mở trình tải xuống bên ngoài đối với các danh sách phát công khai. + Nếu được hiển thị, nút tải xuống danh sách phát sẽ mở trình tải xuống tích hợp sẵn trong ứng dụng. + Tên gói trình tải xuống danh sách phát + Nhập tên gói ứng dụng trình tải xuống đã cài đặt trên thiết bị của bạn, chẳng hạn như YTDLnis. + + Ghi đè nút Youtube Music + Nút Youtube Music sẽ mở ứng dụng RVX Music. + Nút Youtube Music sẽ mở ứng dụng YT Music. + Tên gói của RVX Music + Tên gói của RVX Music đã được cài đặt. + RVX Music + Cảnh báo + Hiện %s chưa được cài đặt. Hãy cài đặt và thử lại. + Điều kiện tiên quyết + Cần phải có YouTube Music để ghi đè hành động của nút. Nhấn vào đây để tải YouTube Music. Trình phát thu nhỏ Thay đổi kiểu trình phát thu nhỏ trong ứng dụng. @@ -398,10 +432,10 @@ Một số thành phần có thể không bị ẩn." Các nút Chuyển về video trước và Chuyển đến video tiếp theo được hiển thị. Độ mờ lớp phủ Giá trị độ mờ của lớp phủ trình phát thu nhỏ trong khoảng từ 0 đến 100, trong đó 0 là trong suốt. - Độ mờ của lớp phủ trình phát thu nhỏ phải nằm trong khoảng 0 - 100. Đã đặt lại về mặc định. - - Thanh điều hướng - Ẩn hoặc hiển thị các nút thành phần trên thanh điều hướng. + Độ mờ của lớp phủ trình phát thu nhỏ phải nằm trong khoảng 0 - 100. + + Thanh điều hướng + Ẩn hoặc hiển thị các thành phần trong phần thanh điều hướng. Thanh điều hướng thu gọn Thanh điều hướng thu gọn đã bật. Khoảng cách giữa các nút trên thanh điều hướng sẽ hẹp hơn. Thanh điều hướng thu gọn đã tắt. @@ -418,8 +452,8 @@ Một số thành phần có thể không bị ẩn." Nút Thông báo đã ẩn. Nút Thông báo được hiển thị. Ẩn nút Shorts - Nút Shorts đã ẩn. - Nút Shorts được hiển thị. + Đã ẩn nút Shorts. + Đã hiện nút Shorts. Ẩn nút Kênh đăng ký Nút Kênh đăng ký đã ẩn. Nút Kênh đăng ký được hiển thị. @@ -427,14 +461,21 @@ Một số thành phần có thể không bị ẩn." Tên nút đã ẩn. Tên nút được hiển thị. Hoán đổi nút Tạo và nút Thông báo - "Hoán đổi vị trí của nút Tạo và nút Thông báo bằng cách giả mạo thông tin thiết bị. + "Nút Tạo đã được hoán đổi với nút Thông báo. + +Lưu ý: Việc bật tuỳ chọn này cũng sẽ ẩn các quảng cáo trong video." + Nút Tạo không được hoán đổi với nút Thông báo. + "Tắt tùy chọn này có thể hiện quảng cáo từ máy chủ. + +Ngoài ra, quảng cáo sẽ không còn bị chặn trong trình phát Shorts. -• Tuỳ chọn này có thể không có hiệu lực cho đến khi khởi động lại thiết bị. -• Tắt tuỳ chọn này sẽ tải thêm quảng cáo từ phía máy chủ. -• Tắt tuỳ chọn này có thể hiển thị quảng cáo dạng video." +Nếu cài đặt này không có hiệu lực, hãy thử chuyển sang chế độ Ẩn danh." Kích hoạt thanh điều hướng trong suốt Thanh điều hướng đã được làm trong suốt. Thanh điều hướng đã được hiển thị. + Ẩn Thanh điều hướng + Thanh điều hướng đã bị ẩn. + Thanh điều hướng đã được hiển thị. Trình đơn Cài đặt Ẩn các thành phần của trình đơn Cài đặt YouTube. @@ -461,7 +502,7 @@ Phụ đề" "Bật thanh tìm kiếm rộng trên thẻ Bạn. Tuỳ chọn này sẽ ẩn nút Cài đặt trên thẻ Bạn. Để truy cập phần Cài đặt, vui lòng làm theo các bước sau: -Thẻ Bạn → Xem kênh → Trình đơn → Cài đặt" +Thẻ Bạn → Xem kênh → Trình đơn → Cài đặt." Ẩn nút Truyền Nút Truyền đã ẩn. Nút Truyền được hiển thị. @@ -491,7 +532,7 @@ Nhấn và giữ để mở cài đặt RVX." Trình phát Độ mờ lớp phủ trình phát Giá trị độ mờ của lớp phủ trình phát trong khoảng từ 0 đến 100, trong đó 0 là trong suốt. - Độ mờ của lớp phủ trình phát phải nằm trong khoảng 0 - 100. Đã đặt lại về mặc định. + Độ mờ của lớp phủ trình phát phải nằm trong khoảng 0 - 100. Tắt bảng tự động bật lên khi phát Bảng tự động bật lên khi phát video (Danh sách phát, Trò chuyện trực tiếp,...) đã tắt. Bảng tự động bật lên khi phát video (Danh sách phát, Trò chuyện trực tiếp,...) đã bật. @@ -509,7 +550,7 @@ Cài đặt → Tự động phát → Tự động phát video tiếp theo" Tốc độ phát khi nhấn và giữ Nhập tốc độ phát khi nhấn và giữ trong khoảng từ 0 đến 8.0. - Tốc độ phát khi nhấn và giữ phải nằm trong khoảng 0 - 8.0. Đã đặt lại về mặc định. + Tốc độ phát khi nhấn và giữ phải nằm trong khoảng 0 - 8.0. Ẩn hình mờ video Hình mờ video đã ẩn. Hình mờ video được hiển thị. @@ -555,12 +596,12 @@ Cài đặt → Tự động phát → Tự động phát video tiếp theo" Video đề xuất ở màn hình kết thúc được hiển thị. - Ẩn lớp phủ zoom - Lớp phủ zoom đã bị ẩn. - Lớp phủ zoom đã được hiển thị. Bỏ qua tự động đếm ngược trước khi phát Bỏ qua đếm ngược tự động phát đã bật.\nNếu tính năng Tự động phát được bật, video tiếp theo sẽ phát ngay lập tức mà không cần đếm ngược. Bỏ qua đếm ngược tự động phát đã tắt.\nNếu tính năng Tự động phát được bật, video tiếp theo sẽ phát sau khi hết thời gian đếm ngược. + Ẩn lớp phủ khi chụm để thu phóng + Lớp phủ khi chụm để thu phóng đã bị ẩn. + Lớp phủ khi chụm để thu phóng đã được hiển thị. Nút Hành động Ẩn hoặc hiển thị các nút hành động bên dưới video. @@ -591,9 +632,9 @@ Cài đặt → Tự động phát → Tự động phát video tiếp theo."Ẩn nút Chia sẻ Nút Chia sẻ đã ẩn. Nút Chia sẻ được hiển thị. - Ẩn nút Mua sắm - Nút Mua sắm đã ẩn. - Nút Mua sắm được hiển thị. + Ẩn nút Cửa hàng + Nút Cửa hàng đã ẩn. + Nút Cửa hàng được hiển thị. Ẩn nút Cảm ơn Nút Cảm ơn đã ẩn. Nút Cảm ơn được hiển thị. @@ -639,9 +680,9 @@ Cài đặt → Tự động phát → Tự động phát video tiếp theo."Ẩn nội dung bình luận Tuỳ chọn này không thay đổi kích thước của phần bình luận, vì vậy có thể mở Phát lại trò chuyện trực tiếp trong phần bình luận. Tuỳ chọn này thay đổi kích thước của phần bình luận, khiến bạn không thể mở Phát lại trò chuyện trực tiếp trong phần bình luận. - Ẩn nút Tạo video Short - Nút Tạo video Short đã ẩn. - Nút Tạo tạo Short được hiển thị. + Ẩn nút Tạo video ngắn + Đã ẩn nút Tạo video ngắn. + Đã hiện nút Tạo video ngắn. Ẩn nút Cảm ơn Nút Cảm ơn đã ẩn. Nút Cảm ơn được hiển thị. @@ -672,9 +713,9 @@ Cài đặt → Tự động phát → Tự động phát video tiếp theo."Ẩn mục Tốc độ phát Mục Tốc độ phát đã ẩn. Mục Tốc độ phát được hiển thị. - Ẩn tiêu đề trình đơn Chất lượng - Tiêu đề trình đơn Chất lượng đã ẩn. - Tiêu đề trình đơn Chất lượng được hiển thị. + Ẩn tiêu đề mục Chất lượng + Tiêu đề mục Chất lượng đã ẩn. + Tiêu đề mục Chất lượng được hiển thị. Ẩn ghi chú cuối mục Chất lượng Ghi chú cuối mục Chất lượng video hiện tại đã ẩn. Ghi chú cuối mục Chất lượng video hiện tại được hiển thị. @@ -766,7 +807,7 @@ Hạn chế: Tiêu đề video sẽ biến mất khi nhấn vào." Nút Chia sẻ được hiển thị. Lề trên bảng nút thao tác nhanh Giá trị khoảng cách từ thanh tiến trình đến bảng nút thao tác nhanh trong khoảng từ 0 đến 32. - Lề trên bảng nút thao tác nhanh phải nằm trong khoảng 0 - 32. Đã đặt lại về mặc định. + Lề trên bảng nút thao tác nhanh phải nằm trong khoảng 0 - 32. Xem ở chế độ toàn màn hình dọc Đang phát video ở chế độ toàn màn hình dọc. @@ -956,8 +997,8 @@ Mở rộng mô tả video có thể không hoạt động nếu bạn lưu chu Trinh phát Shorts sẽ không tiếp tục khi ứng dụng khởi chạy. Trinh phát Shorts sẽ tiếp tục khi ứng dụng khởi chạy. Ẩn nút nổi - "Các nút nổi như 'Dùng bản âm thanh này' bị ẩn trong tab kênh Shorts." - "Các nút nổi như 'Dùng bản âm thanh này' được hiển thị trong tab kênh Shorts." + "Đã ẩn các nút nổi như 'Dùng bản âm thanh này' trong thẻ kênh Shorts." + "Đã hiện các nút nổi như 'Dùng bản âm thanh này' trong thẻ kênh Shorts." Kệ Shorts Ẩn kệ Shorts @@ -977,7 +1018,7 @@ Hạn chế: Tiêu đề chính thức trong kết quả tìm kiếm sẽ đư Ẩn trong phần Nhật ký xem. Hiển thị trong phần Nhật ký xem. - Thay đổi trạng thái lặp lại của trình phát Shorts + Thay đổi trạng thái lặp lại video ngắn Tự động phát Mặc định Dừng @@ -986,122 +1027,133 @@ Hạn chế: Tiêu đề chính thức trong kết quả tìm kiếm sẽ đư Trình phát Shorts Ẩn hoặc hiển thị các thành phần trong trình phát Shorts. Ẩn nút Tham gia - Nút Tham gia được ẩn. - Nút Tham gia được hiển thị. + Đã ẩn nút Tham gia. + Đã hiện nút Tham gia. Ẩn nút Đăng ký - Nút Đăng ký đã ẩn. - Nút Đăng ký được hiển thị. + Đã ẩn nút Đăng ký. + Đã hiện nút Đăng ký. Ẩn tiêu đề tạm dừng - Tiêu đề tạm dừng đã bị ẩn. - Tiêu đề tạm dừng đã được hiển thị. + Đã ẩn Tiêu đề tạm dừng. + Đã hiện Tiêu đề tạm dừng. Ẩn các nút phủ lên khi tạm dừng - Các nút phủ lên khi tạm dừng được ẩn. - Các nút phủ lên khi tạm dừng được hiển thị. + Đã ẩn các nút phủ lên khi tạm dừng. + Đã hiện các nút phủ lên khi tạm dừng. Ẩn nhãn quảng cáo được tài trợ - Nhãn quảng cáo được tài trợ đã ẩn. - Nhãn quảng cáo được tài trợ được hiển thị. + Đã ẩn Nhãn quảng cáo được tài trợ. + Đã hiện Nhãn quảng cáo được tài trợ. Ẩn nút Thịnh hành - Nút Thịnh hành đã ẩn. - Nút Thịnh hành đã hiển thị. + Đã ẩn nút Thịnh hành. + Đã hiện nút Thịnh hành. Ẩn nút Mua sắm - Nút Mua sắm đã ẩn. - Nút Mua sắm đã hiển thị. - Ẩn nút Mua sắm - Nút Mua sắm đã ẩn. - Nút Mua sắm được hiển thị. - Ẩn nút Cảm ơn - Nút Cảm ơn đã ẩn. - Nút Cảm ơn được hiển thị. - Ẩn sản phẩm được gắn thẻ - Sản phẩm được gắn thẻ đã ẩn. - Sản phẩm gắn thẻ được hiển thị. - Ẩn nút Vị trí - Nút Vị trí đã ẩn. - Nút Vị trí đã hiển thị. - Ẩn nút Lưu vào danh sách phát - Nút lưu vào danh sách phát đã ẩn. - Nút lưu vào danh sách phát được hiển thị. - Ẩn nút Gợi ý tìm kiếm - Nút Gợi ý tìm kiếm đã ẩn. - Nút Gợi ý tìm kiếm đã hiển thị. - Ẩn nút \'Dùng bản âm thanh này\' - Nút \'Dùng bản âm thanh này\' đã ẩn. - Nút \'Dùng bản âm thanh này\' đã hiển thị. - Ẩn nút Sử dụng mẫu - Nút Sử dụng mẫu đã ẩn. - Nút Sử dụng mẫu đã hiển thị. + Đã ẩn nút Mua sắm. + Đã hiện nút Mua sắm. Ẩn Bảng thông tin - Bảng thông tin đã ẩn. - Bảng thông tin được hiển thị. + Đã ẩn Bảng thông tin. + Đã hiện Bảng thông tin. Ẩn tiêu đề Trò chuyện trực tiếp - Tiêu đề Trò chuyện trực tiếp đã ẩn.\n\nNút Quay lại trong tiêu đề sẽ không bị ẩn. - Tiêu đề Trò chuyện trực tiếp được hiển thị.\n\nNút Quay lại trong tiêu đề sẽ không bị ẩn. - Ẩn thanh kênh - Thanh kênh được ẩn - Thanh kênh được hiển thị. - Ẩn tiêu đề video - Tiêu đề đã ẩn. - Tiêu đề được hiển thị. - Ẩn nhãn siêu dữ liệu âm thanh - Nhãn siêu dữ liệu đã ẩn. - Nhãn siêu dữ liệu được hiển thị. - Ẩn nhãn liên kết video toàn màn hình - Nhãn liên kết video đã ẩn. - Nhãn liên kết video được hiển thị. + Đã ẩn tiêu đề Trò chuyện trực tiếp.\n\nNút Quay lại trong tiêu đề sẽ không bị ẩn. + Đã hiện tiêu đề Trò chuyện trực tiếp.\n\nNút Quay lại trong tiêu đề sẽ không bị ẩn. + Ẩn Thanh kênh + Đã ẩn Thanh kênh. + Đã hiện Thanh kênh. + Ẩn Tiêu đề video + Đã ẩn Tiêu đề. + Đã hiện Tiêu đề. + Ẩn nhãn Siêu dữ liệu âm thanh + Đã ẩn nhãn Siêu dữ liệu. + Đã hiện nhãn Siêu dữ liệu. + Ẩn nhãn Liên kết toàn video + Đã ẩn nhãn Liên kết video. + Đã hiện nhãn Liên kết video. + + Hành động đề xuất + Ẩn nút Phông xanh + Đã ẩn nút Phông xanh. + Đã hiện nút Phông xanh. + Ẩn nút Lưu âm thanh + Đã ẩn nút Lưu âm thanh. + Đã hiện nút Lưu âm thanh. + Nút Cửa hàng + Đã ẩn nút Cửa hàng. + Đã hiện nút Cửa hàng. + Ẩn nút Super Thanks + Đã ẩn nút Super Thanks. + Đã hiện nút Super Thanks. + Ẩn nút \'Dùng bản âm thanh này\' + Đã ẩn nút \'Dùng bản âm thanh này\'. + Đã hiện nút \'Dùng bản âm thanh này\'. + Ẩn nút Sử dụng mẫu + Ẩn nút Sử dụng mẫu. + Đã hiện nút Sử dụng mẫu. + Ẩn nút Vị trí + Đã ẩn nút Vị trí. + Đã hiện nút Vị trí. + Ẩn nút Gợi ý tìm kiếm + Đã ẩn nút Gợi ý tìm kiếm. + Đã hiện nút Gợi ý tìm kiếm. + Ẩn sản phẩm được gắn thẻ + Đã ẩn Sản phẩm được gắn thẻ. + Đã hiện Sản phẩm được gắn thẻ. Nút hành động Ẩn nút Thích - Nút Thích được ẩn. - Nút Thích được hiển thị. + Đã ẩn nút Thích. + Đã hiện nút Thích. Ẩn nút Không thích - Nút Không thích đã bị ẩn. - Nút Không thích đã được hiển thị. + Đã ẩn nút Không thích. + Đã hiện nút Không thích. Ẩn nút Bình luận - Nút Bình luận đã ẩn. - Nút Bình luận được hiển thị. - Ẩn nút Remix - Nút Remix đã ẩn. - Nút Remix được hiển thị. + Đã ẩn nút Bình luận. + Đã hiện nút Bình luận. + Ẩn nút Phối lại + Đã ẩn nút Phối lại. + Đã hiện nút Phối lại. Ẩn nút Chia sẻ - Nút Chia sẻ đã ẩn . - Nút Chia sẻ được hiển thị. + Đã ẩn nút Chia sẻ. + Đã hiện nút Chia sẻ. Nút Âm thanh - Nút Âm thanh đã ẩn. - Nút Âm thanh được hiển thị. + Đã ẩn nút Âm thanh. + Đã hiện nút Âm thanh. Hoạt ảnh / Phản hồi + Vô hiệu hoá hiệu ứng nút Thích + Đã vô hiệu hoá hiệu ứng phun nước trên nút Thích. + Đã kích hoạt hiệu ứng phun nước trên nút Thích. Ẩn nền các nút Phát & Tạm dừng - Nền nút bị ẩn. - Nền nút được hiển thị. + Đã ẩn nền của nút. + Đã hiển thị nền của nút. Hoạt ảnh nhấn đúp Nguyên bản Thumbs up - Thumbs up (Loang màu) + Thumbs up (Cairo) Trái tim Trái tim (Đỏ) Ẩn Bật Mốc thời gian - "Mốc thời gian được bật. + "Đã kích hoạt Mốc thời gian. Hạn chế: -• Cài đặt này không chỉ bật Mốc thời gian mà còn cho phép ẩn giao diện người dùng bằng cách nhấn vào nền trình phát. +• Cài đặt này không chỉ kích hoạt Mốc thời gian mà còn cho phép ẩn giao diện người dùng bằng cách nhấn vào nền trình phát. • Vì đây là tính năng đang trong giai đoạn phát triển của Google nên bố cục có thể bị hỏng." - Mốc thời gian đã vô hiệu hóa. - Nhấn giữ Mốc thời gian - Nhấn và giữ vào Mốc thời gian để thay đổi trạng thái phát lặp lại trên Shorts. + Đã vô hiệu hoá Mốc thời gian. + Thao tác nhấn giữ Mốc thời gian + Nhấn và giữ vào Mốc thời gian để thay đổi trạng thái phát lặp lại trên trình phát Shorts. Lề dưới của bảng Meta - Cấu hình khoảng cách từ thanh tìm kiếm đến bảng meta, trong khoảng 0-64. - Lề dưới cùng của bảng meta phải nằm trong khoảng từ 0-64. Đặt lại về giá trị mặc định. + Cấu hình khoảng cách từ thanh tiến trình tới bảng meta, nằm trong khoảng 0 đến 64. + Lề dưới của bảng Meta phải nằm trong khoảng từ 0 đến 64. Ẩn thanh công cụ - Thanh công cụ đã ẩn. - Thanh công cụ được hiển thị. + Đã ẩn Thanh công cụ. + Đã hiện Thanh công cụ. Ẩn Thanh điều hướng - Thanh điều hướng đã ẩn. - Thanh điều hướng được hiển thị. - Thay thế tên người dùng của kênh - Tên kênh đã được sử dụng. - Tên người dùng của kênh đã được sử dụng. + Đã ẩn Thanh điều hướng. + Đã hiện Thanh điều hướng. + Chiều cao của khoảng trống + Cấu hình chiều cao của khoảng trống còn lại khi thanh điều hướng bị ẩn, nằm trong khoảng từ 0 đến 100 (%). + Chiều cao phải nằm trong khoảng từ 0 đến 100 (%). + Thay đổi tên người dùng Shorts + Đang hiển thị tên kênh. + Đang hiển thị tên người dùng (@username). Điều khiển vuốt Bật cử chỉ điều chỉnh độ sáng tự động @@ -1132,8 +1184,8 @@ Hạn chế: Kích thước văn bản trên lớp phủ vuốt Độ to nhỏ của văn bản được hiển thị trên lớp phủ vuốt. Kích thước văn bản trên lớp phủ vuốt - Tỉ lệ phần trăm diện tích màn hình có thể vuốt.\n\nLưu ý: Thao tác này cũng sẽ thay đổi kích thước vùng màn hình đối với cử chỉ nhấn đúp để tua. - Kích thước khu vực vuốt không được lớn hơn 50. Đã đặt lại về mặc định. + Phần diện tích màn hình có thể vuốt.\n\nLưu ý: Thao tác này cũng sẽ thay đổi kích thước vùng màn hình đối với cử chỉ nhấn đúp để tua. + Kích thước khu vực vuốt không được lớn hơn 50. Thời gian hiển thị lớp phủ vuốt (mili giây) Số mili giây mà lớp phủ vuốt được hiển thị. Tắt độ sáng HDR tự động @@ -1161,7 +1213,7 @@ Hạn chế: Đang áp dụng các giá trị tốc độ phát video tùy chỉnh. Đang áp dụng các giá trị tốc độ phát video mặc định. Kiểu mục tốc độ phát tùy chỉnh - Hộp thoại tùy chỉnh được sử dụng. + Mục tốc độ phát dạng hộp thoại được sử dụng. Mục tốc độ phát kiểu cũ được sử dụng. Chỉnh sửa tốc độ phát Thêm hoặc thay đổi tốc độ phát lại có sẵn. @@ -1190,7 +1242,7 @@ Hạn chế: Cài đặt này có thể sẽ không áp dụng cho các video kh Tốc độ phát mặc định không áp dụng cho Shorts. Đã bỏ qua bộ đệm tải trước. Bỏ qua bộ đệm tải trước - "Bỏ qua bộ đệm tải trước khi bắt đầu video để ngay lập tức áp dụng chất lượng video mặc định. + "Bỏ qua bộ đệm tải trước ở đầu video để áp dụng ngay chất lượng video mặc định. Chi tiết: • Khi video bắt đầu, sẽ có độ trễ khoảng 0,3 giây. @@ -1202,18 +1254,25 @@ Chi tiết: Giả mạo kích thước thiết bị "Giả lập kích thước thiết bị đến giá trị tối đa. Chất lượng cao có thể được mở khóa trên một số video yêu cầu kích thước thiết bị lớn, nhưng không phải tất cả các video." - Thay thế codec AV1 - Thay thế codec AV1 bằng codec VP9. - Từ chối phản hồi codec AV1 - "Buộc từ chối phản hồi codec AV1. -Một codec khác sẽ được áp dụng sau khoảng 20 giây tải bộ đệm." - Quá trình dự phòng gây ra khoảng 20 giây tải bộ đệm. + Vô hiệu hoá bộ giải mã VP9 + "Bộ giải mã VP9 đã bị vô hiệu hoá. + +• Độ phân giải tối đa là 1080p. +• Việc phát video sẽ sử dụng nhiều dữ liệu di động hơn so với VP9. +• Bộ giải mã VP9 vẫn được sử dụng cho video HDR." + Bộ giải mã VP9 đã được kích hoạt. + Thay thế bộ giải mã phần mềm AV1 + Thay thế bộ giải mã phần mềm AV1 bằng bộ giải mã VP9. + Từ chối phản hồi của bộ giải mã phần mềm AV1 + "Buộc từ chối phản hồi của bộ giải mã phần mềm AV1. +Một bộ giải mã khác sẽ được áp dụng sau khoảng 20 giây tải bộ đệm." + Quá trình dự phòng sẽ tạo ra khoảng 20 giây tải bộ đệm. Đã lưu tốc độ phát mặc định thành %s. Thay đổi chất lượng trên dữ liệu di động mặc định thành %s. Không thể đặt chất lượng video. Thay đổi chất lượng trên WiFi mặc định thành %s. - Tốc độ tùy chỉnh phải nhỏ hơn %sx. Đã đặt lại về giá trị mặc định. - Tốc độ phát tùy chỉnh không hợp lệ. Đã đặt lại về giá trị mặc định. + Tốc độ tùy chỉnh phải nhỏ hơn %sx. + Tốc độ phát tùy chỉnh không hợp lệ. Return YouTube Dislike Hiện số lượt không thích @@ -1414,6 +1473,7 @@ Hạn chế: Lượt không thích có thể không hiển thị nếu người Tên người dùng đã được thay đổi thành công. Xếp hạng của bạn là <b>%.2f</b> Bạn đã tạo <b>%s</b> phân đoạn + Nhấn vào đây để xem các phân đoạn của bạn. Bảng xếp hạng SponsorBlock Bạn đã hỗ trợ mọi người <b>%s</b> phân đoạn Nhấn vào đây để xem số liệu thống kê toàn cầu và những người đóng góp hàng đầu. @@ -1460,13 +1520,13 @@ Nhấn vào Tiếp tục và tắt tối ưu hóa pin." Tiếp tục Liên kết sạch khi chia sẻ Loại bỏ các tham số truy vấn theo dõi khỏi URL khi chia sẻ liên kết. - Tắt giao thức QUIC - "Tắt giao thức QUIC của CronetEngine để giảm độ trễ khi phát video." + Vô hiệu hoá giao thức QUIC + "Vô hiệu hoá giao thức QUIC của CronetEngine để giảm độ trễ khi phát video." Thay đổi giao diện chia sẻ Sử dụng giao diện chia sẻ của hệ thống. Sử dụng giao diện chia sẻ của ứng dụng. - Bật Codec OPUS - Bật codec OPUS nếu phản hồi của trình phát bao gồm codec OPUS. + Kích hoạt bộ giải mã OPUS + Kích hoạt bộ giải mã OPUS nếu phản hồi của trình phát bao gồm bộ giải mã OPUS. Nhập/Xuất cài đặt Nhập hoặc xuất cài đặt. @@ -1494,8 +1554,8 @@ Nhấn vào Tiếp tục và tắt tối ưu hóa pin." Giả mạo dữ liệu phát trực tiếp để ngăn chặn sự cố phát. Giả mạo dữ liệu phát trực tiếp Đã giả mạo dữ liệu phát trực tiếp. - "Dữ liệu phát trực tiếp hiện không được giả mạo. Việc phát video có thể không hoạt động bình thường." - Việc tắt cài đặt này có thể gây ra sự cố phát video. + "Chưa giả mạo dữ liệu phát trực tiếp. Phát video có thể không hoạt động bình thường." + Việc tắt cài đặt này có thể gây ra sự cố khi phát video. Máy khách mặc định iOS Android @@ -1506,13 +1566,17 @@ Nhấn vào Tiếp tục và tắt tối ưu hóa pin." TV HTML5 Trang Web Hạn chế của việc giả mạo - • Phim hoặc video trả phí có thể không phát được. + "• Phim hoặc video trả phí có thể không phát được. +• Video phát trực tiếp sẽ khởi chạy từ đầu. +• Video có thể kết thúc sớm 1 giây. +• Không có bộ giải mã âm thanh opus." • Mục Bản âm thanh bị thiếu. - • Mục Bản âm thanh bị thiếu. + "• Mục Bản âm thanh bị thiếu. +• Mục 'Âm lượng ổn định' không khả dụng." • Video có thể không phát được. Buộc iOS sử dụng AVC (H.264) - Codec video trên iOS là AVC (H.264). - Codec video trên iOS là AVC (H.264), VP9, or AV1. + Bộ giải mã video trên iOS là AVC (H.264). + Bộ giải mã video trên iOS là AVC (H.264), VP9, hoặc là AV1. "Bật chức năng này có thể tăng cường thời lượng pin và khắc phục tình trạng giật lag khi phát video. AVC (H.264) có độ phân giải tối đa 1080p, và phát video sẽ dùng nhiều dữ liệu di động hơn với VP9 hoặc AV1." @@ -1527,7 +1591,7 @@ AVC (H.264) có độ phân giải tối đa 1080p, và phát video sẽ dùng n Kiểu nhật ký Gốc Thay thế miền - Chặn Nhật ký xem + Chặn nhật ký Trạng thái • Nhật ký xem bị chặn. • Tuân theo cài đặt Nhật ký xem của tài khoản Google. @@ -1550,6 +1614,7 @@ AVC (H.264) có độ phân giải tối đa 1080p, và phát video sẽ dùng n Revancify Xanh Revancify Đỏ YouTube + YouTube (Tiêu đề tối giản) Nguyên gốc Không bao gồm Đã bao gồm diff --git a/src/main/resources/youtube/translations/zh-rCN/strings.xml b/src/main/resources/youtube/translations/zh-rCN/strings.xml index 53cf38c69d..e2af6703b5 100644 --- a/src/main/resources/youtube/translations/zh-rCN/strings.xml +++ b/src/main/resources/youtube/translations/zh-rCN/strings.xml @@ -23,13 +23,6 @@ 隐藏全屏广告 全屏广告已隐藏 全屏广告已显示 - 关闭全屏广告 - 全屏广告已通过关闭按钮关闭 - "全屏广告已屏蔽 - -限制:全屏下的社区帖子图片可能被屏蔽" - 全屏广告已屏蔽(对话类型: %s) - 全屏广告已关闭 隐藏一般广告 一般广告已隐藏 一般广告已显示 @@ -39,6 +32,9 @@ 隐藏付费推广横幅 付费推广横幅已隐藏 付费推广横幅已显示 + 隐藏推广横幅广告 + 推广横幅广告已隐藏 + 推广横幅广告已显示 隐藏自我推广卡片 自我推广卡片已隐藏 自我推广卡片已显示 @@ -97,6 +93,7 @@ 隐藏专辑卡片 专辑卡片已隐藏 专辑卡片已显示 + 隐藏轮播内容 "隐藏以下分类: • 突发新闻 • 继续观看 @@ -104,13 +101,15 @@ • 再次收听 • 购物 • 重新观看" - 隐藏轮播内容 隐藏 Chips 视频栏 Chips 视频栏已隐藏 Chips 视频栏已显示 隐藏视频下方的扩展面板 扩展面板已隐藏 扩展面板已显示 + 隐藏扩展边框 + 扩展边框已隐藏 + 扩展边框已显示 隐藏动态字幕按钮 隐藏字幕按钮 显示字幕按钮 @@ -249,18 +248,8 @@ • 来自未订阅频道且观看次数少于1,000次的视频" 隐藏低播放量的视频 从主页隐藏未订阅的频道上传的且播放量少于 1,000 的推荐视频 - - 视频时长过滤器 - 根据时长隐藏视频 - 隐藏时长短于或长于指定时长的视频\n\n已知问题:它不会隐藏播放器中相关视频的时间戳,而是隐藏视频时间标记 - 时长高于此数字 - 时长高于此数字的推荐视频将被隐藏 - 时长低于此数字 - 时长低于此数字的视频将被隐藏 观看次数过滤器 - 播放量 - 隐藏播放量低于此数字的推荐视频 高于播放量 播放量大于此数字的视频将被隐藏。 低于播放量 @@ -287,7 +276,6 @@ 订阅 热门 稍后观看 - 起始页无效,正在重置为默认值 禁用强制自动音轨 强制自动音轨已禁用 强制自动音轨已启用 @@ -316,14 +304,6 @@ 伪装 DPI 以使用一些手机布局 启用平板布局 伪装 DPI 以使用一些平板布局 - 覆盖视频下载按钮 - 视频下载按钮打开外部下载器 - 视频下载按钮打开应用内下载器 - 覆盖播放列表下载按钮 - 播放列表下载按钮打开外部下载器 - 播放列表下载按钮打开应用内下载器 - 播放列表下载器名称 - 已安装的外部下载器应用包名,例如 YTDLnis 伪装应用版本 客户端版本已伪装 客户端版本未伪装 @@ -363,6 +343,17 @@ 按行分隔的名称筛选组件 自定义过滤器无效: %s + + + 覆盖视频下载按钮 + 视频下载按钮打开外部下载器 + 视频下载按钮打开应用内下载器 + 覆盖播放列表下载按钮 + 播放列表下载按钮打开外部下载器 + 播放列表下载按钮打开应用内下载器 + 播放列表下载器名称 + 已安装的外部下载器应用包名,例如 YTDLnis + 迷你播放器 更改应用最小化播放器样式 @@ -394,9 +385,7 @@ 叠加层不透明度 不透明度值介于 0-100 之间,0 为透明 迷你播放器的叠加层不透明度必须介于 0-100 之间 重置为默认值 - - 导航按钮 - 隐藏或显示导航栏区域的组件 + 启用窄间距导航按钮 导航按钮之间的间距变窄 导航按钮之间的间距不会变窄 @@ -422,11 +411,15 @@ 导航栏已隐藏 导航栏已显示 交换创建和通知按钮 - "通过伪装设备信息,交换创建按钮和通知按钮的位置 + "创建按钮替换为通知按钮 + +注意:启用此选项也强制隐藏视频广告" + 创建按钮未替换为通知按钮 + "禁用此功能可能会从服务器加载更多广告 -• 更改此设置,可能需要重新启动设备才能生效 -• 禁用此设置会从服务器端加载更多广告 -• 你应该禁用此设置以显示视频广告" +此外,Shorts 中的广告将不再被屏蔽 + +如果此设置未生效,请尝试切换到隐身模式" 启用半透明导航栏 导航栏半透明 导航栏不透明 @@ -488,6 +481,13 @@ 禁用播放器弹出面板 已启用自动播放器弹出面板 已禁用自动播放器弹出面板 + 禁用切换混合播放列表 + 自动切换混合播放列表已禁用 + "自动播放开启时自动切换混合播放列表 + +自动播放可以在 YouTube 设置中更改: +设置 → 自动播放→ 自动播放下一个视频" + 启用此功能将禁止在自动播放音乐时自动切换到 YouTube Mix 禁用速度叠加 "长按时禁用“2x>>” @@ -542,12 +542,12 @@ 可以在 YouTube 设置中更改自动播放: “设置→自动播放→自动播放下一个视频”" 推荐视频结束界面已显示 - 隐藏缩放叠加层 - 缩放叠加层已隐藏 - 缩放叠加层已显示 跳过自动播放倒计时 如果自动播放已开启,下一个视频将在无倒计时的情况下播放 如果自动播放已开启,下一个视频将在倒计时结束后播放 + 隐藏缩放叠加层 + 缩放叠加层已隐藏 + 缩放叠加层已显示 操作按钮 隐藏或显示视频下方的操作按钮 @@ -670,6 +670,9 @@ 举报菜单已显示 其他设置 + 隐藏氛围模式菜单 + 氛围模式菜单已隐藏 + 氛围模式菜单已显示 隐藏帮助 & 反馈菜单 帮助 & 反馈菜单已隐藏 帮助 & 反馈菜单已显示 @@ -990,30 +993,6 @@ 隐藏商店按钮 商店按钮已隐藏 商店按钮已显示 - 隐藏商店按钮 - 商店按钮已隐藏 - 商店按钮已显示 - 隐藏超级感谢按钮 - 超级感谢按钮已隐藏 - 超级感谢按钮已显示 - 隐藏标记的产品 - 标记的产品已隐藏 - 标记的产品已显示 - 隐藏位置按钮 - 位置按钮已隐藏 - 位置按钮已显示 - 隐藏保存声音到播放列表按钮 - 保存声音到播放列表按钮已隐藏 - 保存声音到播放列表按钮已显示 - 隐藏搜索建议按钮 - 搜索建议按钮已隐藏 - 搜索建议按钮已显示 - 隐藏使用此声音按钮 - 使用此声音按钮已隐藏 - 使用此声音按钮已显示 - 隐藏使用模板按钮 - 使用模板按钮已隐藏 - 使用模板按钮已显示 隐藏信息面板 信息面板已隐藏 信息面板已显示 @@ -1032,6 +1011,31 @@ 隐藏完整视频链接标签 视频链接标签已隐藏 视频链接标签已显示 + + 隐藏保存声音到播放列表按钮 + 保存声音到播放列表按钮已隐藏 + 保存声音到播放列表按钮已显示 + 隐藏商店按钮 + 商店按钮已隐藏 + 商店按钮已显示 + 隐藏超级感谢按钮 + 超级感谢按钮已隐藏 + 超级感谢按钮已显示 + 隐藏使用此声音按钮 + 使用此声音按钮已隐藏 + 使用此声音按钮已显示 + 隐藏使用模板按钮 + 使用模板按钮已隐藏 + 使用模板按钮已显示 + 隐藏位置按钮 + 位置按钮已隐藏 + 位置按钮已显示 + 隐藏搜索建议按钮 + 搜索建议按钮已隐藏 + 搜索建议按钮已显示 + 隐藏标记的产品 + 标记的产品已隐藏 + 标记的产品已显示 操作按钮 隐藏点赞按钮 @@ -1162,6 +1166,11 @@ 恢复旧的视频画质菜单 显示旧的视频画质菜单 不显示旧的视频画质菜单 + 禁用音乐播放速度 + "音乐默认播放速度已禁用 + +限制:此设置可能不适用于不包含“监听YouTube Music”广告的视频" + 音乐默认播放速度已启用 启用 Shorts 默认播放速度 默认播放速度适用于 Shorts 默认播放速度不适用于 Shorts @@ -1177,6 +1186,13 @@ 提示信息已隐藏 伪装设备尺寸 "伪装设备尺寸,以解锁设备可能无法提供的更高视频质量" + 禁用 VP9 编解码器 + "VP9 编解码器已禁用 + +• 最大分辨率是 1080p +• 视频播放将使用比 VP9 更多的网络数据 +要获取 HDR 播放,HDR 视频仍使用VP9 编解码器" + VP9 编解码器已启用 替换软件 AV1 编解码器 使用 VP9 编解码器替换软件 AV1 编解码器 拒绝软件 AV1 编解码器切换 @@ -1388,6 +1404,7 @@ 用户名成功更改。 您的声誉为 <b>%.2f</b> 你已经创建了 <b>%s</b> 段视频 + 点击此处查看您的片段 SponsorBlock 排行榜 您为人们节省了 <b>%s</b> 个片段 点击此处查看全球统计数据和前几名贡献者 @@ -1480,9 +1497,9 @@ TV HTML5 网址 伪装副作用 - • 电影或付费视频可能无法播放 + "• 电影或付费视频可能无法播放" • 音轨菜单缺失 - • 音轨菜单缺失 + "• 音轨菜单缺失" • 视频可能无法播放 强制使用 iOS AVC (H.264) iOS 视频编解码器是 AVC (H.264) diff --git a/src/main/resources/youtube/translations/zh-rTW/strings.xml b/src/main/resources/youtube/translations/zh-rTW/strings.xml index 4a47ca7233..e1dcd4034d 100644 --- a/src/main/resources/youtube/translations/zh-rTW/strings.xml +++ b/src/main/resources/youtube/translations/zh-rTW/strings.xml @@ -6,6 +6,7 @@ ReVanced Extended 搜尋設定 + 重設為預設值。 實驗性功能 你想繼續嗎? 重新啟動以套用更改後的介面 @@ -23,13 +24,6 @@ 隱藏全螢幕廣告 全螢幕廣告已隱藏 全螢幕廣告已顯示 - 隱藏全螢幕廣告 - 全螢幕廣告可透過關閉按鈕隱藏。 - "全螢幕廣告已攔截。 - -限制:全螢幕上的社群貼文圖片可能會被攔截。" - 全螢幕廣告已攔截。(對話方塊類型: %s) - 全螢幕廣告已隱藏。 隱藏一般廣告 一般廣告已隱藏 一般廣告已顯示 @@ -79,6 +73,7 @@ 如果 DeArrow 無法使用,不顯示提示訊息 縮圖快取端點的 URL DeArrow 縮圖快取端點網址。 + DeArrow API URL 無效。 關於靜態影片擷取。 靜態捕捉是截取每個影片的開頭/中間/結尾作為縮圖。 這些圖像內建於 YouTube 中,不會使用外部 API。 使用快速靜態捕捉 @@ -100,6 +95,7 @@ 隱藏專輯卡片 專輯卡片已隱藏 專輯卡片已顯示 + 隱藏輪播內容 "隱藏以下分類區: ・最新消息 ・繼續觀看 @@ -107,13 +103,15 @@ ・再次收聽 ・購物 ・再看一次" - 隱藏輪播內容 隱藏 Chips 影片欄 Chips 影片欄已隱藏 剪輯已顯示 隱藏影片下方的章節選擇欄 影片下方的章節選擇欄已隱藏 影片下方的章節選擇欄已顯示 + 隱藏可展開的選單 + 可展開的選單已隱藏。 + 可展開的選單已顯示。 隱藏動態字幕按鈕 字幕按鈕已隱藏 字幕按鈕已顯示 @@ -251,18 +249,21 @@ ・下方標有「其他人還看了」字樣的影片。" 隱藏低觀看次數的影片 在首頁隱藏來自未訂閱頻道的、觀看次數少於 1,000 次的影片。 - - 影片時長篩選器 - 根據時長隱藏影片 - 隱藏時長過短或過長的影片。\n\n已知問題:這不會隱藏影片播放器中的相關影片,只會隱藏影片的時間戳記。 - 時長高於這個值 - 時長高於這個值的推薦影片會被隱藏 - 時長小於這個值 - 時長小於這個值的影片會被隱藏 + 隱藏即將發布的影片 + "隱藏帶有即將推出的標籤的影片。 + +注意:啟用此功能也會隱藏「通知我」按鈕。" 觀看次數篩選器 - 依觀看次數隱藏推薦影片 - 隱藏觀看次數低於這個值的推薦影片 + 依觀看次數隱藏家庭影片 + 首頁中的影片已被篩選。 + 首頁中的影片不會被篩選。 + 按觀看次數隱藏搜尋結果 + 搜尋結果已被篩選。 + 搜尋結果不會被篩選。 + 按觀看次數隱藏訂閱影片 + 訂閱來源中的影片已被篩選。 + 訂閱來源中的影片不會被過濾。 高於觀看次數 播放量大於此數字的影片將被隱藏。 低於觀看次數 @@ -270,6 +271,18 @@ 觀看次數的值 指定你的語言模板來修飾用戶界面中每個影片下顯示的播放量。每個關鍵字(在你的語言中的一個字/詞) -> 值(關鍵字的含義)必須單獨一行。在「->」符號之前是關鍵字。如果更改應用程序或系統語言,則需要重置此設定。\n\n示範:\n英語:10K views = K -> 1000,views -> views\n西班牙語:10 K vistas = K -> 1000,vistas -> views 千 -> 1 000\n百萬 -> 1 000 000\n十億 -> 1 000 000 000\n觀看數 -> 觀看數 + 關於觀看次數篩選 + "主頁/訂閱/搜尋結果將被過濾以隱藏觀看次數小於或大於指定數量的影片。 + +限制: +• 短影片無法隱藏。 +• 觀看次數為0的影片不會被曬選。" + 隱藏相關影片 + 相關影片已隱藏。 + 相關影片已顯示。 + "此設定限制播放器螢幕上可以載入佈局的最大數量。 + +如果由於伺服器端變更而導致播放器螢幕佈局發生變化,則播放器螢幕上可能會隱藏非預期的佈局。" 一般設定 更改起始頁 @@ -289,7 +302,11 @@ 訂閱 熱門 稍後觀看 - 起始頁無效,正在重設為預設值。 + 變更起始頁類型 + "起始頁總是改變。 + +限制:工具列上的後退按鈕可能無法運作。" + 起始頁僅更改一次。 停用強制自動音軌 強制自動音軌已停用 強制自動音軌已啟用 @@ -318,14 +335,6 @@ 以 DPI 欺騙系統使用手機版面配置 啟用平板版面配置 偽裝 DPI 以使用一些平板電腦版面配置 - 覆蓋影片下載按鈕 - 原生影片下載按鈕可開啟你的外部下載器。 - 原生影片下載按鈕可開啟本機應用程式內下載器。 - 覆蓋播放清單下載按鈕 - 原生播放清單下載按鈕可開啟您的外部下載器。 - 原生播放清單下載按鈕可開啟本機應用程式內下載器。 - 播放清單下載器套件名稱 - 你安裝的外部下載器應用程式的套件名稱,例如 YTDLnis。 偽裝應用程式版本 已偽裝版本 未偽裝版本 @@ -365,6 +374,30 @@ 以換行分隔的名稱來過濾元件 無效的自訂篩選器:%s + + 掛鉤按鈕 + 覆蓋應用程式內按鈕的點選操作。 + + 下載按鈕 + 覆蓋影片下載按鈕 + 原生影片下載按鈕可開啟你的外部下載器。 + 原生影片下載按鈕可開啟本機應用程式內下載器。 + 覆蓋播放清單下載按鈕 + 原生播放清單下載按鈕可開啟您的外部下載器。 + 原生播放清單下載按鈕可開啟本機應用程式內下載器。 + 播放清單下載器套件名稱 + 你安裝的外部下載器應用程式的套件名稱,例如 YTDLnis。 + + 覆蓋 YouTube音樂 按鈕 + YouTube音樂 按鈕可開啟 RVX 音樂。 + YouTube音樂 按鈕可開啟本機應用程式。 + RVX 音樂包名稱 + 已安裝的 RVX 音樂 套件名稱。 + RVX 音樂 + 警告 + %s 未安裝。 請安裝它。 + 先決條件 + YouTube音樂 需要覆蓋按鈕操作。 按此下載 YouTube音樂。 最小化播放器 變更應用程式內最小化播放器的樣式。 @@ -396,9 +429,9 @@ 覆蓋層不透明度 不透明度值介於 0-100 之間,其中 0 表示透明 小型播放器覆蓋層不透明度必須介於 0-100 之間。重設為預設值。 - - 導航按鈕 - 隱藏或顯示導航欄區域的元件 + + 導覽列 + 隱藏或顯示導覽列部分組件。 啟用窄間距導航按鈕 導航按鈕之間的間距變窄 導航按鈕之間的間距不會變窄 @@ -424,14 +457,21 @@ 導覽列已隱藏 導覽列已顯示 交換創作和通知按鈕 - "交換創作按鈕與通知按鈕的位置,透過偽裝裝置資訊來實現 + "「建立」按鈕已與「通知」按鈕交換。 + +注意:啟用此功能也會強制隱藏影片廣告。" + 「建立」按鈕未與「通知」按鈕交換。 + "停用此功能可能會從伺服器載入更多廣告。 + +此外,廣告將不再在 Shorts 中被封鎖。 -• 變更這項設定可能需要重新啟動裝置 -• 停用這項設定會從伺服器端載入更多廣告 -• 你應該停用這項設定以顯示影片廣告" +若此設定未生效,請嘗試切換至無痕模式。" 啟用半透明導覽列 導覽列是半透明的。 導覽列不透明。 + 隱藏導覽列 + 導覽列已隱藏。 + 導覽列已顯示。 設定選單 隱藏 YouTube 設定選單中的元素 @@ -553,12 +593,12 @@ 可以在 YouTube 設定中變更自動播放: '設定 → 自動播放 → 自動播放下一個影片'" 顯示建議的影片結束畫面。 - 隱藏縮放疊加 - 縮放疊加被隱藏。 - 顯示縮放疊加。 跳過自動播放倒計時 如果自動播放已開啟,下一個影片將在無倒計時的情況下播放 如果自動播放已開啟,下一個影片將在倒計時結束後播放 + 隱藏縮放疊加 + 縮放疊加被隱藏。 + 顯示縮放疊加。 操作按鈕 隱藏或顯示影片下方的操作按鈕 @@ -1003,30 +1043,6 @@ 隱藏購物按鈕 購物按鈕已隱藏。 顯示購物按鈕。 - 隱藏商店按鈕 - 商店按鈕已隱藏 - 商店按鈕已顯示 - 隱藏超級感謝按鈕 - 超級感謝按鈕已隱藏。 - 超級感謝按鈕已顯示。 - 隱藏標記的產品 - 標記的產品已隱藏 - 標記的產品已顯示 - 隱藏位置按鈕 - 位置按鈕已隱藏。 - 顯示位置按鈕。 - 隱藏儲存音效到播放清單按鈕 - 儲存音效到播放清單按鈕已隱藏。 - 儲存音效到播放清單按鈕已顯示。 - 隱藏搜尋建議按鈕 - 搜尋建議按鈕已隱藏。 - 顯示搜尋建議按鈕。 - 隱藏使用此聲音按鈕 - 使用此聲音按鈕已隱藏。 - 顯示使用此聲音按鈕。 - 隱藏使用模板按鈕 - 使用模板按鈕已隱藏 - 顯示使用模板按鈕。 隱藏訊息面板 訊息面板已隱藏 訊息面板已顯示 @@ -1045,6 +1061,31 @@ 隱藏完整影片連結標簽 影片連結標簽已隱藏 影片連結標簽已顯示 + + 隱藏儲存音效到播放清單按鈕 + 儲存音效到播放清單按鈕已隱藏。 + 儲存音效到播放清單按鈕已顯示。 + 隱藏商店按鈕 + 商店按鈕已隱藏 + 商店按鈕已顯示 + 隱藏超級感謝按鈕 + 超級感謝按鈕已隱藏。 + 超級感謝按鈕已顯示。 + 隱藏使用此聲音按鈕 + 使用此聲音按鈕已隱藏。 + 顯示使用此聲音按鈕。 + 隱藏使用模板按鈕 + 使用模板按鈕已隱藏 + 顯示使用模板按鈕。 + 隱藏位置按鈕 + 位置按鈕已隱藏。 + 顯示位置按鈕。 + 隱藏搜尋建議按鈕 + 搜尋建議按鈕已隱藏。 + 顯示搜尋建議按鈕。 + 隱藏標記的產品 + 標記的產品已隱藏 + 標記的產品已顯示 操作按鈕 隱藏點贊按鈕 @@ -1092,6 +1133,9 @@ 隱藏導航欄 導航欄已隱藏 導航欄已顯示 + 空白空間高度百分比 + 配置隱藏導覽列時留下的空白空間的高度百分比,介於 0 到 100 (%) 之間。 + 高度百分比必須介於 0-100 (%) 之間。 更換頻道代號 頻道名稱已使用。 頻道代號已使用。 @@ -1194,6 +1238,13 @@ 提示訊息已隱藏 偽裝裝置尺寸 "變更裝置尺寸設定,以便解鎖在您目前的裝置上原本不支援的較高影片品質。" + 停用 VP9 編解碼器 + "VP9 編解碼器已停用。 + +• 最高解析度為 1080p。 +• 影片播放將使用比 VP9 更多的網路數據。 +• 若要獲得 HDR 播放,HDR 影片仍會使用 VP9 編解碼器。" + VP9 編解碼器已啟用。 替換軟體 AV1 編解碼器 將軟體 AV1 編解碼器替換為 VP9 編解碼器。 拒絕軟體 AV1 編解碼器回應 @@ -1412,6 +1463,7 @@ 用戶名成功更改。 您的聲譽為 <b>%.2f</b> 你已經創建了 <b>%s</b> 段影片 + 點擊此處查看您的片段。 SponsorBlock 排行榜 您為人們節省了 <b>%s</b> 個片段 點擊此處查看全球統計數據和前幾名貢獻者 @@ -1505,9 +1557,9 @@ TV HTML5 Web 偽裝副作用 - • 電影或付費影片可能無法播放。 + "• 電影或付費影片可能無法播放。" • 音軌選單遺失。 - • 音軌選單遺失。 + "• 音軌選單遺失。" • 影片可能無法播放。 強制 iOS AVC (H.264) iOS 影片編解碼器為 AVC (H.264)、VP9 或 AV1。 @@ -1549,6 +1601,7 @@ AVC (H.264) 的最大解析度為 1080p,影片播放將比 VP9 或 AV1 使用 復興藍 復興紅 YouTube + YouTube (最小標題) 預設 排除 包括 diff --git a/src/main/resources/youtube/visual/icons/yt_alt/drawable/revanced_extended_settings_key_icon.xml b/src/main/resources/youtube/visual/icons/yt_alt/drawable/revanced_extended_settings_key_icon.xml new file mode 100644 index 0000000000..d135324c6b --- /dev/null +++ b/src/main/resources/youtube/visual/icons/yt_alt/drawable/revanced_extended_settings_key_icon.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/src/main/resources/youtube/visual/shared/drawable/premium_early_access_browse_page_key_icon.xml b/src/main/resources/youtube/visual/shared/drawable/premium_early_access_browse_page_key_icon.xml index b7a18cb13f..3a2e75e3f9 100644 --- a/src/main/resources/youtube/visual/shared/drawable/premium_early_access_browse_page_key_icon.xml +++ b/src/main/resources/youtube/visual/shared/drawable/premium_early_access_browse_page_key_icon.xml @@ -5,14 +5,9 @@ android:height="48dp" android:viewportWidth="960" android:viewportHeight="960"> - - - + diff --git a/src/main/resources/youtube/visual/shared/drawable/revanced_disable_default_playback_speed_music_icon.xml b/src/main/resources/youtube/visual/shared/drawable/revanced_disable_default_playback_speed_music_icon.xml new file mode 100644 index 0000000000..7e4cba0876 --- /dev/null +++ b/src/main/resources/youtube/visual/shared/drawable/revanced_disable_default_playback_speed_music_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/youtube/visual/shared/drawable/revanced_enable_opus_codec_icon.xml b/src/main/resources/youtube/visual/shared/drawable/revanced_enable_opus_codec_icon.xml new file mode 100644 index 0000000000..30c7aebdd3 --- /dev/null +++ b/src/main/resources/youtube/visual/shared/drawable/revanced_enable_opus_codec_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/youtube/visual/shared/drawable/revanced_enable_save_and_restore_brightness_icon.xml b/src/main/resources/youtube/visual/shared/drawable/revanced_enable_save_and_restore_brightness_icon.xml new file mode 100644 index 0000000000..bac03afa00 --- /dev/null +++ b/src/main/resources/youtube/visual/shared/drawable/revanced_enable_save_and_restore_brightness_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/youtube/visual/shared/drawable/revanced_enable_swipe_to_switch_video_icon.xml b/src/main/resources/youtube/visual/shared/drawable/revanced_enable_swipe_to_switch_video_icon.xml new file mode 100644 index 0000000000..7ade456283 --- /dev/null +++ b/src/main/resources/youtube/visual/shared/drawable/revanced_enable_swipe_to_switch_video_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/youtube/visual/shared/drawable/revanced_hide_rewards_button_icon.xml b/src/main/resources/youtube/visual/shared/drawable/revanced_hide_rewards_button_icon.xml new file mode 100644 index 0000000000..ff138dda0f --- /dev/null +++ b/src/main/resources/youtube/visual/shared/drawable/revanced_hide_rewards_button_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/youtube/visual/shared/drawable/revanced_hide_shop_button_icon.xml b/src/main/resources/youtube/visual/shared/drawable/revanced_hide_shop_button_icon.xml new file mode 100644 index 0000000000..20433a8962 --- /dev/null +++ b/src/main/resources/youtube/visual/shared/drawable/revanced_hide_shop_button_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/youtube/visual/shared/drawable/revanced_hide_thanks_button_icon.xml b/src/main/resources/youtube/visual/shared/drawable/revanced_hide_thanks_button_icon.xml new file mode 100644 index 0000000000..d4bcecbb14 --- /dev/null +++ b/src/main/resources/youtube/visual/shared/drawable/revanced_hide_thanks_button_icon.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/src/main/resources/youtube/visual/shared/drawable/revanced_preference_screen_navigation_buttons_icon.xml b/src/main/resources/youtube/visual/shared/drawable/revanced_preference_screen_navigation_bar_icon.xml similarity index 100% rename from src/main/resources/youtube/visual/shared/drawable/revanced_preference_screen_navigation_buttons_icon.xml rename to src/main/resources/youtube/visual/shared/drawable/revanced_preference_screen_navigation_bar_icon.xml