Compare commits

...

45 Commits

Author SHA1 Message Date
semantic-release-bot
5ddd957313 chore(release): 4.16.0-dev.1 [skip ci]
# [4.16.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.15.1-dev.2...v4.16.0-dev.1) (2024-09-27)

### Features

* **YouTube - Hide Shorts components:** Add patch option to hide Shorts app shortcut (long press app icon) ([#3699](https://github.com/ReVanced/revanced-patches/issues/3699)) ([bb0dcbe](bb0dcbe83d))
2024-09-27 01:57:13 +00:00
LisoUseInAIKyrios
bb0dcbe83d feat(YouTube - Hide Shorts components): Add patch option to hide Shorts app shortcut (long press app icon) (#3699)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2024-09-26 21:55:14 -04:00
ReVanced Bot
163736fb26 chore: Sync translations (#3692) 2024-09-25 15:28:51 -04:00
semantic-release-bot
0c6db43bde chore(release): 4.15.1-dev.2 [skip ci]
## [4.15.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.15.1-dev.1...v4.15.1-dev.2) (2024-09-23)

### Bug Fixes

* **YouTube:** Show video chapter titles without clipping when overlay buttons are enabled ([#3674](https://github.com/ReVanced/revanced-patches/issues/3674)) ([317e9a8](317e9a80eb))
2024-09-23 22:52:28 +00:00
LisoUseInAIKyrios
317e9a80eb fix(YouTube): Show video chapter titles without clipping when overlay buttons are enabled (#3674) 2024-09-23 18:50:16 -04:00
semantic-release-bot
464e6a3673 chore(release): 4.15.1-dev.1 [skip ci]
## [4.15.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.15.0...v4.15.1-dev.1) (2024-09-23)

### Bug Fixes

* **Twitter - Open links with app chooser:** Fix incorrect version in compatibility list ([#3683](https://github.com/ReVanced/revanced-patches/issues/3683)) ([2e9142e](2e9142eda4))
2024-09-23 22:39:29 +00:00
7Grn
2e9142eda4 fix(Twitter - Open links with app chooser): Fix incorrect version in compatibility list (#3683) 2024-09-24 00:37:30 +02:00
ReVanced Bot
b4c6d0a7d2 chore: Sync translations (#3679) 2024-09-23 13:05:51 -04:00
semantic-release-bot
c0ee85e12a chore(release): 4.15.0 [skip ci]
# [4.15.0](https://github.com/ReVanced/revanced-patches/compare/v4.14.1...v4.15.0) (2024-09-23)

### Bug Fixes

* **TikTok - Playback speed:** Prevent crash by fixing invalid patch ([ff8fe46](ff8fe46685))
* **TikTok - Settings:** Prevent crash by fixing invalid patch ([0ab7344](0ab7344295))
* **Twitter - Open links with app chooser:** Constrain patch to last working version `10.48.0-release` ([303d2de](303d2de81d))
* **YouTube - Spoof video streams:** Change default client type to Android VR ([a104eea](a104eeaf68))
* **YouTube - Spoof video streams:** Change default client type to Android VR ([#3672](https://github.com/ReVanced/revanced-patches/issues/3672)) ([e8cb6ee](e8cb6ee028))

### Features

* **TikTok:** Bump patches to support the latest version 36.5.4 ([9f314c2](9f314c2425))
2024-09-23 14:10:49 +00:00
oSumAtrIX
2d326072e2 chore: Merge branch dev to main (#3673) 2024-09-23 16:08:46 +02:00
semantic-release-bot
586770aa3a chore(release): 4.15.0-dev.1 [skip ci]
# [4.15.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.2-dev.2...v4.15.0-dev.1) (2024-09-22)

### Features

* **TikTok:** Bump patches to support the latest version 36.5.4 ([9f314c2](9f314c2425))
2024-09-22 23:45:54 +00:00
oSumAtrIX
9f314c2425 feat(TikTok): Bump patches to support the latest version 36.5.4 2024-09-23 01:43:22 +02:00
semantic-release-bot
c8b3456738 chore(release): 4.14.2-dev.2 [skip ci]
## [4.14.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.14.2-dev.1...v4.14.2-dev.2) (2024-09-21)

### Bug Fixes

* **YouTube - Spoof video streams:** Change default client type to Android VR ([a104eea](a104eeaf68))
* **YouTube - Spoof video streams:** Change default client type to Android VR ([#3672](https://github.com/ReVanced/revanced-patches/issues/3672)) ([e8cb6ee](e8cb6ee028))
2024-09-21 23:33:50 +00:00
LisoUseInAIKyrios
e8cb6ee028 fix(YouTube - Spoof video streams): Change default client type to Android VR (#3672) 2024-09-21 19:31:55 -04:00
semantic-release-bot
3e796eb7c2 chore(release): 4.14.2-dev.1 [skip ci]
## [4.14.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.1...v4.14.2-dev.1) (2024-09-21)

### Bug Fixes

* **TikTok - Playback speed:** Prevent crash by fixing invalid patch ([ff8fe46](ff8fe46685))
* **TikTok - Settings:** Prevent crash by fixing invalid patch ([0ab7344](0ab7344295))
* **Twitter - Open links with app chooser:** Constrain patch to last working version `10.48.0-release` ([303d2de](303d2de81d))
2024-09-21 23:20:21 +00:00
oSumAtrIX
303d2de81d fix(Twitter - Open links with app chooser): Constrain patch to last working version 10.48.0-release 2024-09-22 01:18:04 +02:00
oSumAtrIX
0ab7344295 fix(TikTok - Settings): Prevent crash by fixing invalid patch 2024-09-22 01:17:53 +02:00
oSumAtrIX
ff8fe46685 fix(TikTok - Playback speed): Prevent crash by fixing invalid patch 2024-09-22 01:17:47 +02:00
LisoUseInAIKyrios
a104eeaf68 fix(YouTube - Spoof video streams): Change default client type to Android VR 2024-09-21 17:11:54 -04:00
semantic-release-bot
18b09168cc chore(release): 4.14.1 [skip ci]
## [4.14.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.0...v4.14.1) (2024-09-18)

### Bug Fixes

* **YouTube - Check environment:** Only use fields available since Android 8 ([#3655](https://github.com/ReVanced/revanced-patches/issues/3655)) ([e03c14c](e03c14cc01))
2024-09-18 23:02:35 +00:00
oSumAtrIX
f7209f0a53 chore: Merge branch dev to main (#3656) 2024-09-19 01:00:28 +02:00
semantic-release-bot
1fb3fc4857 chore(release): 4.14.1-dev.1 [skip ci]
## [4.14.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.0...v4.14.1-dev.1) (2024-09-18)

### Bug Fixes

* **YouTube - Check environment:** Only use fields available since Android 8 ([#3655](https://github.com/ReVanced/revanced-patches/issues/3655)) ([e03c14c](e03c14cc01))
2024-09-18 22:59:56 +00:00
oSumAtrIX
e03c14cc01 fix(YouTube - Check environment): Only use fields available since Android 8 (#3655) 2024-09-19 00:57:54 +02:00
semantic-release-bot
bed29d00dc chore(release): 4.14.0 [skip ci]
# [4.14.0](https://github.com/ReVanced/revanced-patches/compare/v4.13.3...v4.14.0) (2024-09-18)

### Bug Fixes

* **Pixiv - Hide ads:** Fix for latest version ([#3616](https://github.com/ReVanced/revanced-patches/issues/3616)) ([ff2c456](ff2c4564a0))
* **Soundcloud - Hide ads:** Support latest version ([#3628](https://github.com/ReVanced/revanced-patches/issues/3628)) ([2f7d751](2f7d751f9f))
* **SwissID:** Rename `Remove Google Play Integrity Integrity check` to `Remove Google Play Integrity check` ([#3558](https://github.com/ReVanced/revanced-patches/issues/3558)) ([bdd2f7c](bdd2f7cb0f))
* **YouTube - ReturnYouTubeDislike:** Show estimated like count for videos with hidden likes ([#3601](https://github.com/ReVanced/revanced-patches/issues/3601)) ([70470a9](70470a9162))
* **YouTube - SponsorBlock:** Add summary text to 'view my segments' button ([a642705](a64270514f))
* **YouTube - SponsorBlock:** Handle if the user enters an invalid number into any SB settings ([a276425](a276425d83))
* **YouTube:** Fix issues related to playback by replace streaming data ([#3582](https://github.com/ReVanced/revanced-patches/issues/3582)) ([5b1e07d](5b1e07d861))

### Features

* Add `Change data directory location` patch ([#3602](https://github.com/ReVanced/revanced-patches/issues/3602)) ([96e6f43](96e6f43ca0))
* Add `Check environment` patch ([#3610](https://github.com/ReVanced/revanced-patches/issues/3610)) ([4c2ec28](4c2ec2870c))
* **Duolingo:** Add `Disable ads` and `Enable debug menu` patch ([#3422](https://github.com/ReVanced/revanced-patches/issues/3422)) ([4e323aa](4e323aa206))
* **Sync for Reddit:** Add `Fix /user/ endpoint` patch ([16217f0](16217f012e))
* **Sync for Reddit:** Rename patch to `Use /user/ endpoint` ([5871923](58719239cf))
* **YouTube - Hide Shorts components:** Hide 'Use this sound' button ([#3647](https://github.com/ReVanced/revanced-patches/issues/3647)) ([395e18d](395e18d830))
* **YouTube - Keyword filter:** Add syntax to match whole keywords and not substrings ([#3592](https://github.com/ReVanced/revanced-patches/issues/3592)) ([ed532eb](ed532eb528))
* **YouTube - Spoof client:** Allow forcing AVC codec with iOS ([#3570](https://github.com/ReVanced/revanced-patches/issues/3570)) ([6bb848b](6bb848b991))
* **YouTube Music:** Make working patches compatible with latest versions ([#3556](https://github.com/ReVanced/revanced-patches/issues/3556)) ([f83e314](f83e314dff))
* **YouTube:** Add donation link to settings about screen ([#3626](https://github.com/ReVanced/revanced-patches/issues/3626)) ([bccd62e](bccd62e593))
2024-09-18 22:19:44 +00:00
LisoUseInAIKyrios
d36982e245 chore: Merge branch dev to main (#3559) 2024-09-18 18:17:22 -04:00
ReVanced Bot
13031f0534 chore: Sync translations (#3653) 2024-09-18 17:48:02 -04:00
semantic-release-bot
b5b6ef5d6f chore(release): 4.14.0-dev.15 [skip ci]
# [4.14.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.14...v4.14.0-dev.15) (2024-09-17)

### Bug Fixes

* **YouTube:** Fix issues related to playback by replace streaming data ([#3582](https://github.com/ReVanced/revanced-patches/issues/3582)) ([5b1e07d](5b1e07d861))
2024-09-17 22:47:28 +00:00
Zain
5b1e07d861 fix(YouTube): Fix issues related to playback by replace streaming data (#3582)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2024-09-18 00:45:02 +02:00
ReVanced Bot
e3220cc10a chore: Sync translations (#3649) 2024-09-17 12:37:00 -04:00
semantic-release-bot
02db9378ea chore(release): 4.14.0-dev.14 [skip ci]
# [4.14.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.13...v4.14.0-dev.14) (2024-09-17)

### Features

* **YouTube Music:** Make working patches compatible with latest versions ([#3556](https://github.com/ReVanced/revanced-patches/issues/3556)) ([f83e314](f83e314dff))
2024-09-17 13:58:02 +00:00
Pun Butrach
f83e314dff feat(YouTube Music): Make working patches compatible with latest versions (#3556) 2024-09-17 15:56:00 +02:00
semantic-release-bot
d5e383b78a chore(release): 4.14.0-dev.13 [skip ci]
# [4.14.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.12...v4.14.0-dev.13) (2024-09-17)

### Features

* **YouTube - Hide Shorts components:** Hide 'Use this sound' button ([#3647](https://github.com/ReVanced/revanced-patches/issues/3647)) ([395e18d](395e18d830))
2024-09-17 13:36:53 +00:00
MarcaD
395e18d830 feat(YouTube - Hide Shorts components): Hide 'Use this sound' button (#3647) 2024-09-17 15:34:31 +02:00
semantic-release-bot
887684e7c7 chore(release): 4.14.0-dev.12 [skip ci]
# [4.14.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.11...v4.14.0-dev.12) (2024-09-14)

### Bug Fixes

* **Soundcloud - Hide ads:** Support latest version ([#3628](https://github.com/ReVanced/revanced-patches/issues/3628)) ([2f7d751](2f7d751f9f))
2024-09-14 15:09:54 +00:00
FullerBread2032
2f7d751f9f fix(Soundcloud - Hide ads): Support latest version (#3628)
Co-authored-by: FullerBread2032 <admin@fullerbread2032.tk>
2024-09-14 17:07:27 +02:00
semantic-release-bot
4886a95713 chore(release): 4.14.0-dev.11 [skip ci]
# [4.14.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.10...v4.14.0-dev.11) (2024-09-12)

### Features

* **Sync for Reddit:** Rename patch to `Use /user/ endpoint` ([5871923](58719239cf))
2024-09-12 20:11:21 +00:00
oSumAtrIX
58719239cf feat(Sync for Reddit): Rename patch to Use /user/ endpoint
The bug it was used to fix does not occur anymore. This name is more correct on what the patch actually does.
2024-09-12 22:09:09 +02:00
semantic-release-bot
fcb68cc65e chore(release): 4.14.0-dev.10 [skip ci]
# [4.14.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.9...v4.14.0-dev.10) (2024-09-12)

### Features

* **Sync for Reddit:** Add `Fix /user/ endpoint` patch ([16217f0](16217f012e))
2024-09-12 18:30:19 +00:00
oSumAtrIX
16217f012e feat(Sync for Reddit): Add Fix /user/ endpoint patch 2024-09-12 20:18:22 +02:00
semantic-release-bot
d6f20ee67d chore(release): 4.14.0-dev.9 [skip ci]
# [4.14.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.8...v4.14.0-dev.9) (2024-09-09)

### Features

* **YouTube:** Add donation link to settings about screen ([#3626](https://github.com/ReVanced/revanced-patches/issues/3626)) ([bccd62e](bccd62e593))
2024-09-09 07:23:22 +00:00
LisoUseInAIKyrios
bccd62e593 feat(YouTube): Add donation link to settings about screen (#3626) 2024-09-09 03:21:22 -04:00
semantic-release-bot
1322403698 chore(release): 4.14.0-dev.8 [skip ci]
# [4.14.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.7...v4.14.0-dev.8) (2024-09-09)

### Bug Fixes

* **YouTube - SponsorBlock:** Add summary text to 'view my segments' button ([a642705](a64270514f))
2024-09-09 07:19:55 +00:00
LisoUseInAIKyrios
a64270514f fix(YouTube - SponsorBlock): Add summary text to 'view my segments' button 2024-09-09 03:17:43 -04:00
semantic-release-bot
f5de555adf chore(release): 4.14.0-dev.7 [skip ci]
# [4.14.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.6...v4.14.0-dev.7) (2024-09-06)

### Features

* Add `Check environment` patch ([#3610](https://github.com/ReVanced/revanced-patches/issues/3610)) ([4c2ec28](4c2ec2870c))
2024-09-06 08:29:07 +00:00
oSumAtrIX
4c2ec2870c feat: Add Check environment patch (#3610)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: KobeW50 <84587632+KobeW50@users.noreply.github.com>
2024-09-06 10:26:53 +02:00
193 changed files with 3934 additions and 3836 deletions

View File

@@ -1,3 +1,168 @@
# [4.16.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.15.1-dev.2...v4.16.0-dev.1) (2024-09-27)
### Features
* **YouTube - Hide Shorts components:** Add patch option to hide Shorts app shortcut (long press app icon) ([#3699](https://github.com/ReVanced/revanced-patches/issues/3699)) ([0d4e1f5](https://github.com/ReVanced/revanced-patches/commit/0d4e1f5d03cf3dcc06fd41165e26a1ce901b976b))
## [4.15.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.15.1-dev.1...v4.15.1-dev.2) (2024-09-23)
### Bug Fixes
* **YouTube:** Show video chapter titles without clipping when overlay buttons are enabled ([#3674](https://github.com/ReVanced/revanced-patches/issues/3674)) ([4b88c31](https://github.com/ReVanced/revanced-patches/commit/4b88c316ed90c56e83e2aee266561833b36fc37d))
## [4.15.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.15.0...v4.15.1-dev.1) (2024-09-23)
### Bug Fixes
* **Twitter - Open links with app chooser:** Fix incorrect version in compatibility list ([#3683](https://github.com/ReVanced/revanced-patches/issues/3683)) ([adafe85](https://github.com/ReVanced/revanced-patches/commit/adafe85d77f6a0031a5523b9b7da69475959d78d))
# [4.15.0](https://github.com/ReVanced/revanced-patches/compare/v4.14.1...v4.15.0) (2024-09-23)
### Bug Fixes
* **TikTok - Playback speed:** Prevent crash by fixing invalid patch ([82d53cb](https://github.com/ReVanced/revanced-patches/commit/82d53cbc3bbfa585ba4337fdfaec9f0f19c802e6))
* **TikTok - Settings:** Prevent crash by fixing invalid patch ([8074032](https://github.com/ReVanced/revanced-patches/commit/8074032fad3eff1c03296a882d2e2820da99b592))
* **Twitter - Open links with app chooser:** Constrain patch to last working version `10.48.0-release` ([b9955d5](https://github.com/ReVanced/revanced-patches/commit/b9955d5ff6e456593b01f0f25d80ff660d02082a))
* **YouTube - Spoof video streams:** Change default client type to Android VR ([74c8637](https://github.com/ReVanced/revanced-patches/commit/74c8637943347078955f51325bc6af92a35d4463))
* **YouTube - Spoof video streams:** Change default client type to Android VR ([#3672](https://github.com/ReVanced/revanced-patches/issues/3672)) ([a3306f6](https://github.com/ReVanced/revanced-patches/commit/a3306f6717a09b734354f00363a96abad0ae14e7))
### Features
* **TikTok:** Bump patches to support the latest version 36.5.4 ([e5dcb72](https://github.com/ReVanced/revanced-patches/commit/e5dcb72597092fb32003f11fdf6f861ede4e7ff3))
# [4.15.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.2-dev.2...v4.15.0-dev.1) (2024-09-22)
### Features
* **TikTok:** Bump patches to support the latest version 36.5.4 ([e5dcb72](https://github.com/ReVanced/revanced-patches/commit/e5dcb72597092fb32003f11fdf6f861ede4e7ff3))
## [4.14.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.14.2-dev.1...v4.14.2-dev.2) (2024-09-21)
### Bug Fixes
* **YouTube - Spoof video streams:** Change default client type to Android VR ([74c8637](https://github.com/ReVanced/revanced-patches/commit/74c8637943347078955f51325bc6af92a35d4463))
* **YouTube - Spoof video streams:** Change default client type to Android VR ([#3672](https://github.com/ReVanced/revanced-patches/issues/3672)) ([a3306f6](https://github.com/ReVanced/revanced-patches/commit/a3306f6717a09b734354f00363a96abad0ae14e7))
## [4.14.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.1...v4.14.2-dev.1) (2024-09-21)
### Bug Fixes
* **TikTok - Playback speed:** Prevent crash by fixing invalid patch ([82d53cb](https://github.com/ReVanced/revanced-patches/commit/82d53cbc3bbfa585ba4337fdfaec9f0f19c802e6))
* **TikTok - Settings:** Prevent crash by fixing invalid patch ([8074032](https://github.com/ReVanced/revanced-patches/commit/8074032fad3eff1c03296a882d2e2820da99b592))
* **Twitter - Open links with app chooser:** Constrain patch to last working version `10.48.0-release` ([b9955d5](https://github.com/ReVanced/revanced-patches/commit/b9955d5ff6e456593b01f0f25d80ff660d02082a))
## [4.14.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.0...v4.14.1) (2024-09-18)
### Bug Fixes
* **YouTube - Check environment:** Only use fields available since Android 8 ([#3655](https://github.com/ReVanced/revanced-patches/issues/3655)) ([4413533](https://github.com/ReVanced/revanced-patches/commit/441353306572340131030e1c4fee1ab6acb63cd9))
## [4.14.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.0...v4.14.1-dev.1) (2024-09-18)
### Bug Fixes
* **YouTube - Check environment:** Only use fields available since Android 8 ([#3655](https://github.com/ReVanced/revanced-patches/issues/3655)) ([4413533](https://github.com/ReVanced/revanced-patches/commit/441353306572340131030e1c4fee1ab6acb63cd9))
# [4.14.0](https://github.com/ReVanced/revanced-patches/compare/v4.13.3...v4.14.0) (2024-09-18)
### Bug Fixes
* **Pixiv - Hide ads:** Fix for latest version ([#3616](https://github.com/ReVanced/revanced-patches/issues/3616)) ([98956e8](https://github.com/ReVanced/revanced-patches/commit/98956e8f1a41347bb435720bbf984969469a7110))
* **Soundcloud - Hide ads:** Support latest version ([#3628](https://github.com/ReVanced/revanced-patches/issues/3628)) ([66e7e33](https://github.com/ReVanced/revanced-patches/commit/66e7e33efce9b702fdfcc2b9803e9da8491c1f08))
* **SwissID:** Rename `Remove Google Play Integrity Integrity check` to `Remove Google Play Integrity check` ([#3558](https://github.com/ReVanced/revanced-patches/issues/3558)) ([0f5a771](https://github.com/ReVanced/revanced-patches/commit/0f5a771a5cff5684b4a8fd317f4938fe2cf3cbbe))
* **YouTube - ReturnYouTubeDislike:** Show estimated like count for videos with hidden likes ([#3601](https://github.com/ReVanced/revanced-patches/issues/3601)) ([005be82](https://github.com/ReVanced/revanced-patches/commit/005be82d71b2a42387b1b57035930b20f4663794))
* **YouTube - SponsorBlock:** Add summary text to 'view my segments' button ([df80b9f](https://github.com/ReVanced/revanced-patches/commit/df80b9f92f0d981b9a40b7756d74f8ccc3dcb1e9))
* **YouTube - SponsorBlock:** Handle if the user enters an invalid number into any SB settings ([37b3dd1](https://github.com/ReVanced/revanced-patches/commit/37b3dd1e789f8bb16fa1b9dd582e39c89dbe730c))
* **YouTube:** Fix issues related to playback by replace streaming data ([#3582](https://github.com/ReVanced/revanced-patches/issues/3582)) ([dfa94d7](https://github.com/ReVanced/revanced-patches/commit/dfa94d70f65150d6ef24ea6378b8e6a317055186))
### Features
* Add `Change data directory location` patch ([#3602](https://github.com/ReVanced/revanced-patches/issues/3602)) ([5998029](https://github.com/ReVanced/revanced-patches/commit/59980292809cc0626bf49a160eeb05a1523c4eda))
* Add `Check environment` patch ([#3610](https://github.com/ReVanced/revanced-patches/issues/3610)) ([fbcbdaf](https://github.com/ReVanced/revanced-patches/commit/fbcbdafa4938a35b5fdec46aae7b250a84b9c139))
* **Duolingo:** Add `Disable ads` and `Enable debug menu` patch ([#3422](https://github.com/ReVanced/revanced-patches/issues/3422)) ([d0a8599](https://github.com/ReVanced/revanced-patches/commit/d0a8599f76ce653e5d7c98069ad3c58b9ab9c5eb))
* **Sync for Reddit:** Add `Fix /user/ endpoint` patch ([46d11f3](https://github.com/ReVanced/revanced-patches/commit/46d11f3530fcdae9ed08b7e93aac235638a92dff))
* **Sync for Reddit:** Rename patch to `Use /user/ endpoint` ([98ead49](https://github.com/ReVanced/revanced-patches/commit/98ead493380932cb105530f4ba992673fd364d82))
* **YouTube - Hide Shorts components:** Hide 'Use this sound' button ([#3647](https://github.com/ReVanced/revanced-patches/issues/3647)) ([33fc090](https://github.com/ReVanced/revanced-patches/commit/33fc09061431d4aa457d743c09a0de31ec566df1))
* **YouTube - Keyword filter:** Add syntax to match whole keywords and not substrings ([#3592](https://github.com/ReVanced/revanced-patches/issues/3592)) ([f5fb351](https://github.com/ReVanced/revanced-patches/commit/f5fb3512cfafe214ba6a6d25ba0825ae1884a0ff))
* **YouTube - Spoof client:** Allow forcing AVC codec with iOS ([#3570](https://github.com/ReVanced/revanced-patches/issues/3570)) ([1a49d1f](https://github.com/ReVanced/revanced-patches/commit/1a49d1f3c2a343d05d0abc07c143add486246fd0))
* **YouTube Music:** Make working patches compatible with latest versions ([#3556](https://github.com/ReVanced/revanced-patches/issues/3556)) ([12f6f19](https://github.com/ReVanced/revanced-patches/commit/12f6f1966ad04631451940f7b64d785c3ef481a0))
* **YouTube:** Add donation link to settings about screen ([#3626](https://github.com/ReVanced/revanced-patches/issues/3626)) ([0684ab5](https://github.com/ReVanced/revanced-patches/commit/0684ab5f183631de5720352049cfd293daa58eb0))
# [4.14.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.14...v4.14.0-dev.15) (2024-09-17)
### Bug Fixes
* **YouTube:** Fix issues related to playback by replace streaming data ([#3582](https://github.com/ReVanced/revanced-patches/issues/3582)) ([dfa94d7](https://github.com/ReVanced/revanced-patches/commit/dfa94d70f65150d6ef24ea6378b8e6a317055186))
# [4.14.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.13...v4.14.0-dev.14) (2024-09-17)
### Features
* **YouTube Music:** Make working patches compatible with latest versions ([#3556](https://github.com/ReVanced/revanced-patches/issues/3556)) ([12f6f19](https://github.com/ReVanced/revanced-patches/commit/12f6f1966ad04631451940f7b64d785c3ef481a0))
# [4.14.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.12...v4.14.0-dev.13) (2024-09-17)
### Features
* **YouTube - Hide Shorts components:** Hide 'Use this sound' button ([#3647](https://github.com/ReVanced/revanced-patches/issues/3647)) ([33fc090](https://github.com/ReVanced/revanced-patches/commit/33fc09061431d4aa457d743c09a0de31ec566df1))
# [4.14.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.11...v4.14.0-dev.12) (2024-09-14)
### Bug Fixes
* **Soundcloud - Hide ads:** Support latest version ([#3628](https://github.com/ReVanced/revanced-patches/issues/3628)) ([66e7e33](https://github.com/ReVanced/revanced-patches/commit/66e7e33efce9b702fdfcc2b9803e9da8491c1f08))
# [4.14.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.10...v4.14.0-dev.11) (2024-09-12)
### Features
* **Sync for Reddit:** Rename patch to `Use /user/ endpoint` ([98ead49](https://github.com/ReVanced/revanced-patches/commit/98ead493380932cb105530f4ba992673fd364d82))
# [4.14.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.9...v4.14.0-dev.10) (2024-09-12)
### Features
* **Sync for Reddit:** Add `Fix /user/ endpoint` patch ([46d11f3](https://github.com/ReVanced/revanced-patches/commit/46d11f3530fcdae9ed08b7e93aac235638a92dff))
# [4.14.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.8...v4.14.0-dev.9) (2024-09-09)
### Features
* **YouTube:** Add donation link to settings about screen ([#3626](https://github.com/ReVanced/revanced-patches/issues/3626)) ([0684ab5](https://github.com/ReVanced/revanced-patches/commit/0684ab5f183631de5720352049cfd293daa58eb0))
# [4.14.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.7...v4.14.0-dev.8) (2024-09-09)
### Bug Fixes
* **YouTube - SponsorBlock:** Add summary text to 'view my segments' button ([df80b9f](https://github.com/ReVanced/revanced-patches/commit/df80b9f92f0d981b9a40b7756d74f8ccc3dcb1e9))
# [4.14.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.6...v4.14.0-dev.7) (2024-09-06)
### Features
* Add `Check environment` patch ([#3610](https://github.com/ReVanced/revanced-patches/issues/3610)) ([fbcbdaf](https://github.com/ReVanced/revanced-patches/commit/fbcbdafa4938a35b5fdec46aae7b250a84b9c139))
# [4.14.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.5...v4.14.0-dev.6) (2024-09-06)

View File

@@ -828,6 +828,12 @@ public final class app/revanced/patches/reddit/customclients/syncforreddit/fix/s
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/syncforreddit/fix/slink/FixSLinksPatch;
}
public final class app/revanced/patches/reddit/customclients/syncforreddit/fix/user/UseUserEndpointPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/syncforreddit/fix/user/UseUserEndpointPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
}
public final class app/revanced/patches/reddit/customclients/syncforreddit/misc/integrations/IntegrationsPatch : app/revanced/patches/shared/misc/integrations/BaseIntegrationsPatch {
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/syncforreddit/misc/integrations/IntegrationsPatch;
}
@@ -862,6 +868,12 @@ public final class app/revanced/patches/serviceportalbund/detection/root/RootDet
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
}
public abstract class app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch : app/revanced/patcher/patch/BytecodePatch {
public fun <init> (Lapp/revanced/patcher/fingerprint/MethodFingerprint;Ljava/util/Set;Lapp/revanced/patches/shared/misc/integrations/BaseIntegrationsPatch;)V
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
}
public final class app/revanced/patches/shared/misc/fix/verticalscroll/VerticalScrollPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/shared/misc/fix/verticalscroll/VerticalScrollPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
@@ -1870,6 +1882,10 @@ public final class app/revanced/patches/youtube/misc/backgroundplayback/Backgrou
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
}
public final class app/revanced/patches/youtube/misc/check/CheckEnvironmentPatch : app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/check/CheckEnvironmentPatch;
}
public final class app/revanced/patches/youtube/misc/debugging/DebuggingPatch : app/revanced/patcher/patch/ResourcePatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/debugging/DebuggingPatch;
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
@@ -1900,6 +1916,12 @@ public final class app/revanced/patches/youtube/misc/fix/playback/SpoofSignature
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
}
public final class app/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
}
public final class app/revanced/patches/youtube/misc/fix/playback/UserAgentClientSpoofPatch : app/revanced/patches/all/misc/transformation/BaseTransformInstructionsPatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/UserAgentClientSpoofPatch;
public synthetic fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Ljava/lang/Object;
@@ -1978,13 +2000,19 @@ public final class app/revanced/patches/youtube/misc/playercontrols/BottomContro
public final class app/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch;
public static field showPlayerControlsFingerprintResult Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
public final fun getShowPlayerControlsFingerprintResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;
public final fun initializeBottomControl (Ljava/lang/String;)V
public final fun initializeControl (Ljava/lang/String;)V
public final fun injectVisibilityCheckCall (Ljava/lang/String;)V
public final fun setShowPlayerControlsFingerprintResult (Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;)V
}
public final class app/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch : app/revanced/patcher/patch/ResourcePatch, java/io/Closeable {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch;
public final fun addBottomControls (Ljava/lang/String;)V
public fun close ()V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
}
public final class app/revanced/patches/youtube/misc/playeroverlay/PlayerOverlaysHookPatch : app/revanced/patcher/patch/BytecodePatch {
@@ -2152,6 +2180,8 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
public static final fun indexOfFirstWideLiteralInstructionValue (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfFirstWideLiteralInstructionValueOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfFirstWideLiteralInstructionValueReversed (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfFirstWideLiteralInstructionValueReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfIdResource (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
public static final fun indexOfIdResourceOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V
@@ -2179,6 +2209,7 @@ public final class app/revanced/util/ResourceUtilsKt {
public static final fun copyXmlNode (Ljava/lang/String;Lapp/revanced/patcher/util/DomFileEditor;Lapp/revanced/patcher/util/DomFileEditor;)Ljava/lang/AutoCloseable;
public static final fun doRecursively (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V
public static final fun forEachChildElement (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V
public static final fun insertFirst (Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)V
public static final fun iterateXmlNodeChildren (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
}

View File

@@ -31,6 +31,8 @@ dependencies {
implementation(libs.guava)
// Used in JsonGenerator.
implementation(libs.gson)
// Android API stubs defined here.
compileOnly(project(":stub"))
}
kotlin {

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
version = 4.14.0-dev.6
version = 4.16.0-dev.1

View File

@@ -5,3 +5,5 @@ buildCache {
isEnabled = "CI" !in System.getenv()
}
}
include(":stub")

View File

@@ -13,16 +13,7 @@ import app.revanced.util.exception
name = "Hide video ads",
description = "Hides ads that appear while listening to or streaming music videos, podcasts, or songs.",
compatiblePackages = [
CompatiblePackage(
"com.google.android.apps.youtube.music",
[
"6.45.54",
"6.51.53",
"7.01.53",
"7.02.52",
"7.03.52",
]
)
CompatiblePackage("com.google.android.apps.youtube.music")
],
)
@Suppress("unused")

View File

@@ -12,16 +12,7 @@ import com.android.tools.smali.dexlib2.Opcode
@Patch(
description = "Adds more audio codec options. The new audio codecs usually result in better audio quality.",
compatiblePackages = [
CompatiblePackage(
"com.google.android.apps.youtube.music",
[
"6.45.54",
"6.51.53",
"7.01.53",
"7.02.52",
"7.03.52",
]
)
CompatiblePackage("com.google.android.apps.youtube.music")
]
)
@Deprecated("This patch is no longer needed as the feature is now enabled by default.")

View File

@@ -12,16 +12,7 @@ import app.revanced.util.exception
name = "Enable exclusive audio playback",
description = "Enables the option to play audio without video.",
compatiblePackages = [
CompatiblePackage(
"com.google.android.apps.youtube.music",
[
"6.45.54",
"6.51.53",
"7.01.53",
"7.02.52",
"7.03.52",
]
)
CompatiblePackage("com.google.android.apps.youtube.music")
]
)
@Suppress("unused")

View File

@@ -14,16 +14,7 @@ import app.revanced.patches.music.interaction.permanentrepeat.fingerprints.Repea
name = "Permanent repeat",
description = "Permanently remember your repeating preference even if the playlist ends or another track is played.",
compatiblePackages = [
CompatiblePackage(
"com.google.android.apps.youtube.music",
[
"6.45.54",
"6.51.53",
"7.01.53",
"7.02.52",
"7.03.52",
]
)
CompatiblePackage("com.google.android.apps.youtube.music")
],
use = false
)

View File

@@ -14,16 +14,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
name = "Hide category bar",
description = "Hides the category bar at the top of the homepage.",
compatiblePackages = [
CompatiblePackage(
"com.google.android.apps.youtube.music",
[
"6.45.54",
"6.51.53",
"7.01.53",
"7.02.52",
"7.03.52",
]
)
CompatiblePackage("com.google.android.apps.youtube.music")
],
use = false,
)

View File

@@ -17,16 +17,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
name = "Hide 'Get Music Premium' label",
description = "Hides the \"Get Music Premium\" label from the account menu and settings.",
compatiblePackages = [
CompatiblePackage(
"com.google.android.apps.youtube.music",
[
"6.45.54",
"6.51.53",
"7.01.53",
"7.02.52",
"7.03.52",
]
)
CompatiblePackage("com.google.android.apps.youtube.music")
]
)
@Suppress("unused")

View File

@@ -23,16 +23,7 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
name = "Remove upgrade button",
description = "Removes the upgrade tab from the pivot bar.",
compatiblePackages = [
CompatiblePackage(
"com.google.android.apps.youtube.music",
[
"6.45.54",
"6.51.53",
"7.01.53",
"7.02.52",
"7.03.52",
]
)
CompatiblePackage("com.google.android.apps.youtube.music")
]
)
@Suppress("unused")

View File

@@ -13,16 +13,7 @@ import app.revanced.patches.music.misc.androidauto.fingerprints.CheckCertificate
name = "Bypass certificate checks",
description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.",
compatiblePackages = [
CompatiblePackage(
"com.google.android.apps.youtube.music",
[
"6.45.54",
"6.51.53",
"7.01.53",
"7.02.52",
"7.03.52",
]
)
CompatiblePackage("com.google.android.apps.youtube.music")
]
)
@Suppress("unused")

View File

@@ -14,16 +14,7 @@ import app.revanced.util.resultOrThrow
name = "Remove background playback restrictions",
description = "Removes restrictions on background playback, including playing kids videos in the background.",
compatiblePackages = [
CompatiblePackage(
"com.google.android.apps.youtube.music",
[
"6.45.54",
"6.51.53",
"7.01.53",
"7.02.52",
"7.03.52",
]
)
CompatiblePackage("com.google.android.apps.youtube.music")
]
)
@Suppress("unused")

View File

@@ -23,16 +23,7 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
integrationsPatchDependency = IntegrationsPatch::class,
gmsCoreSupportResourcePatch = GmsCoreSupportResourcePatch,
compatiblePackages = setOf(
CompatiblePackage(
"com.google.android.apps.youtube.music",
setOf(
"6.45.54",
"6.51.53",
"7.01.53",
"7.02.52",
"7.03.52",
),
),
CompatiblePackage("com.google.android.apps.youtube.music"),
),
fingerprints = setOf(
CastDynamiteModuleV2Fingerprint,

View File

@@ -0,0 +1,61 @@
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints.*
import app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints.OAuthFriendRequestFingerprint
import app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints.OAuthSubredditInfoRequestHelperFingerprint
import app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints.OAuthUnfriendRequestFingerprint
import app.revanced.util.getReference
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference
@Patch(
name = "Use /user/ endpoint",
description = "Replaces the deprecated endpoint for viewing user profiles /u with /user, that used to fix a bug.",
compatiblePackages = [
CompatiblePackage("com.laurencedawson.reddit_sync"),
CompatiblePackage("com.laurencedawson.reddit_sync.pro"),
CompatiblePackage("com.laurencedawson.reddit_sync.dev"),
],
use = false,
)
@Suppress("unused")
object UseUserEndpointPatch : BytecodePatch(
fingerprints = setOf(
OAuthFriendRequestFingerprint,
OAuthSubredditInfoRequestConstructorFingerprint,
OAuthSubredditInfoRequestHelperFingerprint,
OAuthUnfriendRequestFingerprint,
OAuthUserIdRequestFingerprint,
OAuthUserInfoRequestFingerprint,
),
) {
override fun execute(context: BytecodeContext) {
arrayOf(
OAuthFriendRequestFingerprint,
OAuthSubredditInfoRequestConstructorFingerprint,
OAuthSubredditInfoRequestHelperFingerprint,
OAuthUnfriendRequestFingerprint,
OAuthUserIdRequestFingerprint,
OAuthUserInfoRequestFingerprint,
).map(MethodFingerprint::resultOrThrow).map {
it.scanResult.stringsScanResult!!.matches.first().index to it.mutableMethod
}.forEach { (userPathStringIndex, method) ->
val userPathStringInstruction = method.getInstruction<OneRegisterInstruction>(userPathStringIndex)
val userPathStringRegister = userPathStringInstruction.registerA
val fixedUserPathString = userPathStringInstruction.getReference<StringReference>()!!.string.replace("u/", "user/")
method.replaceInstruction(
userPathStringIndex,
"const-string v$userPathStringRegister, \"${fixedUserPathString}\"",
)
}
}
}

View File

@@ -0,0 +1,10 @@
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal abstract class BaseUserEndpointFingerprint(source: String, accessFlags: Int? = null) :
MethodFingerprint(
accessFlags = accessFlags,
strings = listOf("u/"),
customFingerprint = { _, classDef -> classDef.sourceFile == source },
)

View File

@@ -0,0 +1,3 @@
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
internal object OAuthFriendRequestFingerprint : BaseUserEndpointFingerprint("OAuthFriendRequest.java")

View File

@@ -0,0 +1,10 @@
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
import app.revanced.patcher.extensions.or
import com.android.tools.smali.dexlib2.AccessFlags
internal object OAuthSubredditInfoRequestConstructorFingerprint :
BaseUserEndpointFingerprint(
"OAuthSubredditInfoRequest.java",
AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
)

View File

@@ -0,0 +1,10 @@
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
import app.revanced.patcher.extensions.or
import com.android.tools.smali.dexlib2.AccessFlags
internal object OAuthSubredditInfoRequestHelperFingerprint :
BaseUserEndpointFingerprint(
"OAuthSubredditInfoRequest.java",
AccessFlags.PRIVATE or AccessFlags.STATIC,
)

View File

@@ -0,0 +1,3 @@
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
internal object OAuthUnfriendRequestFingerprint : BaseUserEndpointFingerprint("OAuthUnfriendRequest.java")

View File

@@ -0,0 +1,3 @@
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
internal object OAuthUserIdRequestFingerprint : BaseUserEndpointFingerprint("OAuthUserIdRequest.java")

View File

@@ -0,0 +1,3 @@
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
internal object OAuthUserInfoRequestFingerprint : BaseUserEndpointFingerprint("OAuthUserInfoRequest.java")

View File

@@ -0,0 +1,114 @@
package app.revanced.patches.shared.misc.checks
import android.os.Build.*
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableLongEncodedValue
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableStringEncodedValue
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.checks.fingerprints.PatchInfoBuildFingerprint
import app.revanced.patches.shared.misc.checks.fingerprints.PatchInfoFingerprint
import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch
import app.revanced.util.exception
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.immutable.value.ImmutableLongEncodedValue
import com.android.tools.smali.dexlib2.immutable.value.ImmutableStringEncodedValue
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
abstract class BaseCheckEnvironmentPatch(
private val mainActivityOnCreateFingerprint: MethodFingerprint,
compatiblePackages: Set<CompatiblePackage>,
integrationsPatch: BaseIntegrationsPatch,
) : BytecodePatch(
description = "Checks, if the application was patched by, otherwise warns the user.",
compatiblePackages = compatiblePackages,
dependencies = setOf(
AddResourcesPatch::class,
integrationsPatch::class,
),
fingerprints = setOf(
PatchInfoFingerprint,
PatchInfoBuildFingerprint,
mainActivityOnCreateFingerprint,
),
) {
override fun execute(context: BytecodeContext) {
AddResourcesPatch(BaseCheckEnvironmentPatch::class)
setPatchInfo()
invokeCheck()
}
private fun setPatchInfo() {
PatchInfoFingerprint.setClassFields(
"PATCH_TIME" to System.currentTimeMillis().encoded,
)
fun setBuildInfo() {
PatchInfoBuildFingerprint.setClassFields(
"PATCH_BOARD" to BOARD.encodedAndHashed,
"PATCH_BOOTLOADER" to BOOTLOADER.encodedAndHashed,
"PATCH_BRAND" to BRAND.encodedAndHashed,
"PATCH_CPU_ABI" to CPU_ABI.encodedAndHashed,
"PATCH_CPU_ABI2" to CPU_ABI2.encodedAndHashed,
"PATCH_DEVICE" to DEVICE.encodedAndHashed,
"PATCH_DISPLAY" to DISPLAY.encodedAndHashed,
"PATCH_FINGERPRINT" to FINGERPRINT.encodedAndHashed,
"PATCH_HARDWARE" to HARDWARE.encodedAndHashed,
"PATCH_HOST" to HOST.encodedAndHashed,
"PATCH_ID" to ID.encodedAndHashed,
"PATCH_MANUFACTURER" to MANUFACTURER.encodedAndHashed,
"PATCH_MODEL" to MODEL.encodedAndHashed,
"PATCH_PRODUCT" to PRODUCT.encodedAndHashed,
"PATCH_RADIO" to RADIO.encodedAndHashed,
"PATCH_TAGS" to TAGS.encodedAndHashed,
"PATCH_TYPE" to TYPE.encodedAndHashed,
"PATCH_USER" to USER.encodedAndHashed,
)
}
try {
Class.forName("android.os.Build")
// This only works on Android,
// because it uses Android APIs.
setBuildInfo()
} catch (_: ClassNotFoundException) { }
}
private fun invokeCheck() = mainActivityOnCreateFingerprint.result?.mutableMethod?.addInstructions(
0,
"invoke-static/range { p0 .. p0 },$INTEGRATIONS_CLASS_DESCRIPTOR->check(Landroid/app/Activity;)V",
) ?: throw mainActivityOnCreateFingerprint.exception
private companion object {
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/shared/checks/CheckEnvironmentPatch;"
@OptIn(ExperimentalEncodingApi::class)
private val String.encodedAndHashed
get() = MutableStringEncodedValue(
ImmutableStringEncodedValue(
Base64.encode(
MessageDigest.getInstance("SHA-1")
.digest(this.toByteArray(StandardCharsets.UTF_8)),
),
),
)
private val Long.encoded get() = MutableLongEncodedValue(ImmutableLongEncodedValue(this))
private fun <T : MutableEncodedValue> MethodFingerprint.setClassFields(vararg fieldNameValues: Pair<String, T>) {
val fieldNameValueMap = mapOf(*fieldNameValues)
resultOrThrow().mutableClass.fields.forEach { field ->
field.initialValue = fieldNameValueMap[field.name] ?: return@forEach
}
}
}
}

View File

@@ -0,0 +1,7 @@
package app.revanced.patches.shared.misc.checks.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object PatchInfoBuildFingerprint : MethodFingerprint(
customFingerprint = { _, classDef -> classDef.type == "Lapp/revanced/integrations/shared/checks/PatchInfo\$Build;" },
)

View File

@@ -0,0 +1,9 @@
package app.revanced.patches.shared.misc.checks.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object PatchInfoFingerprint : MethodFingerprint(
customFingerprint = { _, classDef ->
classDef.type == "Lapp/revanced/integrations/shared/checks/PatchInfo;"
},
)

View File

@@ -115,8 +115,8 @@ abstract class BaseGmsCoreSupportPatch(
// Verify GmsCore is installed and whitelisted for power optimizations and background usage.
mainActivityOnCreateFingerprint.result?.mutableMethod?.apply {
// Temporary fix for Google photos integration.
var setContextIndex = indexOfFirstInstruction {
// Temporary fix for patches with an integrations patch that hook the onCreate method as well.
val setContextIndex = indexOfFirstInstruction {
val reference = getReference<MethodReference>() ?: return@indexOfFirstInstruction false
reference.toString() == "Lapp/revanced/integrations/shared/Utils;->setContext(Landroid/content/Context;)V"

View File

@@ -9,6 +9,7 @@ import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.getNode
import app.revanced.util.insertFirst
import org.w3c.dom.Node
import java.io.Closeable
@@ -47,11 +48,7 @@ abstract class BaseSettingsResourcePatch(
// It may be necessary to ask for the desired resourceValue in the future.
AddResourcesPatch("values", resource)
}.let { preferenceNode ->
if (prepend && firstChild != null) {
insertBefore(preferenceNode, firstChild)
} else {
appendChild(preferenceNode)
}
insertFirst(preferenceNode)
}
}

View File

@@ -62,7 +62,7 @@ object HideAdsPatch : BytecodePatch(
// Prevent verification of an HTTP header containing the user's current plan, which would contradict the previous patch.
InterceptFingerprint.resultOrThrow().let { result ->
val conditionIndex = result.scanResult.patternScanResult!!.endIndex
val conditionIndex = result.scanResult.patternScanResult!!.endIndex + 1
result.mutableMethod.addInstruction(
conditionIndex,
"return-object p1",

View File

@@ -9,14 +9,13 @@ internal object InterceptFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC.value,
parameters = listOf("L"),
opcodes = listOf(
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_EQZ,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT
),
strings = listOf("SC-Mob-UserPlan", "Configuration"),
customFingerprint = { _, classDef ->
classDef.sourceFile == "ApiUserPlanInterceptor.java"
classDef.sourceFile == "ApiUserPlanInterceptor.java" ||
classDef.sourceFile == "ApiUserPlanInterceptor.kt"
},
)

View File

@@ -17,16 +17,16 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Patch(
name = "Feed filter",
description = "Removes ads, livestreams, stories, image videos " +
"and videos with a specific amount of views or likes from the feed.",
"and videos with a specific amount of views or likes from the feed.",
dependencies = [IntegrationsPatch::class, SettingsPatch::class],
compatiblePackages = [
CompatiblePackage("com.ss.android.ugc.trill", ["32.5.3"]),
CompatiblePackage("com.zhiliaoapp.musically", ["32.5.3"])
]
CompatiblePackage("com.ss.android.ugc.trill", ["36.5.4"]),
CompatiblePackage("com.zhiliaoapp.musically", ["36.5.4"]),
],
)
@Suppress("unused")
object FeedFilterPatch : BytecodePatch(
setOf(FeedApiServiceLIZFingerprint, SettingsStatusLoadFingerprint)
setOf(FeedApiServiceLIZFingerprint, SettingsStatusLoadFingerprint),
) {
override fun execute(context: BytecodeContext) {
FeedApiServiceLIZFingerprint.result?.mutableMethod?.apply {
@@ -36,13 +36,13 @@ object FeedFilterPatch : BytecodePatch(
addInstruction(
returnFeedItemInstruction.location.index,
"invoke-static { v$feedItemsRegister }, " +
"Lapp/revanced/integrations/tiktok/feedfilter/FeedItemsFilter;->filter(Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;)V"
"Lapp/revanced/integrations/tiktok/feedfilter/FeedItemsFilter;->filter(Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;)V",
)
} ?: throw FeedApiServiceLIZFingerprint.exception
SettingsStatusLoadFingerprint.result?.mutableMethod?.addInstruction(
0,
"invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableFeedFilter()V"
"invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableFeedFilter()V",
) ?: throw SettingsStatusLoadFingerprint.exception
}
}

View File

@@ -9,7 +9,7 @@ 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.tiktok.interaction.cleardisplay.fingerprints.OnClearDisplayEventFingerprint
import app.revanced.patches.tiktok.interaction.cleardisplay.fingerprints.OnRenderFirstFrameFingerprint
import app.revanced.patches.tiktok.shared.fingerprints.OnRenderFirstFrameFingerprint
import app.revanced.util.exception
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
@@ -19,16 +19,16 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction22c
name = "Remember clear display",
description = "Remembers the clear display configurations in between videos.",
compatiblePackages = [
CompatiblePackage("com.ss.android.ugc.trill", ["32.5.3"]),
CompatiblePackage("com.zhiliaoapp.musically", ["32.5.3"])
]
CompatiblePackage("com.ss.android.ugc.trill", ["36.5.4"]),
CompatiblePackage("com.zhiliaoapp.musically", ["36.5.4"]),
],
)
@Suppress("unused")
object RememberClearDisplayPatch : BytecodePatch(
setOf(
OnClearDisplayEventFingerprint,
OnRenderFirstFrameFingerprint
)
OnRenderFirstFrameFingerprint,
),
) {
override fun execute(context: BytecodeContext) {
OnClearDisplayEventFingerprint.result?.mutableMethod?.let {
@@ -40,7 +40,7 @@ object RememberClearDisplayPatch : BytecodePatch(
it.addInstructions(
isEnabledIndex,
"invoke-static { v$isEnabledRegister }, " +
"Lapp/revanced/integrations/tiktok/cleardisplay/RememberClearDisplayPatch;->rememberClearDisplayState(Z)V"
"Lapp/revanced/integrations/tiktok/cleardisplay/RememberClearDisplayPatch;->rememberClearDisplayState(Z)V",
)
// endregion
@@ -54,22 +54,25 @@ object RememberClearDisplayPatch : BytecodePatch(
"""
# Create a new clearDisplayEvent and post it to the EventBus (https://github.com/greenrobot/EventBus)
# The state of clear display.
invoke-static { }, Lapp/revanced/integrations/tiktok/cleardisplay/RememberClearDisplayPatch;->getClearDisplayState()Z
move-result v3
if-eqz v3, :clear_display_disabled
# Clear display type such as 0 = LONG_PRESS, 1 = SCREEN_RECORD etc.
const/4 v1, 0x0
# Enter method (Such as "pinch", "swipe_exit", or an empty string (unknown, what it means)).
const-string v2, ""
# Name of the clear display type which is equivalent to the clear display type.
const-string v2, "long_press"
const-string v3, "long_press"
# The state of clear display.
invoke-static { }, Lapp/revanced/integrations/tiktok/cleardisplay/RememberClearDisplayPatch;->getClearDisplayState()Z
move-result v4
if-eqz v4, :clear_display_disabled
new-instance v0, $clearDisplayEventClass
invoke-direct { v0, v1, v2, v3 }, $clearDisplayEventClass-><init>(ILjava/lang/String;Z)V
invoke-direct { v0, v1, v2, v3, v4 }, $clearDisplayEventClass-><init>(ILjava/lang/String;Ljava/lang/String;Z)V
invoke-virtual { v0 }, $clearDisplayEventClass->post()Lcom/ss/android/ugc/governance/eventbus/IEvent;
""",
ExternalLabel("clear_display_disabled", getInstruction(0))
ExternalLabel("clear_display_disabled", getInstruction(0)),
)
} ?: throw OnRenderFirstFrameFingerprint.exception

View File

@@ -1,9 +0,0 @@
package app.revanced.patches.tiktok.interaction.cleardisplay.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object OnRenderFirstFrameFingerprint : MethodFingerprint(
customFingerprint = { methodDef, _ ->
methodDef.definingClass.endsWith("/BaseListFragmentPanel;") && methodDef.name == "onRenderFirstFrame"
}
)

View File

@@ -13,14 +13,13 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.tiktok.interaction.downloads.fingerprints.ACLCommonShareFingerprint
import app.revanced.patches.tiktok.interaction.downloads.fingerprints.ACLCommonShareFingerprint2
import app.revanced.patches.tiktok.interaction.downloads.fingerprints.ACLCommonShareFingerprint3
import app.revanced.patches.tiktok.interaction.downloads.fingerprints.DownloadPathParentFingerprint
import app.revanced.patches.tiktok.interaction.downloads.fingerprints.DownloadUriFingerprint
import app.revanced.patches.tiktok.misc.integrations.IntegrationsPatch
import app.revanced.patches.tiktok.misc.settings.SettingsPatch
import app.revanced.patches.tiktok.misc.settings.fingerprints.SettingsStatusLoadFingerprint
import app.revanced.util.exception
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Patch(
@@ -28,9 +27,9 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
description = "Removes download restrictions and changes the default path to download to.",
dependencies = [IntegrationsPatch::class, SettingsPatch::class],
compatiblePackages = [
CompatiblePackage("com.ss.android.ugc.trill", ["32.5.3"]),
CompatiblePackage("com.zhiliaoapp.musically", ["32.5.3"])
]
CompatiblePackage("com.ss.android.ugc.trill", ["36.5.4"]),
CompatiblePackage("com.zhiliaoapp.musically", ["36.5.4"]),
],
)
@Suppress("unused")
object DownloadsPatch : BytecodePatch(
@@ -38,9 +37,9 @@ object DownloadsPatch : BytecodePatch(
ACLCommonShareFingerprint,
ACLCommonShareFingerprint2,
ACLCommonShareFingerprint3,
DownloadPathParentFingerprint,
SettingsStatusLoadFingerprint
)
DownloadUriFingerprint,
SettingsStatusLoadFingerprint,
),
) {
override fun execute(context: BytecodeContext) {
fun MethodFingerprint.getMethod() = result?.mutableMethod ?: throw exception
@@ -52,7 +51,7 @@ object DownloadsPatch : BytecodePatch(
"""
const/4 v0, 0x0
return v0
"""
""",
)
},
ACLCommonShareFingerprint2 to {
@@ -61,7 +60,7 @@ object DownloadsPatch : BytecodePatch(
"""
const/4 v0, 0x2
return v0
"""
""",
)
},
// Download videos without watermark.
@@ -76,48 +75,40 @@ object DownloadsPatch : BytecodePatch(
return v0
:noremovewatermark
nop
"""
""",
)
},
// Change the download path patch.
DownloadPathParentFingerprint to {
val targetIndex = indexOfFirstInstructionOrThrow { opcode == Opcode.INVOKE_STATIC }
val downloadUriMethod = context
.toMethodWalker(this)
.nextMethod(targetIndex, true)
.getMethod() as MutableMethod
val firstIndex = downloadUriMethod.indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_DIRECT && ((this as Instruction35c).reference as MethodReference).name == "<init>"
DownloadUriFingerprint to {
val firstIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.name == "<init>"
}
val secondIndex = downloadUriMethod.indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_STATIC && ((this as Instruction35c).reference as MethodReference).returnType.contains(
"Uri"
)
val secondIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.returnType?.contains("Uri") == true
}
downloadUriMethod.addInstructions(
addInstructions(
secondIndex,
"""
invoke-static {}, Lapp/revanced/integrations/tiktok/download/DownloadsPatch;->getDownloadPath()Ljava/lang/String;
move-result-object v0
"""
invoke-static {}, Lapp/revanced/integrations/tiktok/download/DownloadsPatch;->getDownloadPath()Ljava/lang/String;
move-result-object v0
""",
)
downloadUriMethod.addInstructions(
addInstructions(
firstIndex,
"""
invoke-static {}, Lapp/revanced/integrations/tiktok/download/DownloadsPatch;->getDownloadPath()Ljava/lang/String;
move-result-object v0
"""
invoke-static {}, Lapp/revanced/integrations/tiktok/download/DownloadsPatch;->getDownloadPath()Ljava/lang/String;
move-result-object v0
""",
)
},
SettingsStatusLoadFingerprint to {
addInstruction(
0,
"invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableDownload()V"
"invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableDownload()V",
)
}
},
).forEach { (fingerprint, patch) ->
fingerprint.getMethod().patch()
}

View File

@@ -3,22 +3,18 @@ package app.revanced.patches.tiktok.interaction.downloads.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 DownloadPathParentFingerprint : MethodFingerprint(
"L",
internal object DownloadUriFingerprint : MethodFingerprint(
"Landroid/net/Uri;",
AccessFlags.PUBLIC or AccessFlags.STATIC,
strings = listOf(
"video/mp4"
"/",
"/Camera",
"/Camera/",
"video/mp4",
),
parameters = listOf(
"L",
"L"
"Landroid/content/Context;",
"Ljava/lang/String;",
),
opcodes = listOf(
Opcode.CONST_STRING,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.RETURN_OBJECT
)
)
)

View File

@@ -9,30 +9,33 @@ import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.tiktok.interaction.speed.fingerprints.GetSpeedFingerprint
import app.revanced.patches.tiktok.interaction.speed.fingerprints.OnRenderFirstFrameFingerprint
import app.revanced.patches.tiktok.interaction.speed.fingerprints.SetSpeedFingerprint
import app.revanced.patches.tiktok.shared.fingerprints.GetEnterFromFingerprint
import app.revanced.patches.tiktok.shared.fingerprints.OnRenderFirstFrameFingerprint
import app.revanced.util.exception
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction11x
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Patch(
name = "Playback speed",
description = "Enables the playback speed option for all videos and " +
"retains the speed configurations in between videos.",
"retains the speed configurations in between videos.",
compatiblePackages = [
CompatiblePackage("com.ss.android.ugc.trill", ["32.5.3"]),
CompatiblePackage("com.zhiliaoapp.musically", ["32.5.3"])
]
CompatiblePackage("com.ss.android.ugc.trill", ["36.5.4"]),
CompatiblePackage("com.zhiliaoapp.musically", ["36.5.4"]),
],
)
@Suppress("unused")
object PlaybackSpeedPatch : BytecodePatch(
setOf(
GetSpeedFingerprint,
OnRenderFirstFrameFingerprint,
SetSpeedFingerprint
)
SetSpeedFingerprint,
GetEnterFromFingerprint,
),
) {
override fun execute(context: BytecodeContext) {
SetSpeedFingerprint.result?.let { onVideoSwiped ->
@@ -44,7 +47,7 @@ object PlaybackSpeedPatch : BytecodePatch(
addInstruction(
injectIndex,
"invoke-static { v$register }," +
" Lapp/revanced/integrations/tiktok/speed/PlaybackSpeedPatch;->rememberPlaybackSpeed(F)V"
" Lapp/revanced/integrations/tiktok/speed/PlaybackSpeedPatch;->rememberPlaybackSpeed(F)V",
)
} ?: throw GetSpeedFingerprint.exception
@@ -53,29 +56,29 @@ object PlaybackSpeedPatch : BytecodePatch(
OnRenderFirstFrameFingerprint.result?.mutableMethod?.addInstructions(
0,
"""
# Video playback location (e.g. home page, following page or search result page) retrieved using getEnterFrom method.
const/4 v0, 0x1
invoke-virtual {p0, v0}, Lcom/ss/android/ugc/aweme/feed/panel/BaseListFragmentPanel;->getEnterFrom(Z)Ljava/lang/String;
move-result-object v0
# Model of current video retrieved using getCurrentAweme method.
invoke-virtual {p0}, Lcom/ss/android/ugc/aweme/feed/panel/BaseListFragmentPanel;->getCurrentAweme()Lcom/ss/android/ugc/aweme/feed/model/Aweme;
move-result-object v1
# Desired playback speed retrieved using getPlaybackSpeed method.
invoke-static {}, Lapp/revanced/integrations/tiktok/speed/PlaybackSpeedPatch;->getPlaybackSpeed()F
move-result-object v2
invoke-static { v0, v1, v2 }, ${onVideoSwiped.method}
"""
# Video playback location (e.g. home page, following page or search result page) retrieved using getEnterFrom method.
const/4 v0, 0x1
invoke-virtual {p0, v0}, ${GetEnterFromFingerprint.resultOrThrow().method}
move-result-object v0
# Model of current video retrieved using getCurrentAweme method.
invoke-virtual {p0}, Lcom/ss/android/ugc/aweme/feed/panel/BaseListFragmentPanel;->getCurrentAweme()Lcom/ss/android/ugc/aweme/feed/model/Aweme;
move-result-object v1
# Desired playback speed retrieved using getPlaybackSpeed method.
invoke-static {}, Lapp/revanced/integrations/tiktok/speed/PlaybackSpeedPatch;->getPlaybackSpeed()F
move-result v2
invoke-static { v0, v1, v2 }, ${onVideoSwiped.method}
""",
) ?: throw OnRenderFirstFrameFingerprint.exception
// Force enable the playback speed option for all videos.
onVideoSwiped.mutableClass.methods.find { method -> method.returnType == "Z" }?.addInstructions(
0,
"""
const/4 v0, 0x1
return v0
"""
const/4 v0, 0x1
return v0
""",
) ?: throw PatchException("Failed to force enable the playback speed option.")
} ?: throw SetSpeedFingerprint.exception
}

View File

@@ -24,9 +24,9 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
description = "Adds ReVanced settings to TikTok.",
dependencies = [IntegrationsPatch::class],
compatiblePackages = [
CompatiblePackage("com.ss.android.ugc.trill", ["32.5.3"]),
CompatiblePackage("com.zhiliaoapp.musically", ["32.5.3"])
]
CompatiblePackage("com.ss.android.ugc.trill", ["36.5.4"]),
CompatiblePackage("com.zhiliaoapp.musically", ["36.5.4"]),
],
)
object SettingsPatch : BytecodePatch(
setOf(
@@ -34,21 +34,21 @@ object SettingsPatch : BytecodePatch(
AddSettingsEntryFingerprint,
SettingsEntryFingerprint,
SettingsEntryInfoFingerprint,
)
),
) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/tiktok/settings/AdPersonalizationActivityHook;"
private const val INITIALIZE_SETTINGS_METHOD_DESCRIPTOR =
"$INTEGRATIONS_CLASS_DESCRIPTOR->initialize(" +
"Lcom/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity;" +
")Z"
"Lcom/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity;" +
")Z"
private const val CREATE_SETTINGS_ENTRY_METHOD_DESCRIPTOR =
"$INTEGRATIONS_CLASS_DESCRIPTOR->createSettingsEntry(" +
"Ljava/lang/String;" +
"Ljava/lang/String;" +
")Ljava/lang/Object;"
"Ljava/lang/String;" +
"Ljava/lang/String;" +
")Ljava/lang/Object;"
override fun execute(context: BytecodeContext) {
// Find the class name of classes which construct a settings entry
@@ -70,8 +70,8 @@ object SettingsPatch : BytecodePatch(
markIndex + 2,
listOf(
getUnitManager,
addEntry
)
addEntry,
),
)
addInstructions(
@@ -81,7 +81,8 @@ object SettingsPatch : BytecodePatch(
const-string v1, "$settingsButtonInfoClass"
invoke-static {v0, v1}, $CREATE_SETTINGS_ENTRY_METHOD_DESCRIPTOR
move-result-object v0
"""
check-cast v0, ${SettingsEntryFingerprint.result!!.classDef}
""",
)
} ?: throw AddSettingsEntryFingerprint.exception
@@ -102,12 +103,10 @@ object SettingsPatch : BytecodePatch(
if-eqz v$usableRegister, :do_not_open
return-void
""",
ExternalLabel("do_not_open", getInstruction(initializeSettingsIndex))
ExternalLabel("do_not_open", getInstruction(initializeSettingsIndex)),
)
} ?: throw AdPersonalizationActivityOnCreateFingerprint.exception
}
private fun String.toClassName(): String {
return substring(1, this.length - 1).replace("/", ".")
}
}
private fun String.toClassName(): String = substring(1, this.length - 1).replace("/", ".")
}

View File

@@ -0,0 +1,24 @@
package app.revanced.patches.tiktok.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 GetEnterFromFingerprint : MethodFingerprint(
returnType = "Ljava/lang/String;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Z"),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.RETURN_OBJECT,
),
customFingerprint = { methodDef, _ ->
methodDef.definingClass.endsWith("/BaseListFragmentPanel;")
},
)

View File

@@ -1,9 +1,10 @@
package app.revanced.patches.tiktok.interaction.speed.fingerprints
package app.revanced.patches.tiktok.shared.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object OnRenderFirstFrameFingerprint : MethodFingerprint(
strings = listOf("method_enable_viewpager_preload_duration"),
customFingerprint = { methodDef, _ ->
methodDef.definingClass.endsWith("/BaseListFragmentPanel;") && methodDef.name == "onRenderFirstFrame"
}
methodDef.definingClass.endsWith("/BaseListFragmentPanel;")
},
)

View File

@@ -12,7 +12,7 @@ import app.revanced.util.exception
name = "Open links with app chooser",
description = "Instead of opening links directly, open them with an app chooser. " +
"As a result you can select a browser to open the link with.",
compatiblePackages = [CompatiblePackage("com.twitter.android")],
compatiblePackages = [CompatiblePackage("com.twitter.android", ["10.48.0-release.0"])],
use = false,
)
@Suppress("unused")

View File

@@ -51,8 +51,8 @@ object CopyVideoUrlBytecodePatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) {
BUTTONS_DESCRIPTORS.forEach { descriptor ->
PlayerControlsBytecodePatch.initializeControl("$descriptor->initializeButton(Landroid/view/View;)V")
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$descriptor->changeVisibility(Z)V")
PlayerControlsBytecodePatch.initializeBottomControl(descriptor)
PlayerControlsBytecodePatch.injectVisibilityCheckCall(descriptor)
}
}
}

View File

@@ -5,7 +5,7 @@ import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
@@ -13,7 +13,7 @@ import app.revanced.util.copyResources
@Patch(
dependencies = [
SettingsPatch::class,
BottomControlsResourcePatch::class,
PlayerControlsResourcePatch::class,
AddResourcesPatch::class
]
)
@@ -34,6 +34,6 @@ internal object CopyVideoUrlResourcePatch : ResourcePatch() {
)
)
BottomControlsResourcePatch.addControls("copyvideourl")
PlayerControlsResourcePatch.addBottomControls("copyvideourl")
}
}

View File

@@ -58,8 +58,8 @@ object DownloadsPatch : BytecodePatch(
private const val BUTTON_DESCRIPTOR = "Lapp/revanced/integrations/youtube/videoplayer/ExternalDownloadButton;"
override fun execute(context: BytecodeContext) {
PlayerControlsBytecodePatch.initializeControl("$BUTTON_DESCRIPTOR->initializeButton(Landroid/view/View;)V")
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$BUTTON_DESCRIPTOR->changeVisibility(Z)V")
PlayerControlsBytecodePatch.initializeBottomControl(BUTTON_DESCRIPTOR)
PlayerControlsBytecodePatch.injectVisibilityCheckCall(BUTTON_DESCRIPTOR)
// Main activity is used to launch downloader intent.
MainActivityFingerprint.resultOrThrow().mutableMethod.apply {

View File

@@ -9,14 +9,14 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
@Patch(
dependencies = [
BottomControlsResourcePatch::class,
PlayerControlsResourcePatch::class,
SettingsPatch::class,
AddResourcesPatch::class,
],
@@ -42,6 +42,6 @@ internal object DownloadsResourcePatch : ResourcePatch() {
ResourceGroup("drawable", "revanced_yt_download_button.xml"),
)
BottomControlsResourcePatch.addControls("downloads")
PlayerControlsResourcePatch.addBottomControls("downloads")
}
}

View File

@@ -6,6 +6,7 @@ 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.patch.options.PatchOption.PatchExtensions.booleanPatchOption
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import app.revanced.patches.youtube.layout.hide.shorts.fingerprints.*
@@ -76,6 +77,13 @@ object HideShortsComponentsPatch : BytecodePatch(
) {
private const val FILTER_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/youtube/patches/components/ShortsFilter;"
internal val hideShortsAppShortcut by booleanPatchOption(
key = "hideShortsAppShortcut",
default = false,
title = "Hide Shorts app shortcut",
description = "Permanently hides the shortcut to open Shorts from long pressing the app icon in your launcher."
)
override fun execute(context: BytecodeContext) {
// region Hide the Shorts shelf.

View File

@@ -6,7 +6,10 @@ import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.layout.hide.shorts.HideShortsComponentsPatch.hideShortsAppShortcut
import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.util.findElementByAttributeValueOrThrow
import org.w3c.dom.Element
@Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class, AddResourcesPatch::class])
object HideShortsComponentsResourcePatch : ResourcePatch() {
@@ -38,6 +41,7 @@ object HideShortsComponentsResourcePatch : ResourcePatch() {
SwitchPreference("revanced_hide_shorts_subscribe_button"),
SwitchPreference("revanced_hide_shorts_paused_overlay_buttons"),
SwitchPreference("revanced_hide_shorts_save_sound_button"),
SwitchPreference("revanced_hide_shorts_use_this_sound_button"),
SwitchPreference("revanced_hide_shorts_shop_button"),
SwitchPreference("revanced_hide_shorts_tagged_products"),
SwitchPreference("revanced_hide_shorts_search_suggestions"),
@@ -51,6 +55,19 @@ object HideShortsComponentsResourcePatch : ResourcePatch() {
SwitchPreference("revanced_hide_shorts_navigation_bar"),
)
if (hideShortsAppShortcut == 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",
"shorts-shortcut"
)
shortsItem.parentNode.removeChild(shortsItem)
}
}
reelPlayerRightCellButtonHeight = ResourceMappingPatch[
"dimen",
"reel_player_right_cell_button_height",

View File

@@ -10,7 +10,6 @@ import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.AppendTimeFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.ControlsOverlayFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.RectangleFieldInvalidatorFingerprint
@@ -26,7 +25,10 @@ import app.revanced.patches.youtube.video.information.VideoInformationPatch
import app.revanced.patches.youtube.video.videoid.VideoIdPatch
import app.revanced.util.exception
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.*
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
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.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@@ -169,59 +171,14 @@ object SponsorBlockBytecodePatch : BytecodePatch(
break
}
/*
* Voting & Shield button
*/
val controlsMethodResult = PlayerControlsBytecodePatch.showPlayerControlsFingerprintResult
// Change visibility of the buttons.
PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
val controlsLayoutStubResourceId =
ResourceMappingPatch["id", "controls_layout_stub"]
val zoomOverlayResourceId =
ResourceMappingPatch["id", "video_zoom_overlay_stub"]
PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
methods@ for (method in controlsMethodResult.mutableClass.methods) {
val instructions = method.implementation?.instructions!!
instructions@ for ((index, instruction) in instructions.withIndex()) {
// search for method which inflates the controls layout view
if (instruction.opcode != Opcode.CONST) continue@instructions
when ((instruction as NarrowLiteralInstruction).wideLiteral) {
controlsLayoutStubResourceId -> {
// replace the view with the YouTubeControlsOverlay
val moveResultInstructionIndex = index + 5
val inflatedViewRegister =
(instructions[moveResultInstructionIndex] as OneRegisterInstruction).registerA
// initialize with the player overlay object
method.addInstructions(
moveResultInstructionIndex + 1, // insert right after moving the view to the register and use that register
"""
invoke-static {v$inflatedViewRegister}, $INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->initialize(Landroid/view/View;)V
invoke-static {v$inflatedViewRegister}, $INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->initialize(Landroid/view/View;)V
""",
)
}
zoomOverlayResourceId -> {
val invertVisibilityMethod =
context.toMethodWalker(method).nextMethod(index - 6, true).getMethod() as MutableMethod
// change visibility of the buttons
invertVisibilityMethod.addInstructions(
0,
"""
invoke-static {p1}, $INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibilityNegatedImmediate(Z)V
invoke-static {p1}, $INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibilityNegatedImmediate(Z)V
""".trimIndent(),
)
}
}
}
}
// change visibility of the buttons
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibility(Z)V")
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibility(Z)V")
// append the new time to the player layout
// Append the new time to the player layout.
val appendTimeFingerprintResult = AppendTimeFingerprint.result!!
val appendTimePatternScanStartIndex = appendTimeFingerprintResult.scanResult.patternScanResult!!.startIndex
val targetRegister =

View File

@@ -1,18 +1,16 @@
package app.revanced.patches.youtube.layout.sponsorblock
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.patches.youtube.misc.settings.SettingsResourcePatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.copyXmlNode
import app.revanced.util.inputStreamFromBundledResource
@Patch(
dependencies = [
@@ -60,49 +58,6 @@ internal object SponsorBlockResourcePatch : ResourcePatch() {
context.copyResources("sponsorblock", resourceGroup)
}
// copy nodes from host resources to their real xml files
val hostingResourceStream =
inputStreamFromBundledResource(
"sponsorblock",
"host/layout/youtube_controls_layout.xml",
)!!
var modifiedControlsLayout = false
val editor = context.xmlEditor["res/layout/youtube_controls_layout.xml"]
"RelativeLayout".copyXmlNode(
context.xmlEditor[hostingResourceStream],
editor,
).also {
val document = editor.file
val children = document.getElementsByTagName("RelativeLayout").item(0).childNodes
// Replace the startOf with the voting button view so that the button does not overlap
for (i in 1 until children.length) {
val view = children.item(i)
// Replace the attribute for a specific node only
if (!(
view.hasAttributes() &&
view.attributes.getNamedItem(
"android:id",
).nodeValue.endsWith("live_chat_overlay_button")
)
) {
continue
}
// voting button id from the voting button view from the youtube_controls_layout.xml host file
val votingButtonId = "@+id/revanced_sb_voting_button"
view.attributes.getNamedItem("android:layout_toStartOf").nodeValue = votingButtonId
modifiedControlsLayout = true
break
}
}.close()
if (!modifiedControlsLayout) throw PatchException("Could not modify controls layout")
PlayerControlsResourcePatch.addTopControls("sponsorblock")
}
}

View File

@@ -0,0 +1,13 @@
package app.revanced.patches.youtube.misc.check
import app.revanced.patches.shared.misc.checks.BaseCheckEnvironmentPatch
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
import app.revanced.patches.youtube.shared.fingerprints.MainActivityOnCreateFingerprint
@Suppress("unused")
object CheckEnvironmentPatch :
BaseCheckEnvironmentPatch(
mainActivityOnCreateFingerprint = MainActivityOnCreateFingerprint,
integrationsPatch = IntegrationsPatch,
compatiblePackages = setOf(CompatiblePackage("com.google.android.youtube")),
)

View File

@@ -1,399 +1,11 @@
package app.revanced.patches.youtube.misc.fix.playback
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.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.backgroundplayback.BackgroundPlaybackPatch
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.*
import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.util.getReference
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.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.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
@Patch(
name = "Spoof client",
description = "Spoofs the client to allow video playback.",
dependencies = [
SettingsPatch::class,
AddResourcesPatch::class,
UserAgentClientSpoofPatch::class,
// Required since iOS livestream fix partially enables background playback.
BackgroundPlaybackPatch::class,
PlayerTypeHookPatch::class,
],
compatiblePackages = [
CompatiblePackage(
"com.google.android.youtube",
[
// This patch works with these versions,
// but the dependent background playback patch does not.
// "18.37.36",
// "18.38.44",
// "18.43.45",
// "18.44.41",
// "18.45.43",
"18.48.39",
"18.49.37",
"19.01.34",
"19.02.39",
"19.03.36",
"19.04.38",
"19.05.36",
"19.06.39",
"19.07.40",
"19.08.36",
"19.09.38",
"19.10.39",
"19.11.43",
"19.12.41",
"19.13.37",
"19.14.43",
"19.15.36",
"19.16.39",
],
),
],
)
@Deprecated("This patch is obsolete.", replaceWith = ReplaceWith("SpoofVideoStreamsPatch"))
object SpoofClientPatch : BytecodePatch(
setOf(
// Client type spoof.
BuildInitPlaybackRequestFingerprint,
BuildPlayerRequestURIFingerprint,
SetPlayerRequestClientTypeFingerprint,
CreatePlayerRequestBodyFingerprint,
CreatePlayerRequestBodyWithModelFingerprint,
CreatePlayerRequestBodyWithVersionReleaseFingerprint,
// Player gesture config.
PlayerGestureConfigSyntheticFingerprint,
// Player speed menu item.
CreatePlaybackSpeedMenuItemFingerprint,
// Video qualities missing.
BuildRequestFingerprint,
// Livestream audio only background playback.
PlayerResponseModelBackgroundAudioPlaybackFingerprint,
)
dependencies = setOf(SpoofVideoStreamsPatch::class),
) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofClientPatch;"
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"
private const val REQUEST_CLASS_DESCRIPTOR =
"Lorg/chromium/net/ExperimentalUrlRequest;"
private const val REQUEST_BUILDER_CLASS_DESCRIPTOR =
"Lorg/chromium/net/ExperimentalUrlRequest\$Builder;"
override fun execute(context: BytecodeContext) {
AddResourcesPatch(this::class)
SettingsPatch.PreferenceScreen.MISC.addPreferences(
PreferenceScreen(
key = "revanced_spoof_client_screen",
sorting = PreferenceScreen.Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_spoof_client"),
ListPreference("revanced_spoof_client_type",
summaryKey = null,
entriesKey = "revanced_spoof_client_type_entries",
entryValuesKey = "revanced_spoof_client_type_entry_values"
),
SwitchPreference("revanced_spoof_client_ios_force_avc"),
NonInteractivePreference("revanced_spoof_client_about_android_ios"),
NonInteractivePreference("revanced_spoof_client_about_android_vr")
)
)
)
// region Block /initplayback requests to fall back to /get_watch requests.
BuildInitPlaybackRequestFingerprint.resultOrThrow().let {
val moveUriStringIndex = it.scanResult.patternScanResult!!.startIndex
it.mutableMethod.apply {
val targetRegister = getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
addInstructions(
moveUriStringIndex + 1,
"""
invoke-static { v$targetRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$targetRegister
""",
)
}
}
// endregion
// region Block /get_watch requests to fall back to /player requests.
BuildPlayerRequestURIFingerprint.resultOrThrow().let {
val invokeToStringIndex = it.scanResult.patternScanResult!!.startIndex
it.mutableMethod.apply {
val uriRegister = getInstruction<FiveRegisterInstruction>(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 Get field references to be used below.
val (clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField) =
SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result ->
// Field in the player request object that holds the client info object.
val clientInfoField = result.mutableMethod
.getInstructions().find { instruction ->
// requestMessage.clientInfo = clientInfoBuilder.build();
instruction.opcode == Opcode.IPUT_OBJECT &&
instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
}?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoField")
// Client info object's client type field.
val clientInfoClientTypeField = result.mutableMethod
.getInstruction(result.scanResult.patternScanResult!!.endIndex)
.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientTypeField")
// Client info object's client version field.
val clientInfoClientVersionField = result.mutableMethod
.getInstruction(result.scanResult.stringsScanResult!!.matches.first().index + 1)
.getReference<FieldReference>()
?: throw PatchException("Could not find clientInfoClientVersionField")
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
}
val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().let {
val getClientModelIndex =
CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction(it.method)
// The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
val index = it.mutableMethod.indexOfFirstInstructionOrThrow(getClientModelIndex) {
opcode == Opcode.IPUT_OBJECT
}
it.mutableMethod.getInstruction(index).getReference<FieldReference>()
?: throw PatchException("Could not find clientInfoClientModelField")
}
val clientInfoOsVersionField = CreatePlayerRequestBodyWithVersionReleaseFingerprint.resultOrThrow().let {
val getOsVersionIndex =
CreatePlayerRequestBodyWithVersionReleaseFingerprint.indexOfBuildVersionReleaseInstruction(it.method)
// The next IPUT_OBJECT instruction after getting the client os version is setting the client os version field.
val index = it.mutableMethod.indexOfFirstInstructionOrThrow(getOsVersionIndex) {
opcode == Opcode.IPUT_OBJECT
}
it.mutableMethod.getInstruction(index).getReference<FieldReference>()
?: throw PatchException("Could not find clientInfoOsVersionField")
}
// endregion
// region Spoof client type for /player requests.
CreatePlayerRequestBodyFingerprint.resultOrThrow().let { result ->
val setClientInfoMethodName = "patch_setClientInfo"
val checkCastIndex = result.scanResult.patternScanResult!!.startIndex
var clientInfoContainerClassName: String
result.mutableMethod.apply {
val checkCastInstruction = getInstruction<OneRegisterInstruction>(checkCastIndex)
val requestMessageInstanceRegister = checkCastInstruction.registerA
clientInfoContainerClassName = checkCastInstruction.getReference<TypeReference>()!!.type
addInstruction(
checkCastIndex + 1,
"invoke-static { v$requestMessageInstanceRegister }," +
" ${result.classDef.type}->$setClientInfoMethodName($clientInfoContainerClassName)V",
)
}
// Change client info to use the spoofed values.
// Do this in a helper method, to remove the need of picking out multiple free registers from the hooked code.
result.mutableClass.methods.add(
ImmutableMethod(
result.mutableClass.type,
setClientInfoMethodName,
listOf(ImmutableMethodParameter(clientInfoContainerClassName, null, "clientInfoContainer")),
"V",
AccessFlags.PRIVATE or AccessFlags.STATIC,
null,
null,
MutableMethodImplementation(3),
).toMutable().apply {
addInstructions(
"""
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isClientSpoofingEnabled()Z
move-result v0
if-eqz v0, :disabled
iget-object v0, p0, $clientInfoField
# Set client type to the spoofed value.
iget v1, v0, $clientInfoClientTypeField
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientTypeId(I)I
move-result v1
iput v1, v0, $clientInfoClientTypeField
# Set client model to the spoofed value.
iget-object v1, v0, $clientInfoClientModelField
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientModel(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $clientInfoClientModelField
# Set client version to the spoofed value.
iget-object v1, v0, $clientInfoClientVersionField
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $clientInfoClientVersionField
# Set client os version to the spoofed value.
iget-object v1, v0, $clientInfoOsVersionField
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getOsVersion(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $clientInfoOsVersionField
:disabled
return-void
""",
)
},
)
}
// endregion
// region Fix player gesture if spoofing to iOS.
PlayerGestureConfigSyntheticFingerprint.resultOrThrow().let {
val endIndex = it.scanResult.patternScanResult!!.endIndex
val downAndOutLandscapeAllowedIndex = endIndex - 3
val downAndOutPortraitAllowedIndex = endIndex - 9
arrayOf(
downAndOutLandscapeAllowedIndex,
downAndOutPortraitAllowedIndex,
).forEach { index ->
val gestureAllowedMethod = context.toMethodWalker(it.mutableMethod)
.nextMethod(index, true)
.getMethod() as MutableMethod
gestureAllowedMethod.apply {
val isAllowedIndex = getInstructions().lastIndex
val isAllowed = getInstruction<OneRegisterInstruction>(isAllowedIndex).registerA
addInstructions(
isAllowedIndex,
"""
invoke-static { v$isAllowed }, $INTEGRATIONS_CLASS_DESCRIPTOR->enablePlayerGesture(Z)Z
move-result v$isAllowed
""",
)
}
}
}
// endregion
// region Fix livestream audio only background play if spoofing to iOS.
// This force enables audio background playback.
PlayerResponseModelBackgroundAudioPlaybackFingerprint.resultOrThrow().mutableMethod.addInstructions(
0,
"""
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->overrideBackgroundAudioPlayback()Z
move-result v0
if-eqz v0, :do_not_override
return v0
:do_not_override
nop
"""
)
// endregion
// Fix playback speed menu item if spoofing to iOS.
CreatePlaybackSpeedMenuItemFingerprint.resultOrThrow().let {
val scanResult = it.scanResult.patternScanResult!!
if (scanResult.startIndex != 0) throw PatchException("Unexpected start index: ${scanResult.startIndex}")
it.mutableMethod.apply {
// Find the conditional check if the playback speed menu item is not created.
val shouldCreateMenuIndex =
indexOfFirstInstructionOrThrow(scanResult.endIndex) { opcode == Opcode.IF_EQZ }
val shouldCreateMenuRegister = getInstruction<OneRegisterInstruction>(shouldCreateMenuIndex).registerA
addInstructions(
shouldCreateMenuIndex,
"""
invoke-static { v$shouldCreateMenuRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->forceCreatePlaybackSpeedMenu(Z)Z
move-result v$shouldCreateMenuRegister
""",
)
}
}
// endregion
// region Fix video qualities missing, if spoofing to iOS by overriding the user agent.
BuildRequestFingerprint.resultOrThrow().let { result ->
result.mutableMethod.apply {
val buildRequestIndex = getInstructions().lastIndex - 2
val requestBuilderRegister = getInstruction<FiveRegisterInstruction>(buildRequestIndex).registerC
val newRequestBuilderIndex = result.scanResult.patternScanResult!!.endIndex
val urlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
// Replace "requestBuilder.build(): Request" with "overrideUserAgent(requestBuilder, url): Request".
replaceInstruction(
buildRequestIndex,
"invoke-static { v$requestBuilderRegister, v$urlRegister }, " +
"$INTEGRATIONS_CLASS_DESCRIPTOR->" +
"overrideUserAgent(${REQUEST_BUILDER_CLASS_DESCRIPTOR}Ljava/lang/String;)" +
REQUEST_CLASS_DESCRIPTOR
)
}
}
// endregion
}
}
override fun execute(context: BytecodeContext) {}
}

View File

@@ -1,239 +1,12 @@
package app.revanced.patches.youtube.misc.fix.playback
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.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.*
import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.patches.youtube.video.information.VideoInformationPatch
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
import app.revanced.util.exception
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
@Patch(
description = "Spoofs the signature to prevent playback issues.",
dependencies = [
SettingsPatch::class,
PlayerTypeHookPatch::class,
PlayerResponseMethodHookPatch::class,
VideoInformationPatch::class,
SpoofSignatureResourcePatch::class,
AddResourcesPatch::class,
],
)
@Deprecated("This patch will be removed in the future.")
@Deprecated("This patch is obsolete.", replaceWith = ReplaceWith("SpoofVideoStreamsPatch"))
object SpoofSignaturePatch : BytecodePatch(
setOf(
PlayerResponseModelImplGeneralFingerprint,
PlayerResponseModelImplLiveStreamFingerprint,
PlayerResponseModelImplRecommendedLevelFingerprint,
StoryboardRendererSpecFingerprint,
StoryboardRendererDecoderSpecFingerprint,
StoryboardRendererDecoderRecommendedLevelFingerprint,
StoryboardThumbnailParentFingerprint,
SpoofSignaturePatchScrubbedPreviewLayoutFingerprint,
StatsQueryParameterFingerprint,
ParamsMapPutFingerprint,
),
dependencies = setOf(SpoofVideoStreamsPatch::class),
) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch;"
override fun execute(context: BytecodeContext) {
AddResourcesPatch(this::class)
SettingsPatch.PreferenceScreen.MISC.addPreferences(
PreferenceScreen(
key = "revanced_spoof_signature_verification_screen",
sorting = Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_spoof_signature_verification_enabled"),
SwitchPreference("revanced_spoof_signature_in_feed_enabled"),
SwitchPreference("revanced_spoof_storyboard"),
),
),
)
// Hook the player parameters.
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter(
"$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
)
// Force the seekbar time and chapters to always show up.
// This is used if the storyboard spec fetch fails, for viewing paid videos,
// or if storyboard spoofing is turned off.
StoryboardThumbnailParentFingerprint.result?.classDef?.let { classDef ->
StoryboardThumbnailFingerprint.also {
it.resolve(
context,
classDef,
)
}.result?.let {
val endIndex = it.scanResult.patternScanResult!!.endIndex
// Replace existing instruction to preserve control flow label.
// The replaced return instruction always returns false
// (it is the 'no thumbnails found' control path),
// so there is no need to pass the existing return value to integrations.
it.mutableMethod.replaceInstruction(
endIndex,
"""
invoke-static {}, $INTEGRATIONS_CLASS_DESCRIPTOR->getSeekbarThumbnailOverrideValue()Z
""",
)
// Since this is end of the method must replace one line then add the rest.
it.mutableMethod.addInstructions(
endIndex + 1,
"""
move-result v0
return v0
""",
)
} ?: throw StoryboardThumbnailFingerprint.exception
}
// If storyboard spoofing is turned off, then hide the empty seekbar thumbnail view.
SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.result?.apply {
val endIndex = scanResult.patternScanResult!!.endIndex
mutableMethod.apply {
val imageViewFieldName = getInstruction<ReferenceInstruction>(endIndex).reference
addInstructions(
implementation!!.instructions.lastIndex,
"""
iget-object v0, p0, $imageViewFieldName # copy imageview field to a register
invoke-static {v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->seekbarImageViewCreated(Landroid/widget/ImageView;)V
""",
)
}
} ?: throw SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.exception
/**
* Hook StoryBoard renderer url
*/
arrayOf(
PlayerResponseModelImplGeneralFingerprint,
PlayerResponseModelImplLiveStreamFingerprint,
).forEach { fingerprint ->
fingerprint.result?.let {
it.mutableMethod.apply {
val getStoryBoardIndex = it.scanResult.patternScanResult!!.endIndex
val getStoryBoardRegister =
getInstruction<OneRegisterInstruction>(getStoryBoardIndex).registerA
addInstructions(
getStoryBoardIndex,
"""
invoke-static { v$getStoryBoardRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$getStoryBoardRegister
""",
)
}
} ?: throw fingerprint.exception
}
// Hook recommended seekbar thumbnails quality level.
StoryboardRendererDecoderRecommendedLevelFingerprint.result?.let {
val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex
val originalValueRegister = it.mutableMethod
.getInstruction<OneRegisterInstruction>(moveOriginalRecommendedValueIndex).registerA
it.mutableMethod.addInstructions(
moveOriginalRecommendedValueIndex + 1,
"""
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
move-result v$originalValueRegister
""",
)
} ?: throw StoryboardRendererDecoderRecommendedLevelFingerprint.exception
// Hook the recommended precise seeking thumbnails quality level.
PlayerResponseModelImplRecommendedLevelFingerprint.result?.let {
it.mutableMethod.apply {
val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex
val originalValueRegister =
getInstruction<OneRegisterInstruction>(moveOriginalRecommendedValueIndex).registerA
addInstructions(
moveOriginalRecommendedValueIndex,
"""
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
move-result v$originalValueRegister
""",
)
}
} ?: throw PlayerResponseModelImplRecommendedLevelFingerprint.exception
StoryboardRendererSpecFingerprint.result?.let {
it.mutableMethod.apply {
val storyBoardUrlParams = 0
addInstructionsWithLabels(
0,
"""
if-nez p$storyBoardUrlParams, :ignore
invoke-static { p$storyBoardUrlParams }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object p$storyBoardUrlParams
""",
ExternalLabel("ignore", getInstruction(0)),
)
}
} ?: throw StoryboardRendererSpecFingerprint.exception
// Hook the seekbar thumbnail decoder and use a NULL spec for live streams.
StoryboardRendererDecoderSpecFingerprint.result?.let {
val storyBoardUrlIndex = it.scanResult.patternScanResult!!.startIndex + 1
val storyboardUrlRegister =
it.mutableMethod.getInstruction<OneRegisterInstruction>(storyBoardUrlIndex).registerA
it.mutableMethod.addInstructions(
storyBoardUrlIndex + 1,
"""
invoke-static { v$storyboardUrlRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardDecoderRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$storyboardUrlRegister
""",
)
} ?: throw StoryboardRendererDecoderSpecFingerprint.exception
// Fix stats not being tracked.
// Due to signature spoofing "adformat" is present in query parameters made for /stats requests,
// even though, for regular videos, it should not be.
// This breaks stats tracking.
// Replace the ad parameter with the video parameter in the query parameters.
StatsQueryParameterFingerprint.result?.let {
val putMethod = ParamsMapPutFingerprint.result?.method?.toString()
?: throw ParamsMapPutFingerprint.exception
it.mutableMethod.apply {
val adParamIndex = it.scanResult.stringsScanResult!!.matches.first().index
val videoParamIndex = adParamIndex + 3
// Replace the ad parameter with the video parameter.
replaceInstruction(adParamIndex, getInstruction(videoParamIndex))
// Call paramsMap.put instead of paramsMap.putIfNotExist
// because the key is already present in the map.
val putAdParamIndex = adParamIndex + 1
val putIfKeyNotExistsInstruction = getInstruction<FiveRegisterInstruction>(putAdParamIndex)
replaceInstruction(
putAdParamIndex,
"invoke-virtual { " +
"v${putIfKeyNotExistsInstruction.registerC}, " +
"v${putIfKeyNotExistsInstruction.registerD}, " +
"v${putIfKeyNotExistsInstruction.registerE} }, " +
putMethod,
)
}
} ?: throw StatsQueryParameterFingerprint.exception
}
override fun execute(context: BytecodeContext) {}
}

View File

@@ -2,18 +2,8 @@ package app.revanced.patches.youtube.misc.fix.playback
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
@Patch(dependencies = [ResourceMappingPatch::class])
@Deprecated("This patch will be removed in the future.")
object SpoofSignatureResourcePatch : ResourcePatch() {
internal var scrubbedPreviewThumbnailResourceId: Long = -1
override fun execute(context: ResourceContext) {
scrubbedPreviewThumbnailResourceId = ResourceMappingPatch[
"id",
"thumbnail",
]
}
override fun execute(context: ResourceContext) {}
}

View File

@@ -0,0 +1,285 @@
package app.revanced.patches.youtube.misc.fix.playback
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.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
import app.revanced.patcher.extensions.or
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.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildInitPlaybackRequestFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildMediaDataSourceFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildPlayerRequestURIFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildRequestFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreateStreamingDataFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.ProtobufClassParseByteBufferFingerprint
import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.util.getReference
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.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.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.ImmutableMethodParameter
@Patch(
name = "Spoof video streams",
description = "Spoofs the client video streams to allow video playback.",
dependencies = [
SettingsPatch::class,
AddResourcesPatch::class,
UserAgentClientSpoofPatch::class,
],
compatiblePackages = [
CompatiblePackage(
"com.google.android.youtube",
[
"18.37.36",
"18.38.44",
"18.43.45",
"18.44.41",
"18.45.43",
"18.48.39",
"18.49.37",
"19.01.34",
"19.02.39",
"19.03.36",
"19.04.38",
"19.05.36",
"19.06.39",
"19.07.40",
"19.08.36",
"19.09.38",
"19.10.39",
"19.11.43",
"19.12.41",
"19.13.37",
"19.14.43",
"19.15.36",
"19.16.39",
],
),
],
)
object SpoofVideoStreamsPatch : BytecodePatch(
setOf(
BuildInitPlaybackRequestFingerprint,
BuildPlayerRequestURIFingerprint,
CreateStreamingDataFingerprint,
BuildMediaDataSourceFingerprint,
BuildRequestFingerprint,
ProtobufClassParseByteBufferFingerprint,
),
) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofVideoStreamsPatch;"
override fun execute(context: BytecodeContext) {
AddResourcesPatch(this::class)
SettingsPatch.PreferenceScreen.MISC.addPreferences(
PreferenceScreen(
key = "revanced_spoof_video_streams_screen",
sorting = PreferenceScreen.Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_spoof_video_streams"),
ListPreference(
"revanced_spoof_video_streams_client",
summaryKey = null
),
SwitchPreference(
"revanced_spoof_video_streams_ios_force_avc",
tag = "app.revanced.integrations.youtube.settings.preference.ForceAVCSpoofingPreference",
),
NonInteractivePreference("revanced_spoof_video_streams_about_android_vr"),
NonInteractivePreference("revanced_spoof_video_streams_about_ios"),
),
),
)
// region Block /initplayback requests to fall back to /get_watch requests.
BuildInitPlaybackRequestFingerprint.resultOrThrow().let {
val moveUriStringIndex = it.scanResult.patternScanResult!!.startIndex
it.mutableMethod.apply {
val targetRegister = getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
addInstructions(
moveUriStringIndex + 1,
"""
invoke-static { v$targetRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$targetRegister
""",
)
}
}
// endregion
// region Block /get_watch requests to fall back to /player requests.
BuildPlayerRequestURIFingerprint.resultOrThrow().let {
val invokeToStringIndex = it.scanResult.patternScanResult!!.startIndex
it.mutableMethod.apply {
val uriRegister = getInstruction<FiveRegisterInstruction>(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 Get replacement streams at player requests.
BuildRequestFingerprint.resultOrThrow().mutableMethod.apply {
val newRequestBuilderIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "newUrlRequestBuilder"
}
val urlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
val freeRegister = getInstruction<OneRegisterInstruction>(newRequestBuilderIndex + 1).registerA
addInstructions(
newRequestBuilderIndex,
"""
move-object v$freeRegister, p1
invoke-static { v$urlRegister, v$freeRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
""",
)
}
// endregion
// region Replace the streaming data with the replacement streams.
CreateStreamingDataFingerprint.resultOrThrow().let { result ->
result.mutableMethod.apply {
val setStreamDataMethodName = "patch_setStreamingData"
val resultMethodType = result.mutableClass.type
val videoDetailsIndex = result.scanResult.patternScanResult!!.endIndex
val videoDetailsRegister = getInstruction<TwoRegisterInstruction>(videoDetailsIndex).registerA
val videoDetailsClass = getInstruction(videoDetailsIndex).getReference<FieldReference>()!!.type
addInstruction(
videoDetailsIndex + 1,
"invoke-direct { p0, v$videoDetailsRegister }, " +
"$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V",
)
val protobufClass = ProtobufClassParseByteBufferFingerprint.resultOrThrow().mutableMethod.definingClass
val setStreamingDataIndex = result.scanResult.patternScanResult!!.startIndex
val playerProtoClass = getInstruction(setStreamingDataIndex + 1)
.getReference<FieldReference>()!!.definingClass
val setStreamingDataField = getInstruction(setStreamingDataIndex).getReference<FieldReference>()
val getStreamingDataField = getInstruction(
indexOfFirstInstructionOrThrow {
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.definingClass == playerProtoClass
}
).getReference<FieldReference>()
// Use a helper method to avoid the need of picking out multiple free registers from the hooked code.
result.mutableClass.methods.add(
ImmutableMethod(
resultMethodType,
setStreamDataMethodName,
listOf(ImmutableMethodParameter(videoDetailsClass, null, "videoDetails")),
"V",
AccessFlags.PRIVATE or AccessFlags.FINAL,
null,
null,
MutableMethodImplementation(9),
).toMutable().apply {
addInstructionsWithLabels(
0,
"""
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isSpoofingEnabled()Z
move-result v0
if-eqz v0, :disabled
# Get video id.
iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String;
if-eqz v2, :disabled
# Get streaming data.
invoke-static { v2 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer;
move-result-object v3
if-eqz v3, :disabled
# Parse streaming data.
sget-object v4, $playerProtoClass->a:$playerProtoClass
invoke-static { v4, v3 }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass
move-result-object v5
check-cast v5, $playerProtoClass
# Set streaming data.
iget-object v6, v5, $getStreamingDataField
if-eqz v6, :disabled
iput-object v6, p0, $setStreamingDataField
:disabled
return-void
""",
)
},
)
}
}
// endregion
// region Remove /videoplayback request body to fix playback.
// This is needed when using iOS client as streaming data source.
BuildMediaDataSourceFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
val targetIndex = getInstructions().lastIndex
// Instructions are added just before the method returns,
// so there's no concern of clobbering in-use registers.
addInstructions(
targetIndex,
"""
# Field a: Stream uri.
# Field c: Http method.
# Field d: Post data.
move-object v0, p0 # method has over 15 registers and must copy p0 to a lower register.
iget-object v1, v0, $definingClass->a:Landroid/net/Uri;
iget v2, v0, $definingClass->c:I
iget-object v3, v0, $definingClass->d:[B
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
""",
)
}
}
// endregion
}
}

View File

@@ -0,0 +1,22 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object BuildMediaDataSourceFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
returnType = "V",
parameters = listOf(
"Landroid/net/Uri;",
"J",
"I",
"[B",
"Ljava/util/Map;",
"J",
"J",
"Ljava/lang/String;",
"I",
"Ljava/lang/Object;"
)
)

View File

@@ -3,13 +3,34 @@ package app.revanced.patches.youtube.misc.fix.playback.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 BuildRequestFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
returnType = "Lorg/chromium/net/UrlRequest;",
opcodes = listOf(
Opcode.INVOKE_DIRECT,
Opcode.INVOKE_VIRTUAL
)
customFingerprint = { methodDef, _ ->
// Different targets have slightly different parameters
// Earlier targets have parameters:
//L
//Ljava/util/Map;
//[B
//L
//L
//L
//Lorg/chromium/net/UrlRequest$Callback;
// Later targets have parameters:
//L
//Ljava/util/Map;
//[B
//L
//L
//L
//Lorg/chromium/net/UrlRequest\$Callback;
//L
val parameterTypes = methodDef.parameterTypes
(parameterTypes.size == 7 || parameterTypes.size == 8)
&& parameterTypes[1] == "Ljava/util/Map;" // URL headers.
}
)

View File

@@ -1,34 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.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 CreatePlaybackSpeedMenuItemFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "V",
opcodes = listOf(
Opcode.IGET_OBJECT, // First instruction of the method
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.CONST_4,
Opcode.IF_EQZ,
Opcode.INVOKE_INTERFACE,
null // MOVE_RESULT or MOVE_RESULT_OBJECT, Return value controls the creation of the playback speed menu item.
),
// 19.01 and earlier is missing the second parameter.
// Since this fingerprint is somewhat weak, work around by checking for both method parameter signatures.
customFingerprint = custom@{ methodDef, _ ->
// 19.01 and earlier parameters are: "[L"
// 19.02+ parameters are "[L", "F"
val parameterTypes = methodDef.parameterTypes
val firstParameter = parameterTypes.firstOrNull()
if (firstParameter == null || !firstParameter.startsWith("[L")) {
return@custom false
}
parameterTypes.size == 1 || (parameterTypes.size == 2 && parameterTypes[1] == "F")
}
)

View File

@@ -1,15 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object CreatePlayerRequestBodyFingerprint : MethodFingerprint(
returnType = "V",
parameters = listOf("L"),
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.IGET,
Opcode.AND_INT_LIT16,
),
strings = listOf("ms"),
)

View File

@@ -1,31 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction
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.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf(),
customFingerprint = { methodDef, _ ->
methodDef.containsWideLiteralInstructionValue(1073741824) &&
indexOfBuildModelInstruction(methodDef) >= 0
},
) {
fun indexOfBuildModelInstruction(methodDef: Method) =
methodDef.indexOfFirstInstruction {
val reference = getReference<FieldReference>()
reference?.definingClass == "Landroid/os/Build;" &&
reference.name == "MODEL" &&
reference.type == "Ljava/lang/String;"
}
}

View File

@@ -1,31 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithVersionReleaseFingerprint.indexOfBuildVersionReleaseInstruction
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.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
internal object CreatePlayerRequestBodyWithVersionReleaseFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf(),
customFingerprint = { methodDef, _ ->
methodDef.containsWideLiteralInstructionValue(1073741824) &&
indexOfBuildVersionReleaseInstruction(methodDef) >= 0
},
) {
fun indexOfBuildVersionReleaseInstruction(methodDef: Method) =
methodDef.indexOfFirstInstruction {
val reference = getReference<FieldReference>()
reference?.definingClass == "Landroid/os/Build\$VERSION;" &&
reference.name == "RELEASE" &&
reference.type == "Ljava/lang/String;"
}
}

View File

@@ -0,0 +1,24 @@
package app.revanced.patches.youtube.misc.fix.playback.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 CreateStreamingDataFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
returnType = "V",
parameters = listOf("L"),
opcodes = listOf(
Opcode.IPUT_OBJECT,
Opcode.IGET_OBJECT,
Opcode.IF_NEZ,
Opcode.SGET_OBJECT,
Opcode.IPUT_OBJECT
),
customFingerprint = { methodDef, classDef ->
classDef.fields.any { field ->
field.name == "a" && field.type.endsWith("/StreamingDataOuterClass\$StreamingData;")
}
},
)

View File

@@ -1,25 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.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
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object ParamsMapPutFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf(
"Ljava/lang/String;",
"Ljava/lang/String;",
),
opcodes = listOf(
Opcode.CONST_4,
Opcode.CONST_4,
Opcode.CONST_4,
Opcode.MOVE_OBJECT,
Opcode.MOVE_OBJECT,
Opcode.MOVE_OBJECT,
Opcode.INVOKE_DIRECT_RANGE,
),
)

View File

@@ -1,49 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.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.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal object PlayerGestureConfigSyntheticFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Ljava/lang/Object;"),
opcodes = listOf(
Opcode.SGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_EQZ,
Opcode.IF_EQZ,
Opcode.IGET_OBJECT,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutLandscapeAllowed.
Opcode.MOVE_RESULT,
Opcode.CHECK_CAST,
Opcode.IPUT_BOOLEAN,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutPortraitAllowed.
Opcode.MOVE_RESULT,
Opcode.IPUT_BOOLEAN,
Opcode.RETURN_VOID,
),
customFingerprint = { methodDef, classDef ->
fun indexOfDownAndOutAllowedInstruction(methodDef: Method) =
methodDef.indexOfFirstInstruction {
val reference = getReference<MethodReference>()
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" &&
reference.parameterTypes.isEmpty() &&
reference.returnType == "Z"
}
// This method is always called "a" because this kind of class always has a single method.
methodDef.name == "a" && classDef.methods.count() == 2 &&
indexOfDownAndOutAllowedInstruction(methodDef) >= 0
},
)

View File

@@ -1,25 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.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 PlayerResponseModelBackgroundAudioPlaybackFingerprint : MethodFingerprint(
returnType = "Z",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"),
opcodes = listOf(
Opcode.CONST_4,
Opcode.IF_EQZ,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_NEZ,
Opcode.GOTO,
Opcode.RETURN,
null, // Opcode.CONST_4 or Opcode.MOVE
Opcode.RETURN,
)
)

View File

@@ -1,24 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.util.containsWideLiteralInstructionValue
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint(
returnType = "Ljava/lang/String;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.RETURN_OBJECT,
Opcode.CONST_4,
Opcode.RETURN_OBJECT,
),
customFingerprint = handler@{ methodDef, _ ->
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
methodDef.containsWideLiteralInstructionValue(55735497)
},
)

View File

@@ -1,24 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.util.containsWideLiteralInstructionValue
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object PlayerResponseModelImplLiveStreamFingerprint : MethodFingerprint(
returnType = "Ljava/lang/String;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.RETURN_OBJECT,
Opcode.CONST_4,
Opcode.RETURN_OBJECT,
),
customFingerprint = handler@{ methodDef, _ ->
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
methodDef.containsWideLiteralInstructionValue(70276274)
},
)

View File

@@ -1,24 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.util.containsWideLiteralInstructionValue
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFingerprint(
returnType = "I",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.SGET_OBJECT,
Opcode.IGET,
Opcode.RETURN,
),
customFingerprint = handler@{ methodDef, _ ->
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
methodDef.containsWideLiteralInstructionValue(55735497)
},
)

View File

@@ -0,0 +1,19 @@
package app.revanced.patches.youtube.misc.fix.playback.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 ProtobufClassParseByteBufferFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PROTECTED or AccessFlags.STATIC,
parameters = listOf("L", "Ljava/nio/ByteBuffer;"),
returnType = "L",
opcodes = listOf(
Opcode.SGET_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.RETURN_OBJECT,
),
customFingerprint = { methodDef, _ -> methodDef.name == "parseFrom" },
)

View File

@@ -1,13 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object SetPlayerRequestClientTypeFingerprint : LiteralValueFingerprint(
opcodes = listOf(
Opcode.IGET,
Opcode.IPUT, // Sets ClientInfo.clientId.
),
strings = listOf("10.29"),
literalSupplier = { 134217728 }
)

View File

@@ -1,28 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.misc.fix.playback.SpoofSignatureResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object SpoofSignaturePatchScrubbedPreviewLayoutFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
returnType = "V",
parameters = listOf("Landroid/content/Context;", "Landroid/util/AttributeSet;", "I", "I"),
opcodes = listOf(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.INVOKE_VIRTUAL,
Opcode.CONST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.IPUT_OBJECT, // preview imageview
),
// This resource is used in ~ 40 different locations, but this method has a distinct list of parameters to match to.
literalSupplier = { SpoofSignatureResourcePatch.scrubbedPreviewThumbnailResourceId },
)

View File

@@ -1,8 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StatsQueryParameterFingerprint : MethodFingerprint(
strings = listOf("adunit"),
)

View File

@@ -1,24 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.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
/**
* Resolves to the same method as [StoryboardRendererDecoderSpecFingerprint].
*/
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"),
opcodes = listOf(
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IPUT_OBJECT,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT,
),
strings = listOf("#-1#"),
)

View File

@@ -1,24 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.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
/**
* Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint].
*/
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"),
opcodes = listOf(
Opcode.INVOKE_INTERFACE, // First instruction of the method.
Opcode.MOVE_RESULT_OBJECT,
Opcode.CONST_4,
Opcode.CONST_4,
Opcode.IF_NEZ,
),
strings = listOf("#-1#"),
)

View File

@@ -1,13 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererSpecFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
returnType = "L",
parameters = listOf("Ljava/lang/String;", "J"),
strings = listOf("\\|"),
)

View File

@@ -1,24 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.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
/**
* Resolves using the class found in [StoryboardThumbnailParentFingerprint].
*/
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardThumbnailFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "Z",
parameters = listOf(),
opcodes = listOf(
Opcode.MOVE_RESULT,
Opcode.IF_GTZ,
Opcode.GOTO,
Opcode.CONST_4,
Opcode.RETURN,
Opcode.RETURN, // Last instruction of method.
),
)

View File

@@ -1,18 +0,0 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
/**
* Here lies code that creates the seekbar thumbnails.
*
* An additional change here might force the thumbnails to be created,
* or possibly a change somewhere else (maybe involving YouTube 18.23.35 class `hte`)
*/
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardThumbnailParentFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "Landroid/graphics/Bitmap;",
strings = listOf("Storyboard regionDecoder.decodeRegion exception - "),
)

View File

@@ -32,12 +32,11 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
CompatiblePackage(
"com.google.android.youtube",
setOf(
// Patch supports these versions but ClientSpoof does not.
// "18.37.36",
// "18.38.44",
// "18.43.45",
// "18.44.41",
// "18.45.43",
"18.37.36",
"18.38.44",
"18.43.45",
"18.44.41",
"18.45.43",
"18.48.39",
"18.49.37",
"19.01.34",

View File

@@ -3,70 +3,18 @@ package app.revanced.patches.youtube.misc.playercontrols
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.DomFileEditor
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import java.io.Closeable
@Patch(dependencies = [ResourceMappingPatch::class])
@Patch(
dependencies = [PlayerControlsBytecodePatch::class],
)
@Deprecated("Patch renamed to PlayerControlsResourcePatch", replaceWith = ReplaceWith("PlayerControlsBytecodePatch"))
object BottomControlsResourcePatch : ResourcePatch(), Closeable {
internal var bottomUiContainerResourceId: Long = -1
override fun execute(context: ResourceContext) {}
private const val TARGET_RESOURCE_NAME = "youtube_controls_bottom_ui_container.xml"
private const val TARGET_RESOURCE = "res/layout/$TARGET_RESOURCE_NAME"
// The element to the left of the element being added.
private var lastLeftOf = "fullscreen_button"
private lateinit var resourceContext: ResourceContext
private lateinit var targetDocumentEditor: DomFileEditor
override fun execute(context: ResourceContext) {
resourceContext = context
targetDocumentEditor = context.xmlEditor[TARGET_RESOURCE]
bottomUiContainerResourceId = ResourceMappingPatch["id", "bottom_ui_container_stub"]
}
/**
* Add new controls to the bottom of the YouTube player.
*
* @param resourceDirectoryName The name of the directory containing the hosting resource.
*/
fun addControls(resourceDirectoryName: String) {
val sourceDocumentEditor = resourceContext.xmlEditor[
this::class.java.classLoader.getResourceAsStream(
"$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME",
)!!,
]
val sourceDocument = sourceDocumentEditor.file
val targetDocument = targetDocumentEditor.file
val targetElementTag = "android.support.constraint.ConstraintLayout"
val sourceElements = sourceDocument.getElementsByTagName(targetElementTag).item(0).childNodes
val targetElement = targetDocument.getElementsByTagName(targetElementTag).item(0)
for (index in 1 until sourceElements.length) {
val element = sourceElements.item(index).cloneNode(true)
// If the element has no attributes there's no point to adding it to the destination.
if (!element.hasAttributes()) continue
// Set the elements lastLeftOf attribute to the lastLeftOf value.
val namespace = "@+id"
element.attributes.getNamedItem("yt:layout_constraintRight_toLeftOf").nodeValue =
"$namespace/$lastLeftOf"
// Set lastLeftOf attribute to the current element.
val nameSpaceLength = 5
lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength)
// Add the element.
targetDocument.adoptNode(element)
targetElement.appendChild(element)
}
sourceDocumentEditor.close()
PlayerControlsResourcePatch.addBottomControls(resourceDirectoryName)
}
override fun close() = targetDocumentEditor.close()
}
override fun close() {}
}

View File

@@ -1,65 +1,144 @@
package app.revanced.patches.youtube.misc.playercontrols
import app.revanced.util.exception
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.fingerprint.MethodFingerprintResult
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.shared.fingerprints.LayoutConstructorFingerprint
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.BottomControlsInflateFingerprint
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsVisibilityFingerprint
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.ControlsOverlayVisibility
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.OverlayViewInflateFingerprint
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerBottomControlsInflateFingerprint
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsIntegrationHookFingerprint
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerTopControlsInflateFingerprint
import app.revanced.util.alsoResolve
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstWideLiteralInstructionValueReversedOrThrow
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
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
@Patch(
description = "Manages the code for the player controls of the YouTube player.",
dependencies = [BottomControlsResourcePatch::class],
dependencies = [PlayerControlsResourcePatch::class],
)
object PlayerControlsBytecodePatch : BytecodePatch(
setOf(LayoutConstructorFingerprint, BottomControlsInflateFingerprint)
setOf(
PlayerTopControlsInflateFingerprint,
PlayerBottomControlsInflateFingerprint,
OverlayViewInflateFingerprint,
PlayerControlsIntegrationHookFingerprint
)
) {
lateinit var showPlayerControlsFingerprintResult: MethodFingerprintResult
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/youtube/patches/PlayerControlsPatch;"
private var moveToRegisterInstructionIndex: Int = 0
private var viewRegister: Int = 0
private lateinit var inflateFingerprintResult: MethodFingerprintResult
private lateinit var inflateTopControlMethod: MutableMethod
private var inflateTopControlInsertIndex: Int = -1
private var inflateTopControlRegister: Int = -1
private lateinit var inflateBottomControlMethod: MutableMethod
private var inflateBottomControlInsertIndex: Int = -1
private var inflateBottomControlRegister: Int = -1
private lateinit var visibilityMethod: MutableMethod
private var visibilityInsertIndex: Int = 0
private lateinit var visibilityImmediateMethod: MutableMethod
private var visibilityImmediateInsertIndex: Int = 0
override fun execute(context: BytecodeContext) {
LayoutConstructorFingerprint.result?.let {
if (!PlayerControlsVisibilityFingerprint.resolve(context, it.classDef))
throw LayoutConstructorFingerprint.exception
} ?: throw LayoutConstructorFingerprint.exception
fun MutableMethod.indexOfFirstViewInflateOrThrow() =
indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/view/ViewStub;" &&
reference.name == "inflate"
}
showPlayerControlsFingerprintResult = PlayerControlsVisibilityFingerprint.result!!
PlayerBottomControlsInflateFingerprint.resultOrThrow().mutableMethod.apply{
inflateBottomControlMethod = this
inflateFingerprintResult = BottomControlsInflateFingerprint.result!!.also {
moveToRegisterInstructionIndex = it.scanResult.patternScanResult!!.endIndex
viewRegister =
(it.mutableMethod.implementation!!.instructions[moveToRegisterInstructionIndex] as OneRegisterInstruction).registerA
val inflateReturnObjectIndex = indexOfFirstViewInflateOrThrow() + 1
inflateBottomControlRegister = getInstruction<OneRegisterInstruction>(inflateReturnObjectIndex).registerA
inflateBottomControlInsertIndex = inflateReturnObjectIndex + 1
}
PlayerTopControlsInflateFingerprint.resultOrThrow().mutableMethod.apply {
inflateTopControlMethod = this
val inflateReturnObjectIndex = indexOfFirstViewInflateOrThrow() + 1
inflateTopControlRegister = getInstruction<OneRegisterInstruction>(inflateReturnObjectIndex).registerA
inflateTopControlInsertIndex = inflateReturnObjectIndex + 1
}
ControlsOverlayVisibility.alsoResolve(
context, PlayerTopControlsInflateFingerprint
).mutableMethod.apply {
visibilityMethod = this
}
// Hook the fullscreen close button. Used to fix visibility
// when seeking and other situations.
OverlayViewInflateFingerprint.resultOrThrow().mutableMethod.apply {
val resourceIndex = indexOfFirstWideLiteralInstructionValueReversedOrThrow(
PlayerControlsResourcePatch.fullscreenButton
)
val index = indexOfFirstInstructionOrThrow(resourceIndex) {
opcode == Opcode.CHECK_CAST && getReference<TypeReference>()?.type ==
"Landroid/widget/ImageView;"
}
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstruction(index + 1, "invoke-static { v$register }, " +
"$INTEGRATIONS_CLASS_DESCRIPTOR->setFullscreenCloseButton(Landroid/widget/ImageView;)V")
}
visibilityImmediateMethod = PlayerControlsIntegrationHookFingerprint.resultOrThrow().mutableMethod
}
/**
* Injects the code to change the visibility of controls.
* Injects the code to initialize the controls.
* @param descriptor The descriptor of the method which should be called.
*/
fun injectVisibilityCheckCall(descriptor: String) {
showPlayerControlsFingerprintResult.mutableMethod.addInstruction(
0,
"""
invoke-static {p1}, $descriptor
"""
internal fun initializeTopControl(descriptor: String) {
inflateTopControlMethod.addInstruction(
inflateTopControlInsertIndex++,
"invoke-static { v$inflateTopControlRegister }, $descriptor->initialize(Landroid/view/View;)V"
)
}
/**
* Injects the code to initialize the controls.
* @param descriptor The descriptor of the method which should be calleed.
* @param descriptor The descriptor of the method which should be called.
*/
fun initializeControl(descriptor: String) {
inflateFingerprintResult.mutableMethod.addInstruction(
moveToRegisterInstructionIndex + 1,
"invoke-static {v$viewRegister}, $descriptor"
fun initializeBottomControl(descriptor: String) {
inflateBottomControlMethod.addInstruction(
inflateBottomControlInsertIndex++,
"invoke-static { v$inflateBottomControlRegister }, $descriptor->initializeButton(Landroid/view/View;)V"
)
}
/**
* Injects the code to change the visibility of controls.
* @param descriptor The descriptor of the method which should be called.
*/
fun injectVisibilityCheckCall(descriptor: String) {
visibilityMethod.addInstruction(
visibilityInsertIndex++,
"invoke-static { p1 , p2 }, $descriptor->changeVisibility(ZZ)V"
)
visibilityImmediateMethod.addInstruction(
visibilityImmediateInsertIndex++,
"invoke-static { p0 }, $descriptor->changeVisibilityImmediate(Z)V"
)
}
@Deprecated("Obsolete", replaceWith = ReplaceWith("initializeBottomControl"))
fun initializeControl(descriptor: String)= initializeBottomControl(descriptor)
}

View File

@@ -0,0 +1,146 @@
package app.revanced.patches.youtube.misc.playercontrols
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.DomFileEditor
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import app.revanced.util.copyXmlNode
import app.revanced.util.findElementByAttributeValue
import app.revanced.util.findElementByAttributeValueOrThrow
import app.revanced.util.inputStreamFromBundledResource
import org.w3c.dom.Node
import java.io.Closeable
@Patch(dependencies = [ResourceMappingPatch::class])
object PlayerControlsResourcePatch : ResourcePatch(), Closeable {
private const val TARGET_RESOURCE_NAME = "youtube_controls_bottom_ui_container.xml"
private const val TARGET_RESOURCE = "res/layout/$TARGET_RESOURCE_NAME"
internal var bottomUiContainerResourceId: Long = -1L
internal var controlsLayoutStub: Long = -1L
internal var heatseekerViewstub = -1L
internal var fullscreenButton = -1L
private lateinit var resourceContext: ResourceContext
/**
* The element to the left of the element being added.
*/
private var bottomLastLeftOf = "@id/fullscreen_button"
private lateinit var bottomInsertBeforeNode: Node
private lateinit var bottomTargetDocumentEditor: DomFileEditor
private lateinit var bottomTargetElement : Node
override fun execute(context: ResourceContext) {
bottomUiContainerResourceId = ResourceMappingPatch["id", "bottom_ui_container_stub"]
controlsLayoutStub = ResourceMappingPatch["id", "controls_layout_stub"]
heatseekerViewstub = ResourceMappingPatch["id", "heatseeker_viewstub"]
fullscreenButton = ResourceMappingPatch["id", "fullscreen_button"]
resourceContext = context
bottomTargetDocumentEditor = context.xmlEditor[TARGET_RESOURCE]
val document = bottomTargetDocumentEditor.file
bottomTargetElement = document.getElementsByTagName(
"android.support.constraint.ConstraintLayout"
).item(0)
bottomInsertBeforeNode = document.childNodes.findElementByAttributeValue(
"android:inflatedId",
bottomLastLeftOf
) ?: document.childNodes.findElementByAttributeValueOrThrow(
"android:id", // Older targets use non inflated id.
bottomLastLeftOf
)
}
// Internal until this is modified to work with any patch (and not just SponsorBlock).
internal fun addTopControls(resourceDirectoryName: String) {
val hostingResourceStream = inputStreamFromBundledResource(
resourceDirectoryName,
"host/layout/youtube_controls_layout.xml",
)!!
val editor = resourceContext.xmlEditor["res/layout/youtube_controls_layout.xml"]
"RelativeLayout".copyXmlNode(
resourceContext.xmlEditor[hostingResourceStream],
editor,
).use {
val document = editor.file
val children = document.getElementsByTagName("RelativeLayout").item(0).childNodes
// Replace the startOf with the voting button view so that the button does not overlap
for (index in 1 until children.length) {
val view = children.item(index)
// FIXME: This uses hard coded values that only works with SponsorBlock.
// If other top buttons are added by other patches, this code must be changed.
if (view.hasAttributes() && view.attributes.getNamedItem("android:id")
.nodeValue.endsWith("live_chat_overlay_button")
) {
// voting button id from the voting button view from the youtube_controls_layout.xml host file
val votingButtonId = "@+id/revanced_sb_voting_button"
view.attributes.getNamedItem("android:layout_toStartOf").nodeValue =
votingButtonId
return
}
}
}
throw PatchException("Could not find expected xml to modify")
}
/**
* Add new controls to the bottom of the YouTube player.
*
* @param resourceDirectoryName The name of the directory containing the hosting resource.
*/
fun addBottomControls(resourceDirectoryName: String) {
val sourceDocumentEditor = resourceContext.xmlEditor[
this::class.java.classLoader.getResourceAsStream(
"$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME",
)!!,
]
val sourceElements = sourceDocumentEditor.file.getElementsByTagName(
"android.support.constraint.ConstraintLayout"
).item(0).childNodes
// Copy the patch layout xml into the target layout file.
for (index in 1 until sourceElements.length) {
val element = sourceElements.item(index).cloneNode(true)
// If the element has no attributes there's no point to adding it to the destination.
if (!element.hasAttributes()) continue
element.attributes.getNamedItem("yt:layout_constraintRight_toLeftOf").nodeValue = bottomLastLeftOf
bottomLastLeftOf = element.attributes.getNamedItem("android:id").nodeValue
bottomTargetDocumentEditor.file.adoptNode(element)
// Elements do not need to be added in the layout order since a layout constraint is used,
// but in order is easier to make sense of while debugging.
bottomTargetElement.insertBefore(element, bottomInsertBeforeNode)
bottomInsertBeforeNode = element
}
sourceDocumentEditor.close()
}
override fun close() {
arrayOf(
"@id/bottom_end_container",
"@id/multiview_button",
).forEach {
bottomTargetDocumentEditor.file.childNodes.findElementByAttributeValue(
"android:id",
it
)?.setAttribute("yt:layout_constraintRight_toLeftOf", bottomLastLeftOf)
}
bottomTargetDocumentEditor.close()
}
}

View File

@@ -1,19 +0,0 @@
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal object BottomControlsInflateFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.SYNTHETIC,
returnType = "L",
parameters = listOf(),
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT
),
literalSupplier = { BottomControlsResourcePatch.bottomUiContainerResourceId }
)

View File

@@ -4,7 +4,10 @@ import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object PlayerControlsVisibilityFingerprint : MethodFingerprint(
/**
* Resolves to the class found in [PlayerTopControlsInflateFingerprint].
*/
internal object ControlsOverlayVisibility : MethodFingerprint(
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
returnType = "V",
parameters = listOf("Z", "Z")

View File

@@ -0,0 +1,17 @@
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.util.containsWideLiteralInstructionValue
import com.android.tools.smali.dexlib2.AccessFlags
internal object OverlayViewInflateFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "V",
parameters = listOf("Landroid/view/View;"),
customFingerprint = { methodDef, _ ->
methodDef.containsWideLiteralInstructionValue(PlayerControlsResourcePatch.fullscreenButton) &&
methodDef.containsWideLiteralInstructionValue(PlayerControlsResourcePatch.heatseekerViewstub)
}
)

View File

@@ -0,0 +1,10 @@
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint
internal object PlayerBottomControlsInflateFingerprint : LiteralValueFingerprint(
returnType = "Ljava/lang/Object;",
parameters = listOf(),
literalSupplier = { PlayerControlsResourcePatch.bottomUiContainerResourceId }
)

View File

@@ -0,0 +1,15 @@
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object PlayerControlsIntegrationHookFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
returnType = "V",
parameters = listOf("Z"),
customFingerprint = { methodDef, classDef ->
methodDef.name == "fullscreenButtonVisibilityChanged" &&
classDef.type == "Lapp/revanced/integrations/youtube/patches/PlayerControlsPatch;"
}
)

View File

@@ -0,0 +1,13 @@
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object PlayerTopControlsInflateFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "V",
parameters = listOf(),
literalSupplier = { PlayerControlsResourcePatch.controlsLayoutStub }
)

View File

@@ -15,6 +15,7 @@ import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.misc.check.CheckEnvironmentPatch
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.fingerprints.LicenseActivityOnCreateFingerprint
import app.revanced.patches.youtube.misc.settings.fingerprints.SetThemeFingerprint
@@ -30,6 +31,9 @@ import java.io.Closeable
IntegrationsPatch::class,
SettingsResourcePatch::class,
AddResourcesPatch::class,
// Currently there is no easy way to make a mandatory patch,
// so for now this is a dependent of this patch.
CheckEnvironmentPatch::class,
],
)
object SettingsPatch :

View File

@@ -28,8 +28,6 @@ object SettingsResourcePatch : BaseSettingsResourcePatch(
override fun execute(context: ResourceContext) {
super.execute(context)
AddResourcesPatch(this::class)
// Used for a fingerprint from SettingsPatch.
appearanceStringId = ResourceMappingPatch["string", "app_theme_appearance_dark"]

View File

@@ -32,7 +32,7 @@ object PlaybackSpeedButtonPatch : BytecodePatch(emptySet()) {
SwitchPreference("revanced_playback_speed_dialog_button"),
)
PlayerControlsBytecodePatch.initializeControl("$SPEED_BUTTON_CLASS_DESCRIPTOR->initializeButton(Landroid/view/View;)V")
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$SPEED_BUTTON_CLASS_DESCRIPTOR->changeVisibility(Z)V")
PlayerControlsBytecodePatch.initializeBottomControl(SPEED_BUTTON_CLASS_DESCRIPTOR)
PlayerControlsBytecodePatch.injectVisibilityCheckCall(SPEED_BUTTON_CLASS_DESCRIPTOR)
}
}

View File

@@ -3,12 +3,12 @@ package app.revanced.patches.youtube.video.speed.button
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
@Patch(
dependencies = [BottomControlsResourcePatch::class],
dependencies = [PlayerControlsResourcePatch::class],
)
internal object PlaybackSpeedButtonResourcePatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
@@ -20,6 +20,6 @@ internal object PlaybackSpeedButtonResourcePatch : ResourcePatch() {
),
)
BottomControlsResourcePatch.addControls("speedbutton")
PlayerControlsResourcePatch.addBottomControls("speedbutton")
}
}

View File

@@ -15,6 +15,7 @@ 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.reference.Reference
import com.android.tools.smali.dexlib2.util.MethodUtil
import org.stringtemplate.v4.compiler.Bytecode.instructions
fun MethodFingerprint.resultOrThrow() = result ?: throw exception
@@ -73,7 +74,7 @@ fun MutableMethod.injectHideViewCall(
* @param resourceName the name of the resource to find the id for.
* @return the index of the first instruction with the id of the given resource name, or -1 if not found.
* @throws PatchException if the resource cannot be found.
* @see [indexOfIdResourceOrThrow]
* @see [indexOfIdResourceOrThrow], [indexOfFirstWideLiteralInstructionValueReversed]
*/
fun Method.indexOfIdResource(resourceName: String): Int {
val resourceId = ResourceMappingPatch["id", resourceName]
@@ -86,6 +87,7 @@ fun Method.indexOfIdResource(resourceName: String): Int {
* Requires [ResourceMappingPatch] as a dependency.
*
* @throws [PatchException] if the resource is not found, or the method does not contain the resource id literal value.
* @see [indexOfIdResource], [indexOfFirstWideLiteralInstructionValueReversedOrThrow]
*/
fun Method.indexOfIdResourceOrThrow(resourceName: String): Int {
val index = indexOfIdResource(resourceName)
@@ -120,6 +122,30 @@ fun Method.indexOfFirstWideLiteralInstructionValueOrThrow(literal: Long): Int {
return index
}
/**
* Find the index of the last wide literal instruction with the given value.
*
* @return the last literal instruction with the value, or -1 if not found.
* @see indexOfFirstWideLiteralInstructionValueOrThrow
*/
fun Method.indexOfFirstWideLiteralInstructionValueReversed(literal: Long) = implementation?.let {
it.instructions.indexOfLast { instruction ->
(instruction as? WideLiteralInstruction)?.wideLiteral == literal
}
} ?: -1
/**
* Find the index of the last wide literal instruction with the given value,
* or throw an exception if not found.
*
* @return the last literal instruction with the value, or throws [PatchException] if not found.
*/
fun Method.indexOfFirstWideLiteralInstructionValueReversedOrThrow(literal: Long): Int {
val index = indexOfFirstWideLiteralInstructionValueReversed(literal)
if (index < 0) throw PatchException("Could not find literal value: $literal")
return index
}
/**
* Check if the method contains a literal with the given value.
*

View File

@@ -1,8 +1,11 @@
package app.revanced.util
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.util.DomFileEditor
import app.revanced.util.resource.BaseResource
import org.w3c.dom.Attr
import org.w3c.dom.Element
import org.w3c.dom.Node
import org.w3c.dom.NodeList
import java.io.InputStream
@@ -39,6 +42,14 @@ fun Node.doRecursively(action: (Node) -> Unit) {
for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action)
}
fun Node.insertFirst(node: Node) {
if (hasChildNodes()) {
insertBefore(node, firstChild)
} else {
appendChild(node)
}
}
/**
* Copy resources from the current class loader to the resource directory.
*
@@ -49,7 +60,7 @@ fun ResourceContext.copyResources(
sourceResourceDirectory: String,
vararg resources: ResourceGroup,
) {
val targetResourceDirectory = this.get("res")
val targetResourceDirectory = this["res", false]
for (resourceGroup in resources) {
resourceGroup.resources.forEach { resource ->
@@ -164,3 +175,37 @@ internal fun Node.addResource(
}
internal fun org.w3c.dom.Document.getNode(tagName: String) = this.getElementsByTagName(tagName).item(0)
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")
}
internal fun Element.copyAttributesFrom(oldContainer: Element) {
// Copy attributes from the old element to the new element
val attributes = oldContainer.attributes
for (i in 0 until attributes.length) {
val attr = attributes.item(i) as Attr
setAttribute(attr.name, attr.value)
}
}

View File

@@ -32,15 +32,16 @@ This is because Crowdin requires temporarily flattening this file and removing t
-->
<resources>
<app id="shared">
<patch id="misc.checks.BaseCheckEnvironmentPatch">
</patch>
<patch id="misc.settings.BaseSettingsResourcePatch">
<!-- Settings about dialog. -->
</patch>
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
</patch>
</app>
<app id="youtube">
<patch id="misc.settings.SettingsResourcePatch">
</patch>
<patch id="misc.settings.SettingsPatch">
</patch>
<patch id="misc.debugging.DebuggingPatch">
@@ -57,7 +58,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<!-- 'Component path builder strings' is the technical name for identifying the Litho UI layout items to hide. This is an advanced feature and most users will never use this. -->
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
</patch>
<patch id="ad.general.HideAdsResourcePatch">
@@ -232,10 +233,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch>
<patch id="interaction.seekbar.EnableSlideToSeekPatch">
</patch>
<patch id="misc.fix.playback.SpoofClientPatch">
</patch>
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
<patch id="misc.fix.playback.SpoofSignaturePatch">
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
</patch>
<!-- This patch is no longer used and these strings will soon be deleted. -->
<patch id="video.hdrbrightness.HDRBrightnessPatch">

View File

@@ -32,15 +32,16 @@ This is because Crowdin requires temporarily flattening this file and removing t
-->
<resources>
<app id="shared">
<patch id="misc.checks.BaseCheckEnvironmentPatch">
</patch>
<patch id="misc.settings.BaseSettingsResourcePatch">
<!-- Settings about dialog. -->
</patch>
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
</patch>
</app>
<app id="youtube">
<patch id="misc.settings.SettingsResourcePatch">
</patch>
<patch id="misc.settings.SettingsPatch">
</patch>
<patch id="misc.debugging.DebuggingPatch">
@@ -57,7 +58,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<!-- 'Component path builder strings' is the technical name for identifying the Litho UI layout items to hide. This is an advanced feature and most users will never use this. -->
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
</patch>
<patch id="ad.general.HideAdsResourcePatch">
@@ -232,10 +233,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch>
<patch id="interaction.seekbar.EnableSlideToSeekPatch">
</patch>
<patch id="misc.fix.playback.SpoofClientPatch">
</patch>
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
<patch id="misc.fix.playback.SpoofSignaturePatch">
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
</patch>
<!-- This patch is no longer used and these strings will soon be deleted. -->
<patch id="video.hdrbrightness.HDRBrightnessPatch">

View File

@@ -32,6 +32,17 @@ This is because Crowdin requires temporarily flattening this file and removing t
-->
<resources>
<app id="shared">
<patch id="misc.checks.BaseCheckEnvironmentPatch">
<string name="revanced_check_environment_failed_title">ŲØ´Ų„ØĒ ØšŲ…Ų„ŲŠØ§ØĒ Ø§Ų„ØĒØ­Ų‚Ų‚</string>
<string name="revanced_check_environment_dialog_open_official_source_button">؁ØĒØ­ Ø§Ų„Ų…ŲˆŲ‚Øš Ø§Ų„ØąØŗŲ…ŲŠ</string>
<string name="revanced_check_environment_dialog_ignore_button">ØĒØŦØ§Ų‡Ų„</string>
<string name="revanced_check_environment_failed_message">&lt;h5&gt;Ų„Ø§ ŲŠØ¨Ø¯Ųˆ ØŖŲ† Ų‡Ø°Ø§ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ Ų‚Ø¯ ØĒŲ… ØĒØšØ¯ŲŠŲ„Ų‡ Ų…Ų† Ų‚Ø¨Ų„Ųƒ.&lt;/h5&gt;&lt;br&gt;Ų‚Ø¯ Ų„Ø§ ŲŠØšŲ…Ų„ Ų‡Ø°Ø§ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ Ø¨Ø´ŲƒŲ„ ØĩØ­ŲŠØ­ØŒ &lt;b&gt;Ų‚Ø¯ ŲŠŲƒŲˆŲ† ØļØ§ØąŲ‹Ø§ ØŖŲˆ Ø­ØĒŲ‰ ØŽØˇŲŠØąŲ‹Ø§ Ų„Ų„Ø§ØŗØĒØŽØ¯Ø§Ų…&lt;/b&gt;.&lt;br&gt;&lt;br&gt;ØĒØ´ŲŠØą Ų‡Ø°Ų‡ Ø§Ų„ŲØ­ŲˆØĩاØĒ ØĨŲ„Ų‰ ØŖŲ† Ų‡Ø°Ø§ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ ØĒŲ… ØĒØšØ¯ŲŠŲ„Ų‡ Ų…ØŗØ¨Ų‚Ų‹Ø§ ØŖŲˆ ØĒŲ… Ø§Ų„Ø­ØĩŲˆŲ„ ØšŲ„ŲŠŲ‡ Ų…Ų† Ø´ØŽØĩ ØĸØŽØą:&lt;br&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt;؊؈ØĩŲ‰ بشد؊ Ø¨Ų€ &lt;b&gt;ØĨŲ„ØēØ§ØĄ ØĒØĢØ¨ŲŠØĒ Ų‡Ø°Ø§ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ ؈ØĒØšØ¯ŲŠŲ„Ų‡ Ø¨Ų†ŲØŗŲƒ&lt;/b&gt; Ų„Ų„ØĒØŖŲƒØ¯ Ų…Ų† ØŖŲ†Ųƒ ØĒØŗØĒØŽØ¯Ų… ØĒØˇØ¨ŲŠŲ‚Ų‹Ø§ Ų…ØšØĒŲ…Ø¯Ų‹Ø§ ؈ØĸŲ…Ų†Ų‹Ø§.&lt;p&gt;&lt;br&gt;؁؊ Ø­Ø§Ų„ØŠ ØĒØŦØ§Ų‡Ų„ Ų‡Ø°Ø§ Ø§Ų„ØĒØ­Ø°ŲŠØąØŒ ØŗŲŠØĒŲ… ØšØąØļŲ‡ Ų…ØąØĒŲŠŲ† ŲŲ‚Øˇ.</string>
<string name="revanced_check_environment_not_same_patching_device">ØĒŲ… ØĒØšØ¯ŲŠŲ„Ų‡ ØšŲ„Ų‰ ØŦŲ‡Ø§Ø˛ Ų…ØŽØĒ؄؁</string>
<string name="revanced_check_environment_manager_not_expected_installer">Ų„Ų… ؊ØĒŲ… ØĒØĢØ¨ŲŠØĒŲ‡ Ø¨ŲˆØ§ØŗØˇØŠ ReVanced Manager</string>
<string name="revanced_check_environment_not_near_patch_time">ØĒŲ… ØĒØšØ¯ŲŠŲ„Ų‡ Ų‚Ø¨Ų„ ØŖŲƒØĢØą Ų…Ų† 10 Ø¯Ų‚Ø§ØĻŲ‚</string>
<string name="revanced_check_environment_not_near_patch_time_days">ØĒŲ… Ø§Ų„ØĒØšØ¯ŲŠŲ„ Ų…Ų†Ø° %s ŲŠŲˆŲ…</string>
<string name="revanced_check_environment_not_near_patch_time_invalid">ØĒØ§ØąŲŠØŽ ØĨŲ†Ø´Ø§ØĄ APK ØĒØ§Ų„Ų</string>
</patch>
<patch id="misc.settings.BaseSettingsResourcePatch">
<string name="revanced_settings_confirm_user_dialog_title">Ų‡Ų„ ØĒØąØēب ؁؊ Ø§Ų„Ų…ØĒابؚ؊؟</string>
<string name="revanced_settings_reset">ØĨؚاد؊ Ø§Ų„ØĒØšŲŠŲŠŲ†</string>
@@ -42,6 +53,14 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_import_reset">ØĨؚاد؊ ØĒØšŲŠŲŠŲ† ØĨؚداداØĒ ReVanced ØĨŲ„Ų‰ Ø§Ų„ŲˆØļØš Ø§Ų„Ø§ŲØĒØąØ§Øļ؊</string>
<string name="revanced_settings_import_success">ØĒŲ… Ø§ØŗØĒŲŠØąØ§Ø¯ %d ØĨؚداداØĒ</string>
<string name="revanced_settings_import_failure_parse">ŲØ´Ų„ Ø§Ų„Ø§ØŗØĒŲŠØąØ§Ø¯: %s</string>
<string name="revanced_pref_import_export_title">Ø§ØŗØĒŲŠØąØ§Ø¯ / ØĒØĩØ¯ŲŠØą</string>
<string name="revanced_pref_import_export_summary">Ø§ØŗØĒŲŠØąØ§Ø¯ / ØĒØĩØ¯ŲŠØą ØĨؚداداØĒ ReVanced</string>
<!-- Settings about dialog. -->
<string name="revanced_settings_about_links_body">ØŖŲ†ØĒ ØĒØŗØĒØŽØ¯Ų… ØĨØĩØ¯Ø§Øą ReVanced Patches &lt;i&gt;%s&lt;/i&gt;</string>
<string name="revanced_settings_about_links_dev_header">Ų…Ų„Ø§Ø­Ø¸ØŠ</string>
<string name="revanced_settings_about_links_dev_body">Ų‡Ø°Ø§ Ø§Ų„ØĨØĩØ¯Ø§Øą Ų‡Ųˆ ØĨØĩØ¯Ø§Øą Ų…ØŗØ¨Ų‚ØŒ ŲˆŲ‚Ø¯ ØĒŲˆØ§ØŦŲ‡ Ų…Ø´Ø§ŲƒŲ„ ØēŲŠØą Ų…ØĒŲˆŲ‚ØšØŠ</string>
<string name="revanced_settings_about_links_header">Ø§Ų„ØąŲˆØ§Ø¨Øˇ Ø§Ų„ØąØŗŲ…ŲŠØŠ</string>
<string name="revanced_settings_about_links_donate">ØĒØ¨ØąØš</string>
</patch>
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@@ -54,14 +73,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch>
</app>
<app id="youtube">
<patch id="misc.settings.SettingsResourcePatch">
<string name="revanced_settings_about_links_body">ØŖŲ†ØĒ ØĒØŗØĒØŽØ¯Ų… ØĨØĩØ¯Ø§Øą ReVanced Patches &lt;i&gt;%s&lt;/i&gt;</string>
<string name="revanced_settings_about_links_dev_header">Ų…Ų„Ø§Ø­Ø¸ØŠ</string>
<string name="revanced_settings_about_links_dev_body">Ų‡Ø°Ø§ Ø§Ų„ØĨØĩØ¯Ø§Øą Ų‡Ųˆ ØĨØĩØ¯Ø§Øą Ų…ØŗØ¨Ų‚ØŒ ŲˆŲ‚Ø¯ ØĒŲˆØ§ØŦŲ‡ Ų…Ø´Ø§ŲƒŲ„ ØēŲŠØą Ų…ØĒŲˆŲ‚ØšØŠ</string>
<string name="revanced_settings_about_links_header">Ø§Ų„ØąŲˆØ§Ø¨Øˇ Ø§Ų„ØąØŗŲ…ŲŠØŠ</string>
<string name="revanced_pref_import_export_title">Ø§ØŗØĒŲŠØąØ§Ø¯ / ØĒØĩØ¯ŲŠØą</string>
<string name="revanced_pref_import_export_summary">Ø§ØŗØĒŲŠØąØ§Ø¯ / ØĒØĩØ¯ŲŠØą ØĨؚداداØĒ ReVanced</string>
</patch>
<patch id="misc.settings.SettingsPatch">
<string name="revanced_settings_screen_00_about_title">Ų„Ų…Ø­ØŠ</string>
<string name="revanced_settings_screen_01_ads_title">Ø§Ų„ØĨØšŲ„Ø§Ų†Ø§ØĒ</string>
@@ -242,14 +253,18 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_hide_keyword_content_phrases_title">Ø§Ų„ŲƒŲ„Ų…Ø§ØĒ Ø§Ų„Ų…ŲØĒØ§Ø­ŲŠØŠ Ø§Ų„Ų…ØąØ§Ø¯ ØĨØŽŲØ§Ø¤Ų‡Ø§</string>
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
<string name="revanced_hide_keyword_content_phrases_summary">Ø§Ų„ŲƒŲ„Ų…Ø§ØĒ Ø§Ų„Ų…ŲØĒØ§Ø­ŲŠØŠ ŲˆØ§Ų„ØšØ¨Ø§ØąØ§ØĒ Ø§Ų„Ų…ØąØ§Ø¯ ØĨØŽŲØ§Ø¤Ų‡Ø§ØŒ ؅؁ØĩŲˆŲ„ØŠ Ø¨ØŖØŗØˇØą ØŦØ¯ŲŠØ¯\n\nØ§Ų„ŲƒŲ„Ų…Ø§ØĒ Ø§Ų„ØĒ؊ ØĒØ­ØĒ؈؊ ØšŲ„Ų‰ ØŖØ­ØąŲ ŲƒØ¨ŲŠØąØŠ ؁؊ Ø§Ų„ŲˆØŗØˇ ؊ØŦب ØĨØ¯ØŽØ§Ų„Ų‡Ø§ Ų…Øš Ø§Ų„ØĒØēŲ„ŲŠŲ (Ų…ØĢŲ„: iPhone, TikTok, Leblanc)</string>
<string name="revanced_hide_keyword_content_phrases_summary">Ø§Ų„ŲƒŲ„Ų…Ø§ØĒ ŲˆØ§Ų„ØšØ¨Ø§ØąØ§ØĒ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ Ø§Ų„ØĒ؊ ؊ØŦب ØĨØŽŲØ§Ø¤Ų‡Ø§ØŒ ؅؁ØĩŲˆŲ„ØŠ Ø¨ØŗØˇØą ØŦØ¯ŲŠØ¯\n\nØ§Ų„ŲƒŲ„Ų…Ø§ØĒ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ ŲŠŲ…ŲƒŲ† ØŖŲ† ØĒŲƒŲˆŲ† ØŖØŗŲ…Ø§ØĄ Ų‚Ų†ŲˆØ§ØĒ ØŖŲˆ ØŖŲŠ Ų†Øĩ ŲŠØ¸Ų‡Øą ؁؊ ØšŲ†Ø§ŲˆŲŠŲ† Ø§Ų„ŲŲŠØ¯ŲŠŲˆ\n\n؊ØŦب ØĨØ¯ØŽØ§Ų„ Ø§Ų„ŲƒŲ„Ų…Ø§ØĒ Ø§Ų„ØĒ؊ ØĒØ­ØĒ؈؊ ØšŲ„Ų‰ ØŖØ­ØąŲ ŲƒØ¨ŲŠØąØŠ ؁؊ Ø§Ų„ŲˆØŗØˇ بØĨØŗØĒØŽØ¯Ø§Ų… Ø§Ų„ØŖØ­ØąŲ Ø§Ų„ŲƒØ¨ŲŠØąØŠ (Ų…ØĢØ§Ų„: iPhone، TikTok، LeBlanc)</string>
<string name="revanced_hide_keyword_content_about_title">Ø­ŲˆŲ„ ØĒØĩŲŲŠØŠ Ø§Ų„ŲƒŲ„Ų…Ø§ØĒ Ø§Ų„Ų…ŲØĒØ§Ø­ŲŠØŠ</string>
<string name="revanced_hide_keyword_content_about_summary">Ø§Ų„ØĩŲØ­ØŠ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ/Ø§Ų„Ø§Ø´ØĒØąØ§Ųƒ/Ų†ØĒاØĻØŦ Ø§Ų„Ø¨Ø­ØĢ ؊ØĒŲ… ØĒØĩ؁؊ØĒŲ‡Ø§ Ų„ØĨØŽŲØ§ØĄ Ø§Ų„Ų…Ø­ØĒŲˆŲ‰ Ø§Ų„Ø°ŲŠ ŲŠØˇØ§Ø¨Ų‚ ØšØ¨Ø§ØąØ§ØĒ Ø§Ų„ŲƒŲ„Ų…Ø§ØĒ Ø§Ų„Ų…ŲØĒØ§Ø­ŲŠØŠ\n\nØ§Ų„ØĒŲ‚ŲŠŲŠØ¯\nâ€ĸ بؚØļ Ø§Ų„Ų…Ų‚Ø§ØˇØš Ø§Ų„Ų‚ØĩŲŠØąØŠ Ų‚Ø¯ Ų„Ø§ ØĒŲƒŲˆŲ† Ų…ØŽŲŲŠØŠ\nâ€ĸ بؚØļ Ų…ŲƒŲˆŲ†Ø§ØĒ ŲˆØ§ØŦŲ‡ØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… Ų‚Ø¯ Ų„Ø§ ØĒŲƒŲˆŲ† Ų…ØŽŲŲŠØŠ\nâ€ĸ Ø§Ų„Ø¨Ø­ØĢ ØšŲ† ŲƒŲ„Ų…ØŠ ØąØĻŲŠØŗŲŠØŠ Ų‚Ø¯ Ų„Ø§ ŲŠØ¸Ų‡Øą ØŖŲŠ Ų†ØĒاØĻØŦ</string>
<string name="revanced_hide_keyword_content_about_summary">Ø§Ų„ØĩŲØ­ØŠ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ/Ø§Ų„Ø§Ø´ØĒØąØ§ŲƒØ§ØĒ/Ų†ØĒاØĻØŦ Ø§Ų„ØĨØ´ØĒØąØ§Ųƒ/؊ØĒŲ… ØĒØĩŲŲŠØŠ Ų†ØĒاØĻØŦ Ø§Ų„Ø¨Ø­ØĢ Ų„ØĨØŽŲØ§ØĄ Ø§Ų„Ų…Ø­ØĒŲˆŲ‰ Ø§Ų„Ø°ŲŠ ؊ØĒØˇØ§Ø¨Ų‚ Ų…Øš ØšØ¨Ø§ØąØ§ØĒ Ø§Ų„ŲƒŲ„Ų…Ø§ØĒ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ\n\nØ§Ų„Ų‚ŲŠŲˆØ¯\nâ€ĸ Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĨØŽŲØ§ØĄ ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ Shorts Ø¨ŲˆØ§ØŗØˇØŠ Ø§ØŗŲ… Ø§Ų„Ų‚Ų†Ø§ØŠ\nâ€ĸ Ų‚Ø¯ Ų„Ø§ ØĒŲƒŲˆŲ† بؚØļ Ų…ŲƒŲˆŲ†Ø§ØĒ ŲˆØ§ØŦŲ‡ØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… Ų…ØŽŲŲŠØŠ\nâ€ĸ Ų‚Ø¯ Ų„Ø§ ØĒØ¸Ų‡Øą Ų†ØĒاØĻØŦ بحØĢ ØšŲ† ŲƒŲ„Ų…ØŠ ØąØĻŲŠØŗŲŠØŠ</string>
<string name="revanced_hide_keyword_content_about_whole_words_title">Ų…ØˇØ§Ø¨Ų‚ØŠ Ø§Ų„ŲƒŲ„Ų…Ø§ØĒ Ø¨ØŖŲƒŲ…Ų„Ų‡Ø§</string>
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
<string name="revanced_hide_keyword_content_about_whole_words_summary">ØŗŲŠØ¤Ø¯ŲŠ ؈ØļØš ØšŲ„Ø§Ų…ØŠ Ø§Ų‚ØĒØ¨Ø§Øŗ Ų…Ø˛Ø¯ŲˆØŦØŠ Ø­ŲˆŲ„ ŲƒŲ„Ų…ØŠ ØąØĻŲŠØŗŲŠØŠ/ØšØ¨Ø§ØąØŠ ØĨŲ„Ų‰ Ų…Ų†Øš Ø§Ų„ØĒØˇØ§Ø¨Ų‚Ø§ØĒ Ø§Ų„ØŦØ˛ØĻŲŠØŠ Ų„ØšŲ†Ø§ŲˆŲŠŲ† Ø§Ų„ŲŲŠØ¯ŲŠŲˆ ŲˆØŖØŗŲ…Ø§ØĄ Ø§Ų„Ų‚Ų†ŲˆØ§ØĒ.&lt;br&gt;&lt;br&gt;ØšŲ„Ų‰ ØŗØ¨ŲŠŲ„ Ø§Ų„Ų…ØĢØ§Ų„ØŒ&lt;br&gt;&lt;b&gt;\"ai\"&lt;/b&gt; ØŗŲŠØŽŲŲŠ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ: &lt;b&gt;How does AI work?&lt;/b&gt;&lt;br&gt;ŲˆŲ„ŲƒŲ† Ų„Ų† ŲŠØŽŲŲŠ: &lt;b&gt;What does fair use mean?&lt;/b&gt;</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_hide_keyword_toast_invalid_common">Ø§Ų„ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ŲØĒØ§Ø­ŲŠØŠ ØēŲŠØą ØĩØ§Ų„Ø­ØŠ. Ų„Ø§ ŲŠŲ…ŲƒŲ† Ø§ØŗØĒØŽØ¯Ø§Ų…: \'%s\' ŲƒØšØ§Ų…Ų„ ØĒØĩŲŲŠØŠ</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_hide_keyword_toast_invalid_length">Ø§Ų„ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ŲØĒØ§Ø­ŲŠØŠ ØēŲŠØą ØĩØ§Ų„Ø­ØŠ. \'%1$s\' ØŖŲ‚Ų„ Ų…Ų† %2$d Ø­ØąŲŲ‹Ø§</string>
<string name="revanced_hide_keyword_toast_invalid_broad">Ø§Ų„ŲƒŲ„Ų…ØŠ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ \'%s\' ØŗŲˆŲ ØĒØŽŲŲŠ ØŦŲ…ŲŠØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ</string>
<string name="revanced_hide_keyword_toast_invalid_common">Ų„Ø§ ŲŠŲ…ŲƒŲ† Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„ŲƒŲ„Ų…ØŠ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ: %s</string>
<string name="revanced_hide_keyword_toast_invalid_common_whole_word_required">ØĨØļØ§ŲØŠ Ø§Ų‚ØĒØ¨Ø§ØŗØ§ØĒ Ų„Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„ŲƒŲ„Ų…ØŠ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ: %s</string>
<string name="revanced_hide_keyword_toast_invalid_conflicting">Ø§Ų„ŲƒŲ„Ų…ØŠ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ Ų„Ų‡Ø§ Ø¨ŲŠØ§Ų†Ø§ØĒ Ų…ØĒØļØ§ØąØ¨ØŠ: %s</string>
<string name="revanced_hide_keyword_toast_invalid_length">Ø§Ų„ŲƒŲ„Ų…ØŠ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ Ų‚ØĩŲŠØąØŠ ØŦØ¯Ų‹Ø§ ؈ØĒØĒØˇŲ„Ø¨ Ø§Ų‚ØĒØ¨Ø§ØŗØ§ØĒ: %s</string>
<string name="revanced_hide_keyword_toast_invalid_broad">Ø§Ų„ŲƒŲ„Ų…ØŠ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ ØŗŲˆŲ ØĒØŽŲŲŠ ØŦŲ…ŲŠØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ: %s</string>
</patch>
<patch id="ad.general.HideAdsResourcePatch">
<string name="revanced_hide_general_ads_title">ØĨØŽŲØ§ØĄ Ø§Ų„ØĨØšŲ„Ø§Ų†Ø§ØĒ Ø§Ų„ØšØ§Ų…ØŠ</string>
@@ -615,6 +630,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_hide_shorts_save_sound_button_title">ØĨØŽŲØ§ØĄ Ø­ŲØ¸ Ø§Ų„Øĩ؈ØĒ ØĨŲ„Ų‰ Ø˛Øą Ų‚Ø§ØĻŲ…ØŠ Ø§Ų„ØĒØ´ØēŲŠŲ„</string>
<string name="revanced_hide_shorts_save_sound_button_summary_on">ØĒŲ… ØĨØŽŲØ§ØĄ Ø­ŲØ¸ Ø§Ų„Øĩ؈ØĒ ؁؊ Ų‚Ø§ØĻŲ…ØŠ Ø§Ų„ØĒØ´ØēŲŠŲ„</string>
<string name="revanced_hide_shorts_save_sound_button_summary_off">؊ØĒŲ… ØšØąØļ Ø­ŲØ¸ Ø§Ų„Øĩ؈ØĒ ؁؊ Ų‚Ø§ØĻŲ…ØŠ Ø§Ų„ØĒØ´ØēŲŠŲ„</string>
<string name="revanced_hide_shorts_use_this_sound_button_title">ØĨØŽŲØ§ØĄ Ø˛Øą Ø§ØŗØĒØŽØ¯Ø§Ų… Ų‡Ø°Ø§ Ø§Ų„Øĩ؈ØĒ</string>
<string name="revanced_hide_shorts_use_this_sound_button_summary_on">ØĒŲ… ØĨØŽŲØ§ØĄ Ø˛Øą Ø§ØŗØĒØŽØ¯Ø§Ų… Ų‡Ø°Ø§ Ø§Ų„Øĩ؈ØĒ</string>
<string name="revanced_hide_shorts_use_this_sound_button_summary_off">؊ØĒŲ… ØšØąØļ Ø˛Øą Ø§ØŗØĒØŽØ¯Ø§Ų… Ų‡Ø°Ø§ Ø§Ų„Øĩ؈ØĒ</string>
<string name="revanced_hide_shorts_search_suggestions_title">ØĨØŽŲØ§ØĄ Ø§Ų‚ØĒØąØ§Ø­Ø§ØĒ Ø§Ų„Ø¨Ø­ØĢ</string>
<string name="revanced_hide_shorts_search_suggestions_summary_on">ØĒŲ… ØĨØŽŲØ§ØĄ Ø§Ų‚ØĒØąØ§Ø­Ø§ØĒ Ø§Ų„Ø¨Ø­ØĢ</string>
<string name="revanced_hide_shorts_search_suggestions_summary_off">؊ØĒŲ… ØšØąØļ Ø§Ų‚ØĒØąØ§Ø­Ø§ØĒ Ø§Ų„Ø¨Ø­ØĢ</string>
@@ -678,7 +696,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_player_overlay_opacity_invalid_toast">Ø´ŲØ§ŲŲŠØŠ ŲˆØ§ØŦŲ‡ØŠ Ø§Ų„Ų…Ø´ØēŲ„ ؊ØŦب ØŖŲ† ØĒŲƒŲˆŲ† Ø¨ŲŠŲ† 0-100</string>
</patch>
<patch id="layout.returnyoutubedislike.ReturnYouTubeDislikeResourcePatch">
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Ų…ØŽŲŲŠ</string>
<!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. -->
<string name="revanced_ryd_failure_connection_timeout">Ų„Ų… ŲŠØšØŦØ¨Ų†ŲŠ ØēŲŠØą Ų…ØĒاح Ų…Ø¤Ų‚ØĒŲ‹Ø§ (Ø§Ų†ØĒŲ‡ØĒ Ų…Ų‡Ų„ØŠ API)</string>
<string name="revanced_ryd_failure_connection_status_code">Ų„Ų… ŲŠØšØŦØ¨Ų†ŲŠ ØēŲŠØą Ų…ØĒاح (Ø§Ų„Ø­Ø§Ų„ØŠ %d)</string>
@@ -893,6 +910,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_sb_stats_username_changed">ØĒŲ… ØĒØēŲŠŲŠØą Ø§ØŗŲ… Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… Ø¨Ų†ØŦاح</string>
<string name="revanced_sb_stats_reputation">ØŗŲ…ØšØĒ؃ Ų‡ŲŠ &lt;b&gt;%.2f&lt;/b&gt;</string>
<string name="revanced_sb_stats_submissions">Ų„Ų‚Ø¯ ØŖŲ†Ø´ØŖØĒ &lt;b&gt;%s&lt;/b&gt; Ų…Ų‚ØˇØš</string>
<string name="revanced_sb_stats_submissions_sum">اØļØēØˇ Ų‡Ų†Ø§ Ų„ØšØąØļ Ø§Ų„Ų…Ų‚Ø§ØˇØš Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ</string>
<string name="revanced_sb_stats_saved_zero">Ų…ØĒØĩØ¯ØąŲŠŲ† SponsorBlock</string>
<string name="revanced_sb_stats_saved">Ų„Ų‚Ø¯ Ų‚Ų…ØĒ Ø¨Ø­ŲØ¸ Ø§Ų„Ų†Ø§Øŗ Ų…Ų† &lt;b&gt;%s&lt;/b&gt; Ų…Ų‚ØˇØš</string>
<string name="revanced_sb_stats_saved_sum_zero">اØļØēØˇ Ų‡Ų†Ø§ Ų„ØąØ¤ŲŠØŠ Ø§Ų„ØĨØ­ØĩاØĻŲŠØ§ØĒ Ø§Ų„ØšØ§Ų„Ų…ŲŠØŠ ŲˆØŖØ¨ØąØ˛ Ø§Ų„Ų…ØŗØ§Ų‡Ų…ŲŠŲ†</string>
@@ -1114,27 +1132,23 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_slide_to_seek_summary_on">ØĒŲ… ØĒŲ…ŲƒŲŠŲ† Slide to Seek</string>
<string name="revanced_slide_to_seek_summary_off">ØĒŲ… ØĒØšØˇŲŠŲ„ Slide to Seek</string>
</patch>
<patch id="misc.fix.playback.SpoofClientPatch">
<string name="revanced_spoof_client_screen_title">Spoof Client</string>
<string name="revanced_spoof_client_screen_summary">Ų…Ø­Ø§ŲƒØ§ØŠ Ø§Ų„ØšŲ…ŲŠŲ„ Ų„Ų…Ų†Øš Ų…Ø´ŲƒŲ„Ø§ØĒ Ø§Ų„ØĒØ´ØēŲŠŲ„</string>
<string name="revanced_spoof_client_title">Spoof Client</string>
<string name="revanced_spoof_client_summary_on">؊ØĒŲ… Ų…Ø­Ø§ŲƒØ§ØŠ Ø§Ų„ØšŲ…ŲŠŲ„</string>
<string name="revanced_spoof_client_summary_off">Ų„Ø§ ؊ØĒŲ… Ų…Ø­Ø§ŲƒØ§ØŠ Ø§Ų„ØšŲ…ŲŠŲ„\n\nŲ‚Ø¯ Ų„Ø§ ŲŠØšŲ…Ų„ ØĒØ´ØēŲŠŲ„ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ</string>
<string name="revanced_spoof_client_user_dialog_message">ØĨŲŠŲ‚Ø§Ų ØĒØ´ØēŲŠŲ„ Ų‡Ø°Ø§ Ø§Ų„ØĨؚداد Ų‚Ø¯ ŲŠØŗØ¨Ø¨ Ų…Ø´Ø§ŲƒŲ„ ؁؊ ØĒØ´ØēŲŠŲ„ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ.</string>
<string name="revanced_spoof_client_type_title">Ų†ŲˆØš Spoof Client</string>
<string name="revanced_spoof_client_ios_force_avc_title">ŲØąØļ AVC iOS (H.264)</string>
<string name="revanced_spoof_client_ios_force_avc_summary_on">ØĒØąŲ…ŲŠØ˛ ŲŲŠØ¯ŲŠŲˆ iOS Ų‡Ųˆ AVC</string>
<string name="revanced_spoof_client_ios_force_avc_summary_off">ØĒØąŲ…ŲŠØ˛ ŲŲŠØ¯ŲŠŲˆ iOS Ų‡Ųˆ AVC ØŖŲˆ VP9 ØŖŲˆ AV1</string>
<string name="revanced_spoof_client_ios_force_avc_user_dialog_message">Ų‚Ø¯ ŲŠØ¤Ø¯ŲŠ ØĒŲ…ŲƒŲŠŲ† Ų‡Ø°Ø§ ØĨŲ„Ų‰ ØĒØ­ØŗŲŠŲ† ØšŲ…Øą Ø§Ų„Ø¨ØˇØ§ØąŲŠØŠ ؈ØĨØĩŲ„Ø§Ø­ Ų…Ø´ŲƒŲ„ØŠ ØĒŲ‚ØˇŲŠØš Ø§Ų„ØĒØ´ØēŲŠŲ„.\n\n؊ØĒŲ…ØĒØš ØĒŲ†ØŗŲŠŲ‚ AVC Ø¨Ø¯Ų‚ØŠ Ų‚ØĩŲˆŲ‰ ØĒØ¨Ų„Øē 1080P، ŲˆØŗŲŠØŗØĒØŽØ¯Ų… ØĒØ´ØēŲŠŲ„ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…Ø˛ŲŠØ¯ Ų…Ų† Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„ØĨŲ†ØĒØąŲ†ØĒ Ų…Ų‚Ø§ØąŲ†ØŠŲ‹ بØĒŲ†ØŗŲŠŲ‚ VP9 ØŖŲˆ AV1.</string>
<string name="revanced_spoof_client_about_android_ios_title">Ø§Ų„ØĒØŖØĢŲŠØąØ§ØĒ Ø§Ų„ØŦØ§Ų†Ø¨ŲŠØŠ Ų„Ų…Ø­Ø§ŲƒØ§ØŠ iOS</string>
<string name="revanced_spoof_client_about_android_ios_summary">â€ĸ HDR Ų…Ø¯ØšŲˆŲ… ŲŲ‚Øˇ Ų…Øš ØĒØąŲ…ŲŠØ˛ AV1\nâ€ĸ ØŗØŦŲ„ Ø§Ų„Ų…Ø´Ø§Ų‡Ø¯ØŠ Ų„Ø§ ŲŠØšŲ…Ų„ Ų…Øš Ø­ØŗØ§Ø¨ Ø§Ų„ØšŲ„Ø§Ų…ØŠ Ø§Ų„ØĒØŦØ§ØąŲŠØŠ</string>
<string name="revanced_spoof_client_about_android_vr_title">Ø§Ų„ØĒØŖØĢŲŠØąØ§ØĒ Ø§Ų„ØŦØ§Ų†Ø¨ŲŠØŠ Ų„Ų…Ø­Ø§ŲƒØ§ØŠ Android VR</string>
<string name="revanced_spoof_client_about_android_vr_summary">â€ĸ Ų„Ø§ ؊؈ØŦد ŲŲŠØ¯ŲŠŲˆ HDR\nâ€ĸ Ų„Ø§ ؊ØĒŲ… ØĒØ´ØēŲŠŲ„ Ų…Ų‚Ø§ØˇØš ŲŲŠØ¯ŲŠŲˆ Ø§Ų„ØŖØˇŲØ§Ų„\nâ€ĸ ŲŠŲ…ŲƒŲ† Ø§ØŗØĒØĻŲ†Ø§Ų Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…ØĒŲˆŲ‚ŲØŠ Ų…Ø¤Ų‚ØĒŲ‹Ø§ Ø¨Ø´ŲƒŲ„ ØšØ´ŲˆØ§ØĻ؊\nâ€ĸ Ų…ØĩØēØąØ§ØĒ Ø´ØąŲŠØˇ ØĒŲ‚Ø¯Ų… ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ Shorts Ų…Ų†ØŽŲØļØŠ Ø§Ų„ØŦŲˆØ¯ØŠ\nâ€ĸ Ø˛Øą ØĨØŦØąØ§ØĄ Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„ Ų…ØŽŲŲŠ\nâ€ĸ Ø¨ØˇØ§Ų‚Ø§ØĒ شاش؊ Ø§Ų„Ų†Ų‡Ø§ŲŠØŠ Ų…ØŽŲŲŠØŠ</string>
<string name="revanced_spoof_client_storyboard_timeout">Ų…Ø­Ø§ŲƒØ§ØŠ Ų…ØĩØēØąØ§ØĒ Ø§Ų„ØšŲ…ŲŠŲ„ ØēŲŠØą Ų…ØĒŲˆŲØąØŠ (Ø§Ų†ØĒŲ‡ØĒ Ų…Ų‡Ų„ØŠ API)</string>
<string name="revanced_spoof_client_storyboard_io_exception">Ų…Ø­Ø§ŲƒØ§ØŠ Ų…ØĩØēØąØ§ØĒ Ø§Ų„ØšŲ…ŲŠŲ„ ØēŲŠØą Ų…ØĒŲˆŲØąØŠ Ų…Ø¤Ų‚ØĒŲ‹Ø§: %s</string>
</patch>
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
<patch id="misc.fix.playback.SpoofSignaturePatch">
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
<string name="revanced_spoof_video_streams_screen_title">Spoof Video Streams</string>
<string name="revanced_spoof_video_streams_screen_summary">ØĒØ˛ŲŠŲŠŲ ØĒØ¯ŲŲ‚Ø§ØĒ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ø§Ų„ØšŲ…ŲŠŲ„ Ų„Ų…Ų†Øš Ø­Ø¯ŲˆØĢ Ų…Ø´ŲƒŲ„Ø§ØĒ ØŖØĢŲ†Ø§ØĄ Ø§Ų„ØĒØ´ØēŲŠŲ„</string>
<string name="revanced_spoof_video_streams_title">Spoof Video Streams</string>
<string name="revanced_spoof_video_streams_summary_on">؊ØĒŲ… ØĒØ˛ŲŠŲŠŲ ØĒØ¯ŲŲ‚Ø§ØĒ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ</string>
<string name="revanced_spoof_video_streams_summary_off">Ų„Ø§ ؊ØĒŲ… ØĒØ˛ŲŠŲŠŲ ØĒØ¯ŲŲ‚Ø§ØĒ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ\n\nŲ‚Ø¯ Ų„Ø§ ŲŠØšŲ…Ų„ ØĒØ´ØēŲŠŲ„ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ</string>
<string name="revanced_spoof_video_streams_user_dialog_message">ØĨŲŠŲ‚Ø§Ų ØĒØ´ØēŲŠŲ„ Ų‡Ø°Ø§ Ø§Ų„ØĨؚداد Ų‚Ø¯ ŲŠØŗØ¨Ø¨ Ų…Ø´Ø§ŲƒŲ„ ؁؊ ØĒØ´ØēŲŠŲ„ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ.</string>
<string name="revanced_spoof_video_streams_client_title">Ø§Ų„ØšŲ…ŲŠŲ„ Ø§Ų„Ø§ŲØĒØąØ§Øļ؊</string>
<string name="revanced_spoof_video_streams_ios_force_avc_title">ŲØąØļ AVC (H.264)</string>
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">ØĒØąŲ…ŲŠØ˛ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ų‡Ųˆ AVC (H.264)</string>
<string name="revanced_spoof_video_streams_ios_force_avc_summary_off">ØĒØąŲ…ŲŠØ˛ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ų‡Ųˆ VP9 ØŖŲˆ AV1</string>
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Ų„Ø§ ŲŠØ­ØĒ؈؊ ØŦŲ‡Ø§Ø˛Ųƒ ØšŲ„Ų‰ ؁؃ ØĒØ´ŲŲŠØą Ø§Ų„ØŖØŦŲ‡Ø˛ØŠ VP9، ŲˆŲ‡Ø°Ø§ Ø§Ų„ØĨؚداد ŲŠØšŲ…Ų„ داØĻŲ…Ø§ ØšŲ†Ø¯ ØĒŲ…ŲƒŲŠŲ† ØĒØ˛ŲŠŲŠŲ Ø§Ų„ØšŲ…ŲŠŲ„</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Ų‚Ø¯ ŲŠØ¤Ø¯ŲŠ ØĒŲ…ŲƒŲŠŲ† Ų‡Ø°Ø§ ØĨŲ„Ų‰ ØĒØ­ØŗŲŠŲ† ØšŲ…Øą Ø§Ų„Ø¨ØˇØ§ØąŲŠØŠ ؈ØĨØĩŲ„Ø§Ø­ Ų…Ø´ŲƒŲ„ØŠ ØĒŲ‚ØˇŲŠØš Ø§Ų„ØĒØ´ØēŲŠŲ„.\n\n؊ØĒŲ…ØĒØš ØĒŲ†ØŗŲŠŲ‚ AVC Ø¨Ø¯Ų‚ØŠ Ų‚ØĩŲˆŲ‰ ØĒØ¨Ų„Øē 1080P، ŲˆØŗŲŠØŗØĒØŽØ¯Ų… ØĒØ´ØēŲŠŲ„ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…Ø˛ŲŠØ¯ Ų…Ų† Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„ØĨŲ†ØĒØąŲ†ØĒ Ų…Ų‚Ø§ØąŲ†ØŠŲ‹ بØĒŲ†ØŗŲŠŲ‚ VP9 ØŖŲˆ AV1.</string>
<string name="revanced_spoof_video_streams_about_ios_title">Ø§Ų„ØĒØŖØĢŲŠØąØ§ØĒ Ø§Ų„ØŦØ§Ų†Ø¨ŲŠØŠ Ų„Ų…Ø­Ø§ŲƒØ§ØŠ iOS</string>
<string name="revanced_spoof_video_streams_about_ios_summary">â€ĸ Ų‚Ø¯ Ų„Ø§ ؊ØĒŲ… ØĒØ´ØēŲŠŲ„ Ø§Ų„ØŖŲŲ„Ø§Ų… ØŖŲˆ Ø§Ų„ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ Ø§Ų„Ų…Ø¯ŲŲˆØšØŠ\nâ€ĸ ØĒØ¨Ø¯ØŖ Ø§Ų„Ø¨ØĢ؈ØĢ Ø§Ų„Ų…Ø¨Ø§Ø´ØąØŠ Ų…Ų† Ø§Ų„Ø¨Ø¯Ø§ŲŠØŠ\nâ€ĸ Ų‚Ø¯ ØĒŲ†ØĒŲ‡ŲŠ Ø§Ų„ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ Ų‚Ø¨Ų„ Ø§Ų„Ų†Ų‡Ø§ŲŠØŠ بØĢØ§Ų†ŲŠØŠ ŲˆØ§Ø­Ø¯ØŠ\nâ€ĸ Ų„Ø§ ؊؈ØŦد ØĒØąŲ…ŲŠØ˛ Ø§Ų„Øĩ؈ØĒ Opus</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Ø§Ų„ØĒØŖØĢŲŠØąØ§ØĒ Ø§Ų„ØŦØ§Ų†Ø¨ŲŠØŠ Ų„Ų…Ø­Ø§ŲƒØ§ØŠ Android VR</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">â€ĸ Ų‚Ø§ØĻŲ…ØŠ Ø§Ų„Ų…Ų‚ØˇØš Ø§Ų„Øĩ؈ØĒ؊ Ų…ŲŲ‚ŲˆØ¯ØŠ\nâ€ĸ Ų…ØŗØĒŲˆŲ‰ Ø§Ų„Øĩ؈ØĒ Ø§Ų„ØĢابØĒ ØēŲŠØą Ų…ØĒŲˆŲØą</string>
</patch>
<!-- This patch is no longer used and these strings will soon be deleted. -->
<patch id="video.hdrbrightness.HDRBrightnessPatch">

View File

@@ -32,15 +32,16 @@ This is because Crowdin requires temporarily flattening this file and removing t
-->
<resources>
<app id="shared">
<patch id="misc.checks.BaseCheckEnvironmentPatch">
</patch>
<patch id="misc.settings.BaseSettingsResourcePatch">
<!-- Settings about dialog. -->
</patch>
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
</patch>
</app>
<app id="youtube">
<patch id="misc.settings.SettingsResourcePatch">
</patch>
<patch id="misc.settings.SettingsPatch">
</patch>
<patch id="misc.debugging.DebuggingPatch">
@@ -57,7 +58,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<!-- 'Component path builder strings' is the technical name for identifying the Litho UI layout items to hide. This is an advanced feature and most users will never use this. -->
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
</patch>
<patch id="ad.general.HideAdsResourcePatch">
@@ -232,10 +233,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch>
<patch id="interaction.seekbar.EnableSlideToSeekPatch">
</patch>
<patch id="misc.fix.playback.SpoofClientPatch">
</patch>
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
<patch id="misc.fix.playback.SpoofSignaturePatch">
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
</patch>
<!-- This patch is no longer used and these strings will soon be deleted. -->
<patch id="video.hdrbrightness.HDRBrightnessPatch">

View File

@@ -32,6 +32,17 @@ This is because Crowdin requires temporarily flattening this file and removing t
-->
<resources>
<app id="shared">
<patch id="misc.checks.BaseCheckEnvironmentPatch">
<string name="revanced_check_environment_failed_title">Yoxlamalar uğursuz oldu</string>
<string name="revanced_check_environment_dialog_open_official_source_button">Xidməti veb saytÄą aç</string>
<string name="revanced_check_environment_dialog_ignore_button">Yan keç</string>
<string name="revanced_check_environment_failed_message">&lt;h5&gt;Bu tətbiq sizin tərəfinizdən yamaqlanmayÄąb.&lt;/h5&gt;&lt;br&gt;Bu tətbiq dÃŧzgÃŧn işləməyə bilər, &lt;b&gt;istifadə etmək zərərli və ya hətta təhlÃŧkəli ola bilər&lt;/b&gt;.&lt;br&gt;&lt;br&gt;&lt;br&gt;Bu yoxlamalar bu tətbiqin əvvəldən yamaqlandığınÄą və ya başqasÄąndan əldə edildiyini gÃļstərir:&lt;br&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt; &lt;br&gt;onu silməyiniz və ÃļzÃŧnÃŧz yamaqlamağınÄąz tÃļvsiyə olunur. &lt;/b&gt;təsdiqlənmiş və təhlÃŧkəsiz tətbiq istifadə etdiyinizə əmin olmaq ÃŧçÃŧn. &lt;p&gt;&lt;br&gt; İnkar edilməzsə, bu xəbərdarlÄąq yalnÄąz iki dəfə gÃļstəriləcək.</string>
<string name="revanced_check_environment_not_same_patching_device">Fərqli cihazda yamaqlanıb</string>
<string name="revanced_check_environment_manager_not_expected_installer">ReVanced Manager tərəfindən quraşdÄąrÄąlmayÄąb</string>
<string name="revanced_check_environment_not_near_patch_time">10 dəqiqədən çox əvvəl yamaqlanÄąb</string>
<string name="revanced_check_environment_not_near_patch_time_days">%s gÃŧn əvvəl yamaqlanÄąb</string>
<string name="revanced_check_environment_not_near_patch_time_invalid">APK quruluş tarixi pozulub</string>
</patch>
<patch id="misc.settings.BaseSettingsResourcePatch">
<string name="revanced_settings_confirm_user_dialog_title">Davam etmək istəyirsiniz?</string>
<string name="revanced_settings_reset">SÄąfÄąrla</string>
@@ -42,6 +53,14 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_import_reset">ReVanced tənzimləmələr standarta təyin edildi</string>
<string name="revanced_settings_import_success">%d tənzimləmə idxal edildi</string>
<string name="revanced_settings_import_failure_parse">Uğursuz idxal prosesi: %s</string>
<string name="revanced_pref_import_export_title">İdxal/İxrac et</string>
<string name="revanced_pref_import_export_summary">ReVanced tənzimləmələrin idxal/ixrac et</string>
<!-- Settings about dialog. -->
<string name="revanced_settings_about_links_body">ReVanced Patches &lt;i&gt;%s&lt;/i&gt; versiyasını istifadə edirsiniz</string>
<string name="revanced_settings_about_links_dev_header">Qeyd</string>
<string name="revanced_settings_about_links_dev_body">Bu versiya ilkin buraxÄąlÄąÅŸdÄąr və gÃļzlənilməz problemlərlə Ãŧzləşə bilərsiniz</string>
<string name="revanced_settings_about_links_header">Rəsmi bağlantılar</string>
<string name="revanced_settings_about_links_donate">İanə ver</string>
</patch>
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@@ -54,14 +73,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch>
</app>
<app id="youtube">
<patch id="misc.settings.SettingsResourcePatch">
<string name="revanced_settings_about_links_body">ReVanced Patches &lt;i&gt;%s&lt;/i&gt; versiyasını istifadə edirsiniz</string>
<string name="revanced_settings_about_links_dev_header">Qeyd</string>
<string name="revanced_settings_about_links_dev_body">Bu versiya ilkin buraxÄąlÄąÅŸdÄąr və gÃļzlənilməz problemlərlə Ãŧzləşə bilərsiniz</string>
<string name="revanced_settings_about_links_header">Rəsmi bağlantılar</string>
<string name="revanced_pref_import_export_title">İdxal/İxrac et</string>
<string name="revanced_pref_import_export_summary">ReVanced tənzimləmələrin idxal/ixrac et</string>
</patch>
<patch id="misc.settings.SettingsPatch">
<string name="revanced_settings_screen_00_about_title">HaqqÄąnda</string>
<string name="revanced_settings_screen_01_ads_title">Reklamlar</string>
@@ -242,14 +253,18 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_hide_keyword_content_phrases_title">Gizlədiləcək açar sÃļzlər</string>
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
<string name="revanced_hide_keyword_content_phrases_summary">Yeni sətirlərlə ayrÄąlmÄąÅŸ gizlədiləcək açar sÃļzlər və ifadələr\n\nOrtada bÃļyÃŧk hərf olan sÃļzlər korpusla birlikdə daxil edilməlidir (yəni: iPhone, TikTok, LeBlanc)</string>
<string name="revanced_hide_keyword_content_phrases_summary">Yeni sətirlərlə ayrÄąlmÄąÅŸ gizlədiləcək açar sÃļzlər və frazalar\n\nAçar sÃļzlər kanal adlarÄą və ya video adlarÄąnda gÃļstərilən istənilən mətn ola bilər\n\nOrtada bÃļyÃŧk hərf olan sÃļzlər korpusla birlikdə qeyd edilməlidir (yəni: iPhone, TikTok, LeBlanc)</string>
<string name="revanced_hide_keyword_content_about_title">Açar sÃļz filtrləməsi haqqÄąnda</string>
<string name="revanced_hide_keyword_content_about_summary">Əsas səhifə/Abunəlik/AxtarÄąÅŸ nəticələri açar sÃļz ifadələrinə uyğunlaşan məzmunu gizlətmək ÃŧçÃŧn filtrlənir\n\nMəhdudiyyətlər\nâ€ĸ Bəzi Shorts gizlənə bilməz\nâ€ĸ Bəzi UI elementləri gizlənə bilməz\nâ€ĸ Açar sÃļz axtarÄąÅŸÄą heç bir nəticə gÃļstərməyə bilər</string>
<string name="revanced_hide_keyword_content_about_summary">Əsas səhifə/Abunəlik/AxtarÄąÅŸ nəticələri açar sÃļz ifadələrinə uyğunlaşan məzmunu gizlətmək ÃŧçÃŧn filtrlənir\n\nMəhdudiyyətlər\nâ€ĸ Shorts-lar kanal adÄąna gÃļrə gizlənə bilməz\nâ€ĸ Bəzi UI hissəcikləri gizlədilə bilməz\nâ€ĸ Açar sÃļz axtarÄąÅŸÄąnda nəticə olmaya bilər</string>
<string name="revanced_hide_keyword_content_about_whole_words_title">BÃŧtÃŧn sÃļzləri uyğunlaşdÄąr</string>
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
<string name="revanced_hide_keyword_content_about_whole_words_summary">Açar sÃļz/frazanÄąn qoşa dÄąrnaqlarla əhatə olunmasÄą video adlarÄą və kanal adlarÄąnÄąn qismən uyğunlaşmasÄąna mane olacaq &lt;br&gt;&lt;br&gt;Məsələn,&lt;br&gt;&lt;b&gt;\"ai\"&lt;/b&gt; videonu gizlədəcək:&lt;b&gt;How does AI work?&lt;/b&gt;&lt;br&gt; lakin gizlətməyəcək: DÃŧzgÃŧn;&lt;b&gt;What does fair use mean?&lt;/b&gt;</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_hide_keyword_toast_invalid_common">EtibarsÄąz açar sÃļzÃŧ. \'%s\' istifadə edilə bilməz</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_hide_keyword_toast_invalid_length">EtibarsÄąz açar sÃļzÃŧ. \'%1$s\', %2$d simvoldan azdÄąr</string>
<string name="revanced_hide_keyword_toast_invalid_broad">\"%s\" açar sÃļzÃŧ, bÃŧtÃŧn videolarda gizlədiləcək</string>
<string name="revanced_hide_keyword_toast_invalid_common">Açar sÃļz istifadə edilə bilmir: %s</string>
<string name="revanced_hide_keyword_toast_invalid_common_whole_word_required">Açar sÃļz istifadəsi ÃŧçÃŧn istinad əlavə et: %s</string>
<string name="revanced_hide_keyword_toast_invalid_conflicting">Açar sÃļzÃŧn ziddiyyətli hissəcikləri var: %s</string>
<string name="revanced_hide_keyword_toast_invalid_length">Açar sÃļz çox qÄąsadÄąr və istinad tələb edir: %s</string>
<string name="revanced_hide_keyword_toast_invalid_broad">Açar sÃļz, bÃŧtÃŧn videolarÄą gizlədəcək: %s</string>
</patch>
<patch id="ad.general.HideAdsResourcePatch">
<string name="revanced_hide_general_ads_title">Ümumi reklamlarÄą gizlət</string>
@@ -307,7 +322,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_copy_video_url_timestamp_summary_off">DÃŧymə gÃļstərilmir</string>
</patch>
<patch id="interaction.dialog.RemoveViewerDiscretionDialogPatch">
<string name="revanced_remove_viewer_discretion_dialog_title">İzləyici mÃŧlahizə dialoqunu sil</string>
<string name="revanced_remove_viewer_discretion_dialog_title">İzləyici mÃŧlahizə dialoqun sil</string>
<string name="revanced_remove_viewer_discretion_dialog_summary_on">Dialoq silindi</string>
<string name="revanced_remove_viewer_discretion_dialog_summary_off">Dialoq gÃļstərilir</string>
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">Bu, yaş məhdudiyyətini ÃļtÃŧrmÃŧr. Sadəcə avtomatik qəbul edir.</string>
@@ -615,6 +630,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_hide_shorts_save_sound_button_title">Səsi pleylistdə saxlama dÃŧyməsini gizlət</string>
<string name="revanced_hide_shorts_save_sound_button_summary_on">Səsi pleylistdə saxlama gizlidir</string>
<string name="revanced_hide_shorts_save_sound_button_summary_off">Səsi pleylistdə saxlama gÃļstərilir</string>
<string name="revanced_hide_shorts_use_this_sound_button_title">\"Bu səsi istifadə et\" dÃŧyməsini gizlət</string>
<string name="revanced_hide_shorts_use_this_sound_button_summary_on">\"Bu səsi istifadə et\" dÃŧyməsi gizlidir</string>
<string name="revanced_hide_shorts_use_this_sound_button_summary_off">\"Bu səsi istifadə et\" dÃŧyməsi gÃļstərilir</string>
<string name="revanced_hide_shorts_search_suggestions_title">AxtarÄąÅŸ təkliflərini gizlət</string>
<string name="revanced_hide_shorts_search_suggestions_summary_on">AxtarÄąÅŸ təklifləri gizlədilib</string>
<string name="revanced_hide_shorts_search_suggestions_summary_off">AxtarÄąÅŸ təklifləri gÃļstərilir</string>
@@ -678,7 +696,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_player_overlay_opacity_invalid_toast">OynadÄącÄą ÃļrtÃŧyÃŧnÃŧn qeyri-şəffaflığı 0-100 arasÄą olmalÄądÄąr</string>
</patch>
<patch id="layout.returnyoutubedislike.ReturnYouTubeDislikeResourcePatch">
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Gizli</string>
<!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. -->
<string name="revanced_ryd_failure_connection_timeout">\"Bəyənməmə\" mÃŧvəqqəti əlçatmazdÄąr(API vaxtÄą bitdi)</string>
<string name="revanced_ryd_failure_connection_status_code">Bəyənməmə əlçatmazdÄąr (status %d)</string>
@@ -893,6 +910,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_sb_stats_username_changed">İstifadəçi adÄą uğurla dəyişdirildi</string>
<string name="revanced_sb_stats_reputation">NÃŧfuzunuz &lt;b&gt;%.2f&lt;/b&gt;</string>
<string name="revanced_sb_stats_submissions">&lt;b&gt;%s&lt;/b&gt; bÃļlÃŧm yaratdÄąnÄąz</string>
<string name="revanced_sb_stats_submissions_sum">BÃļlÃŧmlərinizə baxmaq ÃŧçÃŧn bura toxunun</string>
<string name="revanced_sb_stats_saved_zero">SponsorBlock liderlik lÃļvhəsi</string>
<string name="revanced_sb_stats_saved">İnsanlarÄą &lt;b&gt;%s&lt;/b&gt; bÃļlÃŧmdən xilas etdiniz</string>
<string name="revanced_sb_stats_saved_sum_zero">Qlobal statistikalarÄą və başlÄąca tÃļhfəçiləri gÃļrmək ÃŧçÃŧn bura toxunun</string>
@@ -1006,7 +1024,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_alt_thumbnail_search_title">AxtarÄąÅŸ nəticələri</string>
<string name="revanced_alt_thumbnail_options_entry_1">Orijinal miniatÃŧrlər</string>
<string name="revanced_alt_thumbnail_options_entry_2">DeArrow &amp; Orijinal miniatÃŧrlər</string>
<string name="revanced_alt_thumbnail_options_entry_3">DeArrow &amp; Kadr çəkilişləri</string>
<string name="revanced_alt_thumbnail_options_entry_3">DeArrow &amp; Kadr çəkilişlər</string>
<string name="revanced_alt_thumbnail_options_entry_4">Kadr çəkilişləri</string>
<string name="revanced_alt_thumbnail_dearrow_about_summary">DeArrow YouTube videolarÄą ÃŧçÃŧn bÃļlÃŧk mənbəli miniatÃŧrlər təchiz edir. Bu miniatÃŧrlər hər zaman YouTube tərəfindən təmin edilənlərdən daha uyğun olur\n\nƏgər aktivləşdirilərsə, video URL-lər API serverinə gÃļndəriləcək və başqa heç bir məlumat gÃļndərilməyəcək. Videoda DeArrow miniatÃŧrləri yoxdursa, orijinal və ya hələ də kadr çəkilişləri gÃļstərilir\n\nDeArrow haqqÄąnda ətraflÄą Ãļyrənmək ÃŧçÃŧn bura toxun</string>
<string name="revanced_alt_thumbnail_dearrow_connection_toast_title">API əlçatan deyilsə ani bildiriş gÃļstər</string>
@@ -1114,27 +1132,23 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_slide_to_seek_summary_on">Axtarmaq ÃŧçÃŧn sÃŧrÃŧşdÃŧrmə aktivdir</string>
<string name="revanced_slide_to_seek_summary_off">Axtarmaq ÃŧçÃŧn sÃŧrÃŧşdÃŧrmə aktiv deyil</string>
</patch>
<patch id="misc.fix.playback.SpoofClientPatch">
<string name="revanced_spoof_client_screen_title">Qəbuledicini saxtalaşdÄąr</string>
<string name="revanced_spoof_client_screen_summary">Oynatma problemlərinin olmamasÄą ÃŧçÃŧn client-i saxtalaşdÄąr</string>
<string name="revanced_spoof_client_title">Qəbuledicini saxtalaşdÄąr</string>
<string name="revanced_spoof_client_summary_on">Qəbuledici saxtalaşdÄąrÄąldÄą</string>
<string name="revanced_spoof_client_summary_off">Qəbuledici dəyişməyib\n\nVideo oynatma işləməyə bilər</string>
<string name="revanced_spoof_client_user_dialog_message">Bu seçimin bağlanmasÄą, video oynatma problemlərinə səbəb ola bilər.</string>
<string name="revanced_spoof_client_type_title">Qəbuledici saxtalaşdÄąrma nÃļvÃŧ</string>
<string name="revanced_spoof_client_ios_force_avc_title">iOS AVC-yə Zorla (H.264)</string>
<string name="revanced_spoof_client_ios_force_avc_summary_on">iOS video kodlayÄącÄą AVC-dir</string>
<string name="revanced_spoof_client_ios_force_avc_summary_off">iOS video kodlayıcı AVC, VP9 və ya AV1-dir</string>
<string name="revanced_spoof_client_ios_force_avc_user_dialog_message">Bunu aktivləşdirmə batareya ÃļmrÃŧnÃŧ yaxÅŸÄąlaşdÄąra və oynatma donmasÄąnÄą dÃŧzəldə bilər.\n\nAVC maksimum 1080p gÃļrÃŧntÃŧ imkanÄąna malikdir və video oynadÄąlmasÄą VP9 və ya AV1-dən daha çox internet məlumatÄąndan istifadə edəcək.</string>
<string name="revanced_spoof_client_about_android_ios_title">iOS saxtakarlığının yan təsirləri</string>
<string name="revanced_spoof_client_about_android_ios_summary">â€ĸ HDR yalnÄąz AV1 kodlayÄącÄą ilə dəstəklənir\nâ€ĸ BaxÄąÅŸ tarixçəsi Ãļdəyici hesab ilə işləmir</string>
<string name="revanced_spoof_client_about_android_vr_title">Android VR saxtakarlığı yan təsirləri</string>
<string name="revanced_spoof_client_about_android_vr_summary">â€ĸ HDR video yoxdurâ€ĸ Uşaq videolarÄą oynadÄąlmÄąr\nâ€ĸ Fasilə verilmiş videolar gÃļzlənilmədən davam edə bilər\nâ€ĸ Aşağı keyfiyyətli Shorts axtarma çubuğu miniatÃŧrləri\nâ€ĸ \"YÃŧklə\" fəaliyyət dÃŧyməsi gizlidir\nâ€ĸ Bitiş ekran kartlarÄą gizlidir</string>
<string name="revanced_spoof_client_storyboard_timeout">Client kiçik şəkillərini təqlid etmə əlçatmazdÄąr (API vaxtÄą bitdi)</string>
<string name="revanced_spoof_client_storyboard_io_exception">Client kiçik şəkillərini təqlid etmə mÃŧvəqqəti əlçatmazdÄąr: %s</string>
</patch>
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
<patch id="misc.fix.playback.SpoofSignaturePatch">
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
<string name="revanced_spoof_video_streams_screen_title">Video yayÄąmlarÄą saxtalaşdÄąr</string>
<string name="revanced_spoof_video_streams_screen_summary">Oynatma problemlərin Ãļnləmək ÃŧçÃŧn qəbuledici video yayÄąmlarÄąn saxtalaşdÄąr</string>
<string name="revanced_spoof_video_streams_title">Video yayÄąmlarÄą saxtalaşdÄąr</string>
<string name="revanced_spoof_video_streams_summary_on">Video yayÄąmlarÄą saxtalaşdÄąrÄąlÄąr</string>
<string name="revanced_spoof_video_streams_summary_off">Video yayÄąmlarÄą saxtalaşmÄąr\n\nVideo oynatma işləməyə bilər</string>
<string name="revanced_spoof_video_streams_user_dialog_message">Bu seçimi bağlamaq, video oynatma problemlərinə səbəb olar.</string>
<string name="revanced_spoof_video_streams_client_title">İlkin qəbuledici</string>
<string name="revanced_spoof_video_streams_ios_force_avc_title">Məcburi AVC (H.264)</string>
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">Video kodlaşdÄąrma: AVC (H.264)</string>
<string name="revanced_spoof_video_streams_ios_force_avc_summary_off">Video kodlaşdÄąrma / VP9 və ya AV1</string>
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">CihazÄąnÄązÄąn VP9 hardware decoding\'i yoxdur və bu seçim, \"Qəbuledicini saxtalaşdÄąrma\" aktivləşdikdə həmişəlikdir</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Bunu aktivləşdirmə batareya ÃļmrÃŧnÃŧ yaxÅŸÄąlaşdÄąra və oynatma donmasÄąnÄą dÃŧzəldə bilər.\n\nAVC maksimum 1080p gÃļrÃŧntÃŧ imkanÄąna malikdir və video oynadÄąlmasÄą VP9 və ya AV1-dən daha çox internet məlumatÄą istifadə edəcək.</string>
<string name="revanced_spoof_video_streams_about_ios_title">iOS saxtakarlığı yan təsirləri</string>
<string name="revanced_spoof_video_streams_about_ios_summary">â€ĸ Filmlər və ya Ãļdənişli videolar oynadÄąlmaya bilər\nâ€ĸ CanlÄą yayÄąmlar əvvəldən başlayÄąr\nâ€ĸ Videolar 1 saniyə tez bitə bilər\nâ€ĸ Opus səs kodlama yoxdur</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR saxtakarlığı yan təsirləri</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">â€ĸ Səs axÄąnÄą menyusu əskikdir\nâ€ĸ Stabil səs səviyyəsi əlçatan deyil</string>
</patch>
<!-- This patch is no longer used and these strings will soon be deleted. -->
<patch id="video.hdrbrightness.HDRBrightnessPatch">

View File

@@ -32,6 +32,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
-->
<resources>
<app id="shared">
<patch id="misc.checks.BaseCheckEnvironmentPatch">
</patch>
<patch id="misc.settings.BaseSettingsResourcePatch">
<string name="revanced_settings_confirm_user_dialog_title">Đ’Ņ‹ Ņ…ĐžŅ‡Đ°Ņ†Đĩ ĐŋŅ€Đ°Ņ†ŅĐŗĐŊŅƒŅ†ŅŒ?</string>
<string name="revanced_settings_reset">ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ</string>
@@ -42,6 +44,13 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_import_reset">НаĐģĐ°Đ´Ņ‹ ReVanced ҁĐēŅ–ĐŊŅƒŅ‚Ņ‹ да ŅŅ‚Đ°ĐŊĐ´Đ°Ņ€Ņ‚ĐŊҋ҅</string>
<string name="revanced_settings_import_success">ІĐŧĐŋĐ°Ņ€Ņ‚Đ°Đ˛Đ°ĐŊа %d ĐŊаĐģад</string>
<string name="revanced_settings_import_failure_parse">ПаĐŧŅ‹ĐģĐēа Ņ–ĐŧĐŋĐ°Ņ€Ņ‚Ņƒ: %s</string>
<string name="revanced_pref_import_export_title">ІĐŧĐŋĐ°Ņ€Ņ‚ / Đ­ĐēҁĐŋĐ°Ņ€Ņ‚</string>
<string name="revanced_pref_import_export_summary">ІĐŧĐŋĐ°Ņ€Ņ‚ / Đ­ĐēҁĐŋĐ°Ņ€Ņ‚ ĐŊаĐģад ReVanced</string>
<!-- Settings about dialog. -->
<string name="revanced_settings_about_links_body">Đ’Ņ‹ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Đĩ҆Đĩ вĐĩŅ€ŅŅ–ŅŽ ReVanced Patches &lt;i&gt;%s&lt;/i&gt;</string>
<string name="revanced_settings_about_links_dev_header">ĐĐ°Ņ‚Đ°Ņ‚Đēа</string>
<string name="revanced_settings_about_links_dev_body">Đ“ŅŅ‚Đ°Ņ вĐĩŅ€ŅŅ–Ņ С\"ŅŅžĐģŅĐĩŅ†Ņ†Đ° ĐŋаĐŋŅŅ€ŅĐ´ĐŊŅĐš вĐĩŅ€ŅŅ–ŅĐš, Ņ– Đ˛Ņ‹ ĐŧĐžĐļĐ°Ņ†Đĩ ŅŅƒŅ‚Ņ‹ĐēĐŊŅƒŅ†Ņ†Đ° С ĐŊĐĩĐŋŅ€Đ°Đ´ĐąĐ°Ņ‡Đ°ĐŊŅ‹ĐŧŅ– ĐŋŅ€Đ°ĐąĐģĐĩĐŧаĐŧŅ–</string>
<string name="revanced_settings_about_links_header">ĐŅ„Ņ–Ņ†Ņ‹ĐšĐŊŅ‹Ņ ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–</string>
</patch>
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@@ -54,14 +63,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch>
</app>
<app id="youtube">
<patch id="misc.settings.SettingsResourcePatch">
<string name="revanced_settings_about_links_body">Đ’Ņ‹ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Đĩ҆Đĩ вĐĩŅ€ŅŅ–ŅŽ ReVanced Patches &lt;i&gt;%s&lt;/i&gt;</string>
<string name="revanced_settings_about_links_dev_header">ĐĐ°Ņ‚Đ°Ņ‚Đēа</string>
<string name="revanced_settings_about_links_dev_body">Đ“ŅŅ‚Đ°Ņ вĐĩŅ€ŅŅ–Ņ С\"ŅŅžĐģŅĐĩŅ†Ņ†Đ° ĐŋаĐŋŅŅ€ŅĐ´ĐŊŅĐš вĐĩŅ€ŅŅ–ŅĐš, Ņ– Đ˛Ņ‹ ĐŧĐžĐļĐ°Ņ†Đĩ ŅŅƒŅ‚Ņ‹ĐēĐŊŅƒŅ†Ņ†Đ° С ĐŊĐĩĐŋŅ€Đ°Đ´ĐąĐ°Ņ‡Đ°ĐŊŅ‹ĐŧŅ– ĐŋŅ€Đ°ĐąĐģĐĩĐŧаĐŧŅ–</string>
<string name="revanced_settings_about_links_header">ĐŅ„Ņ–Ņ†Ņ‹ĐšĐŊŅ‹Ņ ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–</string>
<string name="revanced_pref_import_export_title">ІĐŧĐŋĐ°Ņ€Ņ‚ / Đ­ĐēҁĐŋĐ°Ņ€Ņ‚</string>
<string name="revanced_pref_import_export_summary">ІĐŧĐŋĐ°Ņ€Ņ‚ / Đ­ĐēҁĐŋĐ°Ņ€Ņ‚ ĐŊаĐģад ReVanced</string>
</patch>
<patch id="misc.settings.SettingsPatch">
<string name="revanced_settings_screen_00_about_title">ĐŸŅ€Đ° ĐŋŅ€Đ°ĐŗŅ€Đ°Đŧ҃</string>
<string name="revanced_settings_screen_01_ads_title">Ай\"ŅĐ˛Ņ‹</string>
@@ -242,14 +243,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_hide_keyword_content_phrases_title">КĐģŅŽŅ‡Đ°Đ˛Ņ‹Ņ ҁĐģĐžĐ˛Ņ‹, ŅĐēŅ–Ņ Ņ‚Ņ€ŅĐąĐ° ŅŅ…Đ°Đ˛Đ°Ņ†ŅŒ</string>
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
<string name="revanced_hide_keyword_content_phrases_summary">КĐģŅŽŅ‡Đ°Đ˛Ņ‹Ņ ҁĐģĐžĐ˛Ņ‹ Ņ– Ņ„Ņ€Đ°ĐˇŅ‹, ŅĐēŅ–Ņ Ņ‚Ņ€ŅĐąĐ° ŅŅ…Đ°Đ˛Đ°Ņ†ŅŒ, ĐŋадСĐĩĐģĐĩĐŊŅ‹Ņ ĐŊĐžĐ˛Ņ‹ĐŧŅ– Ņ€Đ°Đ´ĐēаĐŧŅ–\n\nĐĄĐģĐžĐ˛Ņ‹ С Đ˛ŅĐģŅ–ĐēŅ–ĐŧŅ– ĐģŅ–Ņ‚Đ°Ņ€Đ°ĐŧŅ– ĐŋĐ°ŅŅŅ€ŅĐ´ĐˇŅ–ĐŊĐĩ Ņ‚Ņ€ŅĐąĐ° ŅžĐ˛ĐžĐ´ĐˇŅ–Ņ†ŅŒ С Đ˛ŅĐģŅ–ĐēŅ–Đŧ Ņ€ŅĐŗŅ–ŅŅ‚Ņ€Đ°Đŧ (ĐŊаĐŋҀҋĐēĐģад: iPhone, TikTok, LeBlanc)</string>
<string name="revanced_hide_keyword_content_about_title">Ай ҄ҖĐģŅŒŅ‚Ņ€Đ°Ņ†Ņ‹Ņ– ĐēĐģŅŽŅ‡Đ°Đ˛Ņ‹Ņ… ҁĐģĐžŅž</string>
<string name="revanced_hide_keyword_content_about_summary">ГаĐģĐžŅžĐŊĐ°Ņ/ПадĐŋҖҁĐēа/Đ’Ņ‹ĐŊŅ–ĐēŅ– ĐŋĐžŅˆŅƒĐē҃ ҄ҖĐģŅŒŅ‚Ņ€ŅƒŅŽŅ†Ņ†Đ°, Đēай ŅŅ…Đ°Đ˛Đ°Ņ†ŅŒ СĐŧĐĩŅŅ†Ņ–Đ˛Đ°, ŅĐēĐžĐĩ ҁ҃ĐŋадаĐĩ С ĐēĐģŅŽŅ‡Đ°Đ˛Ņ‹ĐŧŅ– Ņ„Ņ€Đ°ĐˇĐ°ĐŧŅ–\n\nАйĐŧĐĩĐļаваĐŊĐŊŅ–\nâ€ĸ НĐĩĐēĐ°Ņ‚ĐžŅ€Ņ‹Ņ ŅˆĐžŅ€Ņ‚Ņ‹ ĐŧĐžĐŗŅƒŅ†ŅŒ ĐąŅ‹Ņ†ŅŒ ĐŊĐĩ ŅŅ…Đ°Đ˛Đ°ĐŊŅ‹\nâ€ĸ НĐĩĐēĐ°Ņ‚ĐžŅ€Ņ‹Ņ ĐēаĐŧĐŋаĐŊĐĩĐŊ҂ҋ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊҖ҆ĐēĐ°ĐŗĐ° Ņ–ĐŊŅ‚ŅŅ€Ņ„ĐĩĐšŅŅƒ ĐŧĐžĐŗŅƒŅ†ŅŒ ĐąŅ‹Ņ†ŅŒ ĐŊĐĩ ŅŅ…Đ°Đ˛Đ°ĐŊŅ‹\nâ€ĸ ĐŸĐžŅˆŅƒĐē Đŋа ĐēĐģŅŽŅ‡Đ°Đ˛Ņ‹Đŧ ҁĐģОвĐĩ ĐŧĐžĐļа ĐŊĐĩ Đ´Đ°Ņ†ŅŒ Đ˛Ņ‹ĐŊŅ–ĐēĐ°Ņž</string>
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_hide_keyword_toast_invalid_common">ĐŅĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊаĐĩ ĐēĐģŅŽŅ‡Đ°Đ˛ĐžĐĩ ҁĐģОва. НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ: \"%s\" ҃ ŅĐēĐ°ŅŅ†Ņ– ҄ҖĐģŅŒŅ‚Ņ€Đ°</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_hide_keyword_toast_invalid_length">ĐŅĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊаĐĩ ĐēĐģŅŽŅ‡Đ°Đ˛ĐžĐĩ ҁĐģОва. \"%1$s\" СĐŧŅŅˆŅ‡Đ°Đĩ ĐŧĐĩĐŊ҈ Са %2$d ҁҖĐŧваĐģĐ°Ņž</string>
<string name="revanced_hide_keyword_toast_invalid_broad">КĐģŅŽŅ‡Đ°Đ˛ĐžĐĩ ҁĐģОва \"%s\" ŅŅ…Đ°Đ˛Đ°Đĩ ŅžŅĐĩ Đ˛Ņ–Đ´ŅĐ°</string>
</patch>
<patch id="ad.general.HideAdsResourcePatch">
<string name="revanced_hide_general_ads_title">ĐĄŅ…Đ°Đ˛Đ°Ņ†ŅŒ Đ°ĐŗŅƒĐģҌĐŊŅƒŅŽ Ņ€ŅĐēĐģаĐŧ҃</string>
@@ -678,7 +674,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_player_overlay_opacity_invalid_toast">НĐĩĐŋŅ€Đ°ĐˇŅ€Ņ‹ŅŅ‚Đ°ŅŅ†ŅŒ ĐŊаĐēĐģадаĐŊĐŊŅ ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐģҌĐŊŅ–Đēа ĐŋĐ°Đ˛Ņ–ĐŊĐŊа ĐąŅ‹Ņ†ŅŒ ĐŋаĐŧŅ–Đļ 0-100</string>
</patch>
<patch id="layout.returnyoutubedislike.ReturnYouTubeDislikeResourcePatch">
<string name="revanced_ryd_video_likes_hidden_by_video_owner">ĐĄŅ…Đ°Đ˛Đ°ĐŊŅ‹</string>
<!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. -->
<string name="revanced_ryd_failure_connection_timeout">АдСĐŊаĐēŅ– \"НĐĩ ĐŋадайаĐĩŅ†Ņ†Đ°\" Ņ‡Đ°ŅĐžĐ˛Đ° ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹Ņ (Ņ‡Đ°Ņ Ņ‡Đ°ĐēаĐŊĐŊŅ API ҁĐēĐžĐŊŅ‡Ņ‹ŅžŅŅ)</string>
<string name="revanced_ryd_failure_connection_status_code">Đ”Ņ‹ĐˇĐģаКĐēŅ– ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹Ņ (ŅŅ‚Đ°Ņ‚ŅƒŅ %d)</string>
@@ -1112,19 +1107,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_slide_to_seek_summary_on">ĐĄĐģаКд Đ´ĐģŅ ĐŋĐžŅˆŅƒĐē҃ ŅžĐēĐģŅŽŅ‡Đ°ĐŊŅ‹</string>
<string name="revanced_slide_to_seek_summary_off">ĐĄĐģаКд Đ´ĐģŅ ĐŋĐžŅˆŅƒĐē҃ ĐŊĐĩ ŅžĐēĐģŅŽŅ‡Đ°ĐŊŅ‹</string>
</patch>
<patch id="misc.fix.playback.SpoofClientPatch">
<string name="revanced_spoof_client_screen_title">ПадĐŧаĐŊ ĐēĐģŅ–ĐĩĐŊŅ‚Đ°</string>
<string name="revanced_spoof_client_screen_summary">ĐŸĐ°Đ´Ņ€ĐžĐąĐēа ĐēĐģŅ–ĐĩĐŊŅ‚Đ°, Đēай ĐŋŅ€Đ°Đ´ŅƒŅ…Ņ–ĐģŅ–Ņ†ŅŒ ĐŋŅ€Đ°ĐąĐģĐĩĐŧŅ‹ С ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊĐĩĐŧ</string>
<string name="revanced_spoof_client_title">ПадĐŧаĐŊ ĐēĐģŅ–ĐĩĐŊŅ‚Đ°</string>
<string name="revanced_spoof_client_summary_on">КĐģŅ–ĐĩĐŊŅ‚ ĐŋĐ°Đ´Ņ€ĐžĐąĐģĐĩĐŊŅ‹</string>
<string name="revanced_spoof_client_summary_off">КĐģŅ–ĐĩĐŊŅ‚ ĐŊĐĩ ĐŋĐ°Đ´Ņ€ĐžĐąĐģĐĩĐŊŅ‹\n\nĐŸŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊĐĩ Đ˛Ņ–Đ´ŅĐ° ĐŧĐžĐļа ĐŊĐĩ ĐŋŅ€Đ°Ņ†Đ°Đ˛Đ°Ņ†ŅŒ</string>
<string name="revanced_spoof_client_user_dialog_message">АдĐēĐģŅŽŅ‡ŅĐŊĐŊĐĩ ĐŗŅŅ‚Đ°Đš ĐŊаĐģĐ°Đ´Ņ‹ ĐŧĐžĐļа Đ˛Ņ‹ĐēĐģŅ–ĐēĐ°Ņ†ŅŒ ĐŋŅ€Đ°ĐąĐģĐĩĐŧŅ‹ С ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊĐĩĐŧ Đ˛Ņ–Đ´ŅĐ°.</string>
<string name="revanced_spoof_client_about_android_vr_summary">â€ĸ ĐŅĐŧа HDR-Đ˛Ņ–Đ´ŅĐ°\nâ€ĸ Đ”ĐˇŅ–Ņ†ŅŅ‡Ņ‹Ņ Đ˛Ņ–Đ´ŅĐ° ĐŊĐĩ ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ŅŽŅ†Ņ†Đ°\nâ€ĸ ĐŸŅ€Ņ‹ĐŋŅ‹ĐŊĐĩĐŊŅ‹Ņ Đ˛Ņ–Đ´ŅĐ° ĐŧĐžĐŗŅƒŅ†ŅŒ адĐŊĐ°ŅžĐģŅŅ†Ņ†Đ° Đ˛Ņ‹ĐŋадĐēĐžĐ˛Ņ‹Đŧ ҇ҋĐŊаĐŧ\nâ€ĸ ĐŅ–ĐˇĐēĐ°Ņ ŅĐēĐ°ŅŅ†ŅŒ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€ ĐŊа ĐŋаĐŊŅĐģŅ– ĐŋĐžŅˆŅƒĐē҃ Shorts\nâ€ĸ КĐŊĐžĐŋĐēа дСĐĩŅĐŊĐŊŅ ĐĄĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ ŅŅ…Đ°Đ˛Đ°ĐŊа\nâ€ĸ ĐšĐ°Ņ€Ņ‚ĐēŅ– ĐēаĐŊŅ†Đ°Đ˛ĐžĐŗĐ° ŅĐēŅ€Đ°ĐŊа ŅŅ…Đ°Đ˛Đ°ĐŊŅ‹</string>
<string name="revanced_spoof_client_storyboard_timeout">ĐœŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Ņ‹ ĐēĐģŅ–ĐĩĐŊŅ‚Đ° Spoof ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹Ņ (Ņ‡Đ°Ņ Ņ‡Đ°ĐēаĐŊĐŊŅ API ҁĐēĐžĐŊŅ‡Ņ‹ŅžŅŅ)</string>
<string name="revanced_spoof_client_storyboard_io_exception">ĐœŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Ņ‹ ĐēĐģŅ–ĐĩĐŊŅ‚Đ° Spoof Ņ‡Đ°ŅĐžĐ˛Đ° ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹Ņ: %s</string>
</patch>
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
<patch id="misc.fix.playback.SpoofSignaturePatch">
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
<string name="revanced_spoof_video_streams_user_dialog_message">АдĐēĐģŅŽŅ‡ŅĐŊĐŊĐĩ ĐŗŅŅ‚Đ°Đš ĐŊаĐģĐ°Đ´Ņ‹ ĐŧĐžĐļа Đ˛Ņ‹ĐēĐģŅ–ĐēĐ°Ņ†ŅŒ ĐŋŅ€Đ°ĐąĐģĐĩĐŧŅ‹ С ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊĐĩĐŧ Đ˛Ņ–Đ´ŅĐ°.</string>
</patch>
<!-- This patch is no longer used and these strings will soon be deleted. -->
<patch id="video.hdrbrightness.HDRBrightnessPatch">

View File

@@ -32,16 +32,35 @@ This is because Crowdin requires temporarily flattening this file and removing t
-->
<resources>
<app id="shared">
<patch id="misc.checks.BaseCheckEnvironmentPatch">
<string name="revanced_check_environment_failed_title">ĐŸŅ€ĐžĐ˛ĐĩŅ€ĐēĐ°Ņ‚Đ° Đĩ ĐŊĐĩ҃ҁĐŋĐĩ҈ĐŊа</string>
<string name="revanced_check_environment_dialog_open_official_source_button">ĐžŅ‚Đ˛ĐžŅ€ĐĩŅ‚Đĩ ĐžŅ„Đ¸Ņ†Đ¸Đ°ĐģĐŊĐ¸Ņ ҃ĐĩĐąŅĐ°ĐšŅ‚</string>
<string name="revanced_check_environment_dialog_ignore_button">ĐŸŅ€ĐžĐŋ҃ҁĐŊи</string>
<string name="revanced_check_environment_failed_message">&lt;h5&gt;Đ˜ĐˇĐŗĐģĐĩĐļда, ҇Đĩ Ņ‚ĐžĐ˛Đ° ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ ĐŊĐĩ Đĩ ĐēĐžŅ€Đ¸ĐŗĐ¸Ņ€Đ°ĐŊĐž ĐžŅ‚ Đ˛Đ°Ņ.&lt;/h5&gt;&lt;br&gt;ĐĸОва ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ ĐŧĐžĐļĐĩ да ĐŊĐĩ Ņ„ŅƒĐŊĐēŅ†Đ¸ĐžĐŊĐ¸Ņ€Đ° ĐŋŅ€Đ°Đ˛Đ¸ĐģĐŊĐž, &lt;b&gt;ĐŧĐžĐļĐĩ да ĐąŅŠĐ´Đĩ Đ˛Ņ€ĐĩĐ´ĐŊĐž иĐģи Đ´ĐžŅ€Đ¸ ĐžĐŋĐ°ŅĐŊĐž Са иСĐŋĐžĐģСваĐŊĐĩ&lt;/b&gt;.&lt; br&gt;&lt;br&gt;ĐĸĐĩСи ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēи ĐŋŅ€ĐĩĐ´ĐŋĐžĐģĐ°ĐŗĐ°Ņ‚, ҇Đĩ Ņ‚ĐžĐ˛Đ° ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ Đĩ ĐŋŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģĐŊĐž ĐēĐžŅ€Đ¸ĐŗĐ¸Ņ€Đ°ĐŊĐž иĐģи ĐŋĐžĐģŅƒŅ‡ĐĩĐŊĐž ĐžŅ‚ ĐŊŅĐēОК Đ´Ņ€ŅƒĐŗ:&lt;br&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt;ХиĐģĐŊĐž ҁĐĩ ĐŋŅ€ĐĩĐŋĐžŅ€ŅŠŅ‡Đ˛Đ° да &lt;b&gt;Đ´ĐĩиĐŊŅŅ‚Đ°ĐģĐ¸Ņ€Đ°ĐšŅ‚Đĩ Ņ‚ĐžĐ˛Đ° ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ и ĐŗĐž ĐŋĐžĐŋŅ€Đ°Đ˛ĐĩŅ‚Đĩ ŅĐ°Đŧи&lt;/b&gt; Са да ҁ҂Đĩ ŅĐ¸ĐŗŅƒŅ€ĐŊи, ҇Đĩ иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ ваĐģĐ¸Đ´Đ¸Ņ€Đ°ĐŊĐž и ĐˇĐ°Ņ‰Đ¸Ņ‚ĐĩĐŊĐž ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ.&lt;p&gt;&lt;br&gt;АĐēĐž ĐąŅŠĐ´Đĩ ĐŋŅ€ĐĩĐŊĐĩĐąŅ€ĐĩĐŗĐŊĐ°Ņ‚Đž, Ņ‚ĐžĐ˛Đ° ĐŋŅ€ĐĩĐ´ŅƒĐŋŅ€ĐĩĐļĐ´ĐĩĐŊиĐĩ ҉Đĩ ҁĐĩ ĐŋĐžĐēаĐļĐĩ ŅĐ°ĐŧĐž два ĐŋŅŠŅ‚Đ¸.</string>
<string name="revanced_check_environment_not_same_patching_device">ĐšĐžŅ€Đ¸ĐŗĐ¸Ņ€Đ°ĐŊĐž ĐŊа Đ´Ņ€ŅƒĐŗĐž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž</string>
<string name="revanced_check_environment_manager_not_expected_installer">НĐĩ Đĩ иĐŊŅŅ‚Đ°ĐģĐ¸Ņ€Đ°ĐŊ ReVanced Manager</string>
<string name="revanced_check_environment_not_near_patch_time">Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐž ĐŋŅ€Đĩди ĐŋОвĐĩ҇Đĩ ĐžŅ‚ 10 ĐŧиĐŊŅƒŅ‚Đ¸</string>
<string name="revanced_check_environment_not_near_patch_time_days">ĐšĐžŅ€Đ¸ĐŗĐ¸Ņ€Đ°ĐŊĐž ĐŋŅ€Đĩди %s Đ´ĐŊи</string>
<string name="revanced_check_environment_not_near_patch_time_invalid">Đ”Đ°Ņ‚Đ°Ņ‚Đ° ĐŊа ĐēĐžĐŧĐŋиĐģĐ°Ņ†Đ¸Ņ ĐŊа APK Đĩ ĐŋĐžĐ˛Ņ€ĐĩĐ´ĐĩĐŊа</string>
</patch>
<patch id="misc.settings.BaseSettingsResourcePatch">
<string name="revanced_settings_confirm_user_dialog_title">Đ˜ŅĐēĐ°Ņ‚Đĩ Đģи да ĐŋŅ€ĐžĐ´ŅŠĐģĐļĐ¸Ņ‚Đĩ?</string>
<string name="revanced_settings_reset">ĐŅƒĐģĐ¸Ņ€Đ°ĐŊĐĩ</string>
<string name="revanced_settings_restart_title">ОĐŋŅ€ĐĩҁĐŊĐĩŅ‚Đĩ и Ņ€ĐĩŅŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°ĐšŅ‚Đĩ</string>
<string name="revanced_settings_restart_title">Đ ĐĩŅŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°Đš и ĐžĐŋŅ€ĐĩҁĐŊи</string>
<string name="revanced_settings_restart">Đ ĐĩŅŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ</string>
<string name="revanced_settings_import">ИĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ</string>
<string name="revanced_settings_import_copy">КоĐŋĐ¸Ņ€Đ°ĐŊĐĩ</string>
<string name="revanced_settings_import_reset">ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ ĐŊа ReVanced ĐąŅŅ…Đ° ĐŊ҃ĐģĐ¸Ņ€Đ°ĐŊи</string>
<string name="revanced_settings_import_success">ĐĄĐģĐĩĐ´ĐŊĐ¸Ņ‚Đĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐąŅŅ…Đ° иĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊи ҃ҁĐŋĐĩ҈ĐŊĐž: %d</string>
<string name="revanced_settings_import_failure_parse">ИĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž ĐąĐĩ҈Đĩ ĐŊĐĩ҃ҁĐŋĐĩ҈ĐŊĐž: %s</string>
<string name="revanced_pref_import_export_title">ИĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ / ЕĐēҁĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ</string>
<string name="revanced_pref_import_export_summary">ИĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ / ЕĐēҁĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ReVanced ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ</string>
<!-- Settings about dialog. -->
<string name="revanced_settings_about_links_body">ВиĐĩ иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ вĐĩŅ€ŅĐ¸ŅŅ‚Đ° ĐŊа ReVanced Patches&lt;i&gt;%s&lt;/i&gt;</string>
<string name="revanced_settings_about_links_dev_header">ЗабĐĩĐģĐĩĐļĐēа</string>
<string name="revanced_settings_about_links_dev_body">ĐĸаСи вĐĩŅ€ŅĐ¸Ņ Đĩ ĐŋŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģĐŊа, Ņ‚Đ°Đēа ҇Đĩ ĐŧĐžĐļĐĩ да ҁҀĐĩ҉ĐŊĐĩŅ‚Đĩ ĐŊĐĩĐžŅ‡Đ°ĐēваĐŊи ĐŋŅ€ĐžĐąĐģĐĩĐŧи</string>
<string name="revanced_settings_about_links_header">ĐžŅ„Đ¸Ņ†Đ¸Đ°ĐģĐŊи ĐģиĐŊĐēОвĐĩ</string>
<string name="revanced_settings_about_links_donate">Đ”Đ°Ņ€ĐĩĐŊиĐĩ</string>
</patch>
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@@ -54,14 +73,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch>
</app>
<app id="youtube">
<patch id="misc.settings.SettingsResourcePatch">
<string name="revanced_settings_about_links_body">ВиĐĩ иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ вĐĩŅ€ŅĐ¸ŅŅ‚Đ° ĐŊа ReVanced Patches&lt;i&gt;%s&lt;/i&gt;</string>
<string name="revanced_settings_about_links_dev_header">ЗабĐĩĐģĐĩĐļĐēа</string>
<string name="revanced_settings_about_links_dev_body">ĐĸаСи вĐĩŅ€ŅĐ¸Ņ Đĩ ĐŋŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģĐŊа, Ņ‚Đ°Đēа ҇Đĩ ĐŧĐžĐļĐĩ да ҁҀĐĩ҉ĐŊĐĩŅ‚Đĩ ĐŊĐĩĐžŅ‡Đ°ĐēваĐŊи ĐŋŅ€ĐžĐąĐģĐĩĐŧи</string>
<string name="revanced_settings_about_links_header">ĐžŅ„Đ¸Ņ†Đ¸Đ°ĐģĐŊи ĐģиĐŊĐēОвĐĩ</string>
<string name="revanced_pref_import_export_title">ИĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ / ЕĐēҁĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ</string>
<string name="revanced_pref_import_export_summary">ИĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ / ЕĐēҁĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ReVanced ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ</string>
</patch>
<patch id="misc.settings.SettingsPatch">
<string name="revanced_settings_screen_00_about_title">ĐžŅ‚ĐŊĐžŅĐŊĐž</string>
<string name="revanced_settings_screen_01_ads_title">Đ ĐĩĐēĐģаĐŧи</string>
@@ -242,14 +253,18 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_hide_keyword_content_phrases_title">КĐģŅŽŅ‡ĐžĐ˛Đ¸ Đ´ŅƒĐŧи, ĐēĐžĐ¸Ņ‚Đž да ĐąŅŠĐ´Đ°Ņ‚ ҁĐēŅ€Đ¸Ņ‚Đ¸</string>
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
<string name="revanced_hide_keyword_content_phrases_summary">КĐģŅŽŅ‡ĐžĐ˛Đ¸ Đ´ŅƒĐŧи и Ņ„Ņ€Đ°ĐˇĐ¸, ĐēĐžĐ¸Ņ‚Đž да ĐąŅŠĐ´Đ°Ņ‚ ҁĐēŅ€Đ¸Ņ‚Đ¸, Ņ€Đ°ĐˇĐ´ĐĩĐģĐĩĐŊи ҁ ĐŊОви Ņ€ĐĩдОвĐĩ\n\nĐ”ŅƒĐŧи ҁ ĐŗĐģавĐŊи ĐąŅƒĐēви в ҁҀĐĩĐ´Đ°Ņ‚Đ° Ņ‚Ņ€ŅĐąĐ˛Đ° да ĐąŅŠĐ´Đ°Ņ‚ Đ˛ŅŠĐ˛ĐĩĐ´ĐĩĐŊи ҁ ĐŗĐžĐģĐĩĐŧи ĐąŅƒĐēви (ĐŊаĐŋŅ€Đ¸ĐŧĐĩŅ€: iPhone, TikTok, LeBlanc)</string>
<string name="revanced_hide_keyword_content_phrases_summary">КĐģŅŽŅ‡ĐžĐ˛Đ¸ Đ´ŅƒĐŧи и Ņ„Ņ€Đ°ĐˇĐ¸ Са ҁĐēŅ€Đ¸Đ˛Đ°ĐŊĐĩ, Ņ€Đ°ĐˇĐ´ĐĩĐģĐĩĐŊи ҁ ĐŊОви Ņ€ĐĩдОвĐĩ\n\nКĐģŅŽŅ‡ĐžĐ˛Đ¸Ņ‚Đĩ Đ´ŅƒĐŧи ĐŧĐžĐŗĐ°Ņ‚ да ĐąŅŠĐ´Đ°Ņ‚ иĐŧĐĩĐŊа ĐŊа ĐēаĐŊаĐģи иĐģи Đ˛ŅĐĩĐēи Ņ‚ĐĩĐēҁ҂, ĐŋĐžĐēаСаĐŊ в ĐˇĐ°ĐŗĐģĐ°Đ˛Đ¸ŅŅ‚Đ° ĐŊа видĐĩĐžĐēĐģиĐŋОвĐĩŅ‚Đĩ\n\nĐ”ŅƒĐŧĐ¸Ņ‚Đĩ ҁ ĐŗĐģавĐŊи ĐąŅƒĐēви в ҁҀĐĩĐ´Đ°Ņ‚Đ° Ņ‚Ņ€ŅĐąĐ˛Đ° да ĐąŅŠĐ´Đ°Ņ‚ Đ˛ŅŠĐ˛ĐĩĐ´ĐĩĐŊи ҁ ĐŧаĐģĐēи ĐąŅƒĐēви (ĐŊаĐŋŅ€.: iPhone, TikTok, LeBlanc)</string>
<string name="revanced_hide_keyword_content_about_title">За Ņ„Đ¸ĐģŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ҁ ĐēĐģŅŽŅ‡ĐžĐ˛Đ¸ Đ´ŅƒĐŧи</string>
<string name="revanced_hide_keyword_content_about_summary">ĐĐ°Ņ‡Đ°ĐģĐž/АйОĐŊаĐŧĐĩĐŊŅ‚/Đ ĐĩĐˇŅƒĐģŅ‚Đ°Ņ‚Đ¸Ņ‚Đĩ ĐžŅ‚ Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩŅ‚Đž ҁĐĩ Ņ„Đ¸ĐģŅ‚Ņ€Đ¸Ņ€Đ°Ņ‚, Са да ҁĐĩ ҁĐēŅ€Đ¸Đĩ ŅŅŠĐ´ŅŠŅ€ĐļаĐŊиĐĩ, ĐēĐžĐĩŅ‚Đž ŅŅŠĐžŅ‚Đ˛ĐĩŅ‚ŅŅ‚Đ˛Đ° ĐŊа ĐēĐģŅŽŅ‡ĐžĐ˛Đ¸ Ņ„Ņ€Đ°ĐˇĐ¸\n\nĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊĐ¸Ņ\nâ€ĸ ĐŅĐēОи Shorts ĐŧĐžĐļĐĩ да ĐŊĐĩ ŅĐ° ҁĐēŅ€Đ¸Ņ‚Đ¸\nâ€ĸ ĐŅĐēОи ĐēĐžĐŧĐŋĐžĐŊĐĩĐŊŅ‚Đ¸ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģҁĐēĐ¸Ņ иĐŊŅ‚ĐĩҀ҄ĐĩĐšŅ ĐŧĐžĐļĐĩ да ĐŊĐĩ ŅĐ° ҁĐēŅ€Đ¸Ņ‚Đ¸\nâ€ĸ ĐĸŅŠŅ€ŅĐĩĐŊĐĩŅ‚Đž ĐŊа ĐēĐģŅŽŅ‡ĐžĐ˛Đ° Đ´ŅƒĐŧа ĐŧĐžĐļĐĩ да ĐŊĐĩ ĐŋĐžĐēаĐļĐĩ Ņ€ĐĩĐˇŅƒĐģŅ‚Đ°Ņ‚Đ¸</string>
<string name="revanced_hide_keyword_content_about_summary">ĐĐ°Ņ‡Đ°ĐģĐž/АйОĐŊаĐŧĐĩĐŊŅ‚/Đ ĐĩĐˇŅƒĐģŅ‚Đ°Ņ‚Đ¸Ņ‚Đĩ ĐžŅ‚ Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩŅ‚Đž ҁĐĩ Ņ„Đ¸ĐģŅ‚Ņ€Đ¸Ņ€Đ°Ņ‚, Са да ҁĐĩ ҁĐēŅ€Đ¸Đĩ ŅŅŠĐ´ŅŠŅ€ĐļаĐŊиĐĩ, ĐēĐžĐĩŅ‚Đž ŅŅŠĐžŅ‚Đ˛ĐĩŅ‚ŅŅ‚Đ˛Đ° ĐŊа ĐēĐģŅŽŅ‡ĐžĐ˛Đ¸ Ņ„Ņ€Đ°ĐˇĐ¸\n\nĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊĐ¸Ņ\nâ€ĸ Shorts ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ĐąŅŠĐ´Đ°Ņ‚ ҁĐēŅ€Đ¸Ņ‚Đ¸ ĐžŅ‚ иĐŧĐĩŅ‚Đž ĐŊа ĐēаĐŊаĐģа\nâ€ĸ ĐŅĐēОи ĐēĐžĐŧĐŋĐžĐŊĐĩĐŊŅ‚Đ¸ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģҁĐēĐ¸Ņ иĐŊŅ‚ĐĩҀ҄ĐĩĐšŅ ĐŧĐžĐļĐĩ да ĐŊĐĩ ŅĐ° ҁĐēŅ€Đ¸Ņ‚Đ¸\nâ€ĸ ĐĸŅŠŅ€ŅĐĩĐŊĐĩŅ‚Đž ĐŋĐž ĐēĐģŅŽŅ‡ĐžĐ˛Đ° Đ´ŅƒĐŧа ĐŧĐžĐļĐĩ да ĐŊĐĩ ĐŋĐžĐēаĐļĐĩ Ņ€ĐĩĐˇŅƒĐģŅ‚Đ°Ņ‚Đ¸</string>
<string name="revanced_hide_keyword_content_about_whole_words_title">ĐĄŅŠĐ˛ĐŋадĐĩĐŊиĐĩ ĐŊа Đ˛ŅĐ¸Ņ‡Đēи Đ´ŅƒĐŧи</string>
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
<string name="revanced_hide_keyword_content_about_whole_words_summary">ĐžĐŗŅ€Đ°ĐļдаĐŊĐĩŅ‚Đž ĐŊа ĐēĐģŅŽŅ‡ĐžĐ˛Đ° Đ´ŅƒĐŧа/Ņ„Ņ€Đ°ĐˇĐ° ҁ двОКĐŊи ĐēĐ°Đ˛Đ¸Ņ‡Đēи ҉Đĩ ĐŋŅ€ĐĩĐ´ĐžŅ‚Đ˛Ņ€Đ°Ņ‚Đ¸ Ņ‡Đ°ŅŅ‚Đ¸Ņ‡ĐŊи ŅŅŠĐ˛ĐŋадĐĩĐŊĐ¸Ņ ĐŊа ĐˇĐ°ĐŗĐģĐ°Đ˛Đ¸Ņ ĐŊа видĐĩĐžĐēĐģиĐŋОвĐĩ и иĐŧĐĩĐŊа ĐŊа ĐēаĐŊаĐģи&lt;br&gt;&lt;br&gt;НаĐŋŅ€Đ¸ĐŧĐĩŅ€,&lt;br&gt;&lt;b&gt;\"ai\"&lt;/b&gt; ҉Đĩ ҁĐēŅ€Đ¸Đĩ видĐĩĐžĐēĐģиĐŋа: &lt;b&gt;How does AI work?&lt;/b&gt;&lt;br&gt;ĐŊĐž ĐŊŅĐŧа да ҁĐēŅ€Đ¸Đĩ: &lt;b&gt;What does fair use mean?&lt;/b&gt;</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_hide_keyword_toast_invalid_common">НĐĩваĐģидĐŊа Đ´ŅƒĐŧа. НĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ иСĐŋĐžĐģСва: \'%s\' ĐēĐ°Ņ‚Đž Ņ„Đ¸ĐģŅ‚ŅŠŅ€</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_hide_keyword_toast_invalid_length">НĐĩваĐģидĐŊа ĐēĐģŅŽŅ‡ĐžĐ˛Đ° Đ´ŅƒĐŧа. %1$s Đĩ ĐŋĐž-ĐŧаĐģĐēĐž ĐžŅ‚ %2$d СĐŊаĐēа</string>
<string name="revanced_hide_keyword_toast_invalid_broad">КĐģŅŽŅ‡ĐžĐ˛Đ°Ņ‚Đ° Đ´ŅƒĐŧа \'%s\' ҉Đĩ ҁĐēŅ€Đ¸Đĩ Đ˛ŅĐ¸Ņ‡Đēи видĐĩĐžĐēĐģиĐŋОвĐĩ</string>
<string name="revanced_hide_keyword_toast_invalid_common">НĐĩ ĐŧĐžĐļĐĩŅ‚Đĩ да иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ ĐēĐģŅŽŅ‡ĐžĐ˛Đ°Ņ‚Đ° Đ´ŅƒĐŧа: %s</string>
<string name="revanced_hide_keyword_toast_invalid_common_whole_word_required">ДобавĐĩŅ‚Đĩ ĐēĐ°Đ˛Đ¸Ņ‡Đēи, Са да иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ ĐēĐģŅŽŅ‡ĐžĐ˛Đ° Đ´ŅƒĐŧа: %s</string>
<string name="revanced_hide_keyword_toast_invalid_conflicting">КĐģŅŽŅ‡ĐžĐ˛Đ°Ņ‚Đ° Đ´ŅƒĐŧа иĐŧа ĐŋŅ€ĐžŅ‚Đ¸Đ˛ĐžŅ€ĐĩŅ‡Đ¸Đ˛Đ¸ Ņ‚Đ˛ŅŠŅ€Đ´ĐĩĐŊĐ¸Ņ: %s</string>
<string name="revanced_hide_keyword_toast_invalid_length">КĐģŅŽŅ‡ĐžĐ˛Đ°Ņ‚Đ° Đ´ŅƒĐŧа Đĩ Ņ‚Đ˛ŅŠŅ€Đ´Đĩ ĐēŅ€Đ°Ņ‚Đēа и Đ¸ĐˇĐ¸ŅĐēва ĐēĐ°Đ˛Đ¸Ņ‡Đēи: %s</string>
<string name="revanced_hide_keyword_toast_invalid_broad">Đ’ŅĐ¸Ņ‡Đēи видĐĩа ҁ ĐēĐģŅŽŅ‡ĐžĐ˛Đ°Ņ‚Đ° Đ´ŅƒĐŧа ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ ҁĐēŅ€Đ¸Ņ‚Đ¸: %s</string>
</patch>
<patch id="ad.general.HideAdsResourcePatch">
<string name="revanced_hide_general_ads_title">ĐĄĐēŅ€Đ¸Đ˛Đ°ĐŊĐĩ ĐŊа ĐžĐąŅ‰Đ¸Ņ‚Đĩ Ņ€ĐĩĐēĐģаĐŧи</string>
@@ -615,6 +630,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_hide_shorts_save_sound_button_title">Đ‘ŅƒŅ‚ĐžĐŊ Са СаĐŋаСваĐŊĐĩ ĐŊа Đ°ŅƒĐ´Đ¸ĐžŅ‚Đž в ĐŋĐģĐĩĐšĐģĐ¸ŅŅ‚Đ°</string>
<string name="revanced_hide_shorts_save_sound_button_summary_on">Đ‘ŅƒŅ‚ĐžĐŊŅŠŅ‚ Са ЗаĐŋаСваĐŊĐĩ в ĐŋĐģĐĩĐšĐģĐ¸ŅŅ‚Đ° Đĩ ҁĐēŅ€Đ¸Ņ‚</string>
<string name="revanced_hide_shorts_save_sound_button_summary_off">Đ‘ŅƒŅ‚ĐžĐŊŅŠŅ‚ Са ЗаĐŋаСваĐŊĐĩ в ĐŋĐģĐĩĐšĐģĐ¸ŅŅ‚Đ° ҁĐĩ ĐŋĐžĐēаСва</string>
<string name="revanced_hide_shorts_use_this_sound_button_title">Đ‘ŅƒŅ‚ĐžĐŊ „ИзĐŋĐžĐģСваĐŊĐĩ ĐŊа Ņ‚ĐžĐˇĐ¸ ĐˇĐ˛ŅƒĐē“</string>
<string name="revanced_hide_shorts_use_this_sound_button_summary_on">Đ‘ŅƒŅ‚ĐžĐŊ „ИзĐŋĐžĐģСваĐŊĐĩ ĐŊа Ņ‚ĐžĐˇĐ¸ ĐˇĐ˛ŅƒĐē“ Đĩ ҁĐēŅ€Đ¸Ņ‚</string>
<string name="revanced_hide_shorts_use_this_sound_button_summary_off">Đ‘ŅƒŅ‚ĐžĐŊ „ИзĐŋĐžĐģСваĐŊĐĩ ĐŊа Ņ‚ĐžĐˇĐ¸ ĐˇĐ˛ŅƒĐē“ ҁĐĩ ĐŋĐžĐēаСва</string>
<string name="revanced_hide_shorts_search_suggestions_title">ĐĄĐēŅ€Đ¸Đ˛Đ°ĐŊĐĩ ĐŊа ĐŋŅ€ĐĩĐ´ĐģĐžĐļĐĩĐŊĐ¸ŅŅ‚Đ° Са Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ</string>
<string name="revanced_hide_shorts_search_suggestions_summary_on">ĐŸŅ€ĐĩĐ´ĐģĐžĐļĐĩĐŊĐ¸ŅŅ‚Đ° Са Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ ŅĐ° ҁĐēŅ€Đ¸Ņ‚Đ¸</string>
<string name="revanced_hide_shorts_search_suggestions_summary_off">ĐŸŅ€ĐĩĐ´ĐģĐžĐļĐĩĐŊĐ¸ŅŅ‚Đ° Са Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ ҁĐĩ ĐŋĐžĐēĐ°ĐˇĐ˛Đ°Ņ‚</string>
@@ -678,7 +696,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_player_overlay_opacity_invalid_toast">ĐŸŅ€ĐžĐˇŅ€Đ°Ņ‡ĐŊĐžŅŅ‚Ņ‚Đ° ĐŊа ĐŧĐĩĐŊŅŽŅ‚Đž ĐŊа ĐŋĐģĐĩĐšŅŠŅ€Đ° Ņ‚Ņ€ŅĐąĐ˛Đ° да ĐąŅŠĐ´Đĩ ĐŧĐĩĐļĐ´Ņƒ 0-100</string>
</patch>
<patch id="layout.returnyoutubedislike.ReturnYouTubeDislikeResourcePatch">
<string name="revanced_ryd_video_likes_hidden_by_video_owner">ĐĄĐēŅ€Đ¸Ņ‚Đž</string>
<!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. -->
<string name="revanced_ryd_failure_connection_timeout">НĐĩŅ…Đ°Ņ€ĐĩŅĐ˛Đ°ĐŊĐ¸ŅŅ‚Đ° Đ˛Ņ€ĐĩĐŧĐĩĐŊĐŊĐž ĐŊĐĩ ŅĐ° ĐŊаĐģĐ¸Ņ‡ĐŊи (API timed out)</string>
<string name="revanced_ryd_failure_connection_status_code">НĐĩŅ…Đ°Ņ€ĐĩŅĐ˛Đ°ĐŊĐ¸ŅŅ‚Đ° ĐŊĐĩ ŅĐ° ĐŊаĐģĐ¸Ņ‡ĐŊи (status %d)</string>
@@ -893,6 +910,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_sb_stats_username_changed">ĐŸĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģҁĐēĐžŅ‚Đž иĐŧĐĩ Đĩ ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋŅ€ĐžĐŧĐĩĐŊĐĩĐŊĐž</string>
<string name="revanced_sb_stats_reputation">Đ ĐĩĐŋŅƒŅ‚Đ°Ņ†Đ¸ŅŅ‚Đ° ви Đĩ &lt;b&gt;%.2f&lt;/b&gt;</string>
<string name="revanced_sb_stats_submissions">ĐĄŅŠĐˇĐ´Đ°Đ´ĐžŅ…Ņ‚Đĩ &lt;b&gt;%s&lt;/b&gt; Ņ‡Đ°ŅŅ‚Đ¸</string>
<string name="revanced_sb_stats_submissions_sum">ДоĐēĐžŅĐŊĐĩŅ‚Đĩ Ņ‚ŅƒĐē, Са да Đ˛Đ¸Đ´Đ¸Ņ‚Đĩ Đ˛Đ°ŅˆĐ¸Ņ‚Đĩ ҁĐĩĐŗĐŧĐĩĐŊŅ‚Đ¸</string>
<string name="revanced_sb_stats_saved_zero">SponsorBlock ĐēĐģĐ°ŅĐ°Ņ†Đ¸Ņ</string>
<string name="revanced_sb_stats_saved">ĐĄĐŋĐ°ŅĐ¸Ņ…Ņ‚Đĩ Ņ…ĐžŅ€Đ°Ņ‚Đ° ĐžŅ‚ &lt;b&gt;%s&lt;/b&gt; Ņ‡Đ°ŅŅ‚Đ¸</string>
<string name="revanced_sb_stats_saved_sum_zero">ДоĐēĐžŅĐŊĐĩŅ‚Đĩ Са да Đ˛Đ¸Đ´Đ¸Ņ‚Đĩ ŅŅ‚Đ°Ņ‚Đ¸ŅŅ‚Đ¸ĐēĐ°Ņ‚Đ° и Ņ‚ĐĩСи Đ´ĐžĐŋŅ€Đ¸ĐŊĐĩҁĐģи ĐŊаК-ĐŧĐŊĐžĐŗĐž</string>
@@ -1115,25 +1133,23 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_slide_to_seek_summary_on">ĐĄĐģаКд Са ĐŋŅ€ĐĩĐ˛ŅŠŅ€Ņ‚Đ°ĐŊĐĩ Đĩ аĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊ</string>
<string name="revanced_slide_to_seek_summary_off">ĐĄĐģаКд Са ĐŋŅ€ĐĩĐ˛ŅŠŅ€Ņ‚Đ°ĐŊĐĩ Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊ</string>
</patch>
<patch id="misc.fix.playback.SpoofClientPatch">
<string name="revanced_spoof_client_screen_title">ПодĐŧŅĐŊа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ° (ĐŊа ĐēĐģиĐĩĐŊŅ‚Đ°)</string>
<string name="revanced_spoof_client_screen_summary">ПодĐŧŅĐŊа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ°, Са да ĐŋŅ€ĐĩĐ´ĐžŅ‚Đ˛Ņ€Đ°Ņ‚Đ¸Ņ‚Đĩ ĐŋŅ€ĐžĐąĐģĐĩĐŧи ҁ Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž ĐŊа видĐĩĐž</string>
<string name="revanced_spoof_client_title">ПодĐŧŅĐŊа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ° (ĐŊа ĐēĐģиĐĩĐŊŅ‚Đ°)</string>
<string name="revanced_spoof_client_summary_on">ВĐĩŅ€ŅĐ¸ŅŅ‚Đ° Đĩ ĐŋОдĐŧĐĩĐŊĐĩĐŊа</string>
<string name="revanced_spoof_client_summary_off">КĐģиĐĩĐŊŅ‚ŅŠŅ‚ ĐŊĐĩ Đĩ ĐŋОдĐŋŅ€Đ°Đ˛ĐĩĐŊ.\n\nĐ’ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž ĐŊа видĐĩĐž ĐŧĐžĐļĐĩ да ĐŊĐĩ Ņ€Đ°ĐąĐžŅ‚Đ¸</string>
<string name="revanced_spoof_client_user_dialog_message">ДĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž ĐŊа Ņ‚Đ°ĐˇĐ¸ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа ҉Đĩ дОвĐĩĐ´Đĩ Đ´Đž ĐŋŅ€ĐžĐąĐģĐĩĐŧи ҁ Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž ĐŊа видĐĩĐž.</string>
<string name="revanced_spoof_client_type_title">ĐĐ°Ņ‡Đ¸ĐŊ Са ĐŋОдĐŧŅĐŊа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ°</string>
<string name="revanced_spoof_client_ios_force_avc_title">ĐŸŅ€Đ¸ĐŊŅƒĐ´Đ¸Ņ‚ĐĩĐģĐŊĐž AVC (H.264) Са iOS</string>
<string name="revanced_spoof_client_ios_force_avc_summary_on">ĐŸŅ€Đ¸ ĐŋОдĐŧŅĐŊа ĐŊа ĐēĐģиĐĩĐŊŅ‚Đ° ĐŊа iOS ҁĐĩ иСĐŋĐžĐģСва видĐĩĐžĐēОдĐĩĐē AVC</string>
<string name="revanced_spoof_client_ios_force_avc_summary_off">ĐŸŅ€Đ¸ ĐŋОдĐŧŅĐŊа ĐŊа ĐēĐģиĐĩĐŊŅ‚Đ° ĐŊа iOS ҁĐĩ иСĐŋĐžĐģСва видĐĩĐžĐēОдĐĩĐē AVC, VP9 иĐģи AV1</string>
<string name="revanced_spoof_client_ios_force_avc_user_dialog_message">АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž ĐŊа Ņ‚ĐžĐ˛Đ° ĐŧĐžĐļĐĩ да ĐŋĐžĐ´ĐžĐąŅ€Đ¸ ĐļĐ¸Đ˛ĐžŅ‚Đ° ĐŊа ĐąĐ°Ņ‚ĐĩŅ€Đ¸ŅŅ‚Đ° и да ĐēĐžŅ€Đ¸ĐŗĐ¸Ņ€Đ° ĐŋŅ€ĐĩĐēŅŠŅĐ˛Đ°ĐŊĐ¸ŅŅ‚Đ° ĐŋŅ€Đ¸ Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ.\n\nAVC иĐŧа ĐŧаĐēŅĐ¸ĐŧаĐģĐŊа Ņ€Đ°ĐˇĐ´ĐĩĐģĐ¸Ņ‚ĐĩĐģĐŊа ҁĐŋĐžŅĐžĐąĐŊĐžŅŅ‚ ĐžŅ‚ 1080p и Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž ĐŊа видĐĩĐž ҉Đĩ иСĐŋĐžĐģСва ĐŋОвĐĩ҇Đĩ иĐŊŅ‚ĐĩŅ€ĐŊĐĩŅ‚ даĐŊĐŊи ĐžŅ‚ VP9 иĐģи AV1.</string>
<string name="revanced_spoof_client_about_android_ios_title">CŅ‚Ņ€Đ°ĐŊĐ¸Ņ‡ĐŊи ĐĩŅ„ĐĩĐēŅ‚Đ¸ ĐžŅ‚ ĐŋОдĐŧŅĐŊĐ°Ņ‚Đ° ĐŊа iOS</string>
<string name="revanced_spoof_client_about_android_ios_summary">â€ĸ HDR ҁĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа ŅĐ°ĐŧĐž ҁ ĐēОдĐĩĐē AV1\nâ€ĸ Đ˜ŅŅ‚ĐžŅ€Đ¸ŅŅ‚Đ° ĐŊа ĐŗĐģĐĩдаĐŊĐĩ ĐŊĐĩ Ņ€Đ°ĐąĐžŅ‚Đ¸ ҁ аĐēĐ°ŅƒĐŊŅ‚ ĐŊа ĐŧĐ°Ņ€Đēа</string>
<string name="revanced_spoof_client_about_android_vr_title">ĐĄŅ‚Ņ€Đ°ĐŊĐ¸Ņ‡ĐŊи ĐĩŅ„ĐĩĐēŅ‚Đ¸ ĐžŅ‚ ĐŋОдĐŋŅ€Đ°Đ˛ŅĐŊĐĩ ĐŊа Android VR</string>
<string name="revanced_spoof_client_about_android_vr_summary">â€ĸ ĐŅĐŧа HDR видĐĩĐž\nâ€ĸ ДĐĩ҂ҁĐēĐ¸Ņ‚Đĩ видĐĩĐžĐēĐģиĐŋОвĐĩ ĐŊĐĩ ҁĐĩ Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļĐ´Đ°Ņ‚\nâ€ĸ ĐŸĐžŅŅ‚Đ°Đ˛ĐĩĐŊĐ¸Ņ‚Đĩ ĐŊа ĐŋĐ°ŅƒĐˇĐ° видĐĩĐžĐēĐģиĐŋОвĐĩ ĐŧĐžĐŗĐ°Ņ‚ ĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐģĐŊĐž да ҁĐĩ Đ˛ŅŠĐˇĐžĐąĐŊĐžĐ˛ŅŅ‚\nâ€ĸ ĐĐ¸ŅĐēĐžĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐĩĐŊи ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ¸ ĐŊа ĐģĐĩĐŊŅ‚Đ°Ņ‚Đ° Са Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа Shorts\nâ€ĸ Đ‘ŅƒŅ‚ĐžĐŊ Са Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đĩ Са Đ¸ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ Đĩ ҁĐēŅ€Đ¸Ņ‚Đž\nâ€ĸ ĐšĐ°Ņ€Ņ‚Đ¸Ņ‚Đĩ ĐŊа ĐēŅ€Đ°ĐšĐŊĐ¸Ņ ĐĩĐēŅ€Đ°ĐŊ ŅĐ° ҁĐēŅ€Đ¸Ņ‚Đ¸</string>
</patch>
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
<patch id="misc.fix.playback.SpoofSignaturePatch">
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
<string name="revanced_spoof_video_streams_screen_title">ПодĐŋŅ€Đ°Đ˛ŅĐŊĐĩ ĐŊа видĐĩĐž ĐŋĐžŅ‚ĐžŅ†Đ¸</string>
<string name="revanced_spoof_video_streams_screen_summary">ПодĐŋŅ€Đ°Đ˛ĐĩŅ‚Đĩ ĐēĐģиĐĩĐŊ҂ҁĐēĐ¸Ņ‚Đĩ видĐĩĐž ĐŋĐžŅ‚ĐžŅ†Đ¸, Са да ĐŋŅ€ĐĩĐ´ĐžŅ‚Đ˛Ņ€Đ°Ņ‚Đ¸Ņ‚Đĩ ĐŋŅ€ĐžĐąĐģĐĩĐŧи ҁ Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž</string>
<string name="revanced_spoof_video_streams_title">ПодĐŋŅ€Đ°Đ˛ŅĐŊĐĩ ĐŊа видĐĩĐž ĐŋĐžŅ‚ĐžŅ†Đ¸</string>
<string name="revanced_spoof_video_streams_summary_on">ВидĐĩĐž ĐŋĐžŅ‚ĐžŅ†Đ¸Ņ‚Đĩ ŅĐ° ĐŋОдĐŋŅ€Đ°Đ˛ĐĩĐŊи</string>
<string name="revanced_spoof_video_streams_summary_off">ВидĐĩĐž ĐŋĐžŅ‚ĐžŅ†Đ¸Ņ‚Đĩ ĐŊĐĩ ŅĐ° ĐŋОдĐŋŅ€Đ°Đ˛ĐĩĐŊи\n\nĐ’ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž ĐŊа видĐĩĐž ĐŧĐžĐļĐĩ да ĐŊĐĩ Ņ€Đ°ĐąĐžŅ‚Đ¸</string>
<string name="revanced_spoof_video_streams_user_dialog_message">ДĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž ĐŊа Ņ‚Đ°ĐˇĐ¸ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа ҉Đĩ дОвĐĩĐ´Đĩ Đ´Đž ĐŋŅ€ĐžĐąĐģĐĩĐŧи ҁ Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž ĐŊа видĐĩĐž.</string>
<string name="revanced_spoof_video_streams_client_title">КĐģиĐĩĐŊŅ‚ ĐŋĐž ĐŋĐžĐ´Ņ€Đ°ĐˇĐąĐ¸Ņ€Đ°ĐŊĐĩ</string>
<string name="revanced_spoof_video_streams_ios_force_avc_title">ĐŸŅ€Đ¸ĐŊŅƒĐ´Đ¸Ņ‚ĐĩĐģĐŊĐž AVC (H.264)</string>
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">ВидĐĩĐžĐēОдĐĩĐēа Đĩ AVC (H.264)</string>
<string name="revanced_spoof_video_streams_ios_force_avc_summary_off">ВидĐĩĐžĐēОдĐĩĐēа Đĩ VP9 иĐģи AV1</string>
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Đ’Đ°ŅˆĐĩŅ‚Đž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž ĐŊŅĐŧа Ņ…Đ°Ņ€Đ´ŅƒĐĩŅ€ĐŊĐž VP9 Đ´ĐĩĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ и Ņ‚Đ°ĐˇĐ¸ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа виĐŊĐ°ĐŗĐ¸ Đĩ аĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊа, ĐēĐžĐŗĐ°Ņ‚Đž Đĩ аĐēŅ‚Đ¸Đ˛ĐŊĐž ĐŋОдĐŋŅ€Đ°Đ˛ŅĐŊĐĩ ĐŊа ĐēĐģиĐĩĐŊŅ‚Đ°</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž ĐŊа Ņ‚ĐžĐ˛Đ° ĐŧĐžĐļĐĩ да ĐŋĐžĐ´ĐžĐąŅ€Đ¸ ĐļĐ¸Đ˛ĐžŅ‚Đ° ĐŊа ĐąĐ°Ņ‚ĐĩŅ€Đ¸ŅŅ‚Đ° и да ĐēĐžŅ€Đ¸ĐŗĐ¸Ņ€Đ° ĐŋŅ€ĐĩĐēŅŠŅĐ˛Đ°ĐŊĐ¸ŅŅ‚Đ° ĐŋŅ€Đ¸ Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ.\n\nAVC иĐŧа ĐŧаĐēŅĐ¸ĐŧаĐģĐŊа Ņ€Đ°ĐˇĐ´ĐĩĐģĐ¸Ņ‚ĐĩĐģĐŊа ҁĐŋĐžŅĐžĐąĐŊĐžŅŅ‚ ĐžŅ‚ 1080p и Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž ĐŊа видĐĩĐž ҉Đĩ иСĐŋĐžĐģСва ĐŋОвĐĩ҇Đĩ иĐŊŅ‚ĐĩŅ€ĐŊĐĩŅ‚ даĐŊĐŊи ĐžŅ‚ VP9 иĐģи AV1.</string>
<string name="revanced_spoof_video_streams_about_ios_title">CŅ‚Ņ€Đ°ĐŊĐ¸Ņ‡ĐŊи ĐĩŅ„ĐĩĐēŅ‚Đ¸ ĐžŅ‚ ĐŋОдĐŧŅĐŊĐ°Ņ‚Đ° ĐŊа iOS</string>
<string name="revanced_spoof_video_streams_about_ios_summary">â€ĸ ФиĐģĐŧи иĐģи ĐŋĐģĐ°Ņ‚ĐĩĐŊи видĐĩĐžĐēĐģиĐŋОвĐĩ ĐŧĐžĐļĐĩ да ĐŊĐĩ ҁĐĩ Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļĐ´Đ°Ņ‚\nâ€ĸ ĐŸĐžŅ‚ĐžŅ†Đ¸Ņ‚Đĩ ĐŊа ĐļивО СаĐŋĐžŅ‡Đ˛Đ°Ņ‚ ĐžŅ‚ĐŊĐ°Ņ‡Đ°ĐģĐž\nâ€ĸ ВидĐĩĐžĐēĐģиĐŋОвĐĩŅ‚Đĩ ĐŧĐžĐļĐĩ да ĐˇĐ°Đ˛ŅŠŅ€ŅˆĐ˛Đ°Ņ‚ 1 ҁĐĩĐē҃ĐŊда ĐŋĐž-Ņ€Đ°ĐŊĐž\nâ€ĸ ĐŅĐŧа Đ°ŅƒĐ´Đ¸ĐžĐēОдĐĩĐē Opus</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">ĐĄŅ‚Ņ€Đ°ĐŊĐ¸Ņ‡ĐŊи ĐĩŅ„ĐĩĐēŅ‚Đ¸ ĐžŅ‚ ĐŋОдĐŋŅ€Đ°Đ˛ŅĐŊĐĩ ĐŊа Android VR</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">â€ĸ ЛиĐŋŅĐ˛Đ° ĐŧĐĩĐŊŅŽŅ‚Đž Са Đ¸ĐˇĐąĐžŅ€ Đ°ŅƒĐ´Đ¸Đž\nâ€ĸ НĐĩ Đĩ ĐŊаĐģĐ¸Ņ‡ĐŊа ŅŅ‚Đ°ĐąĐ¸ĐģĐŊа ŅĐ¸Đģа ĐŊа ĐˇĐ˛ŅƒĐēа</string>
</patch>
<!-- This patch is no longer used and these strings will soon be deleted. -->
<patch id="video.hdrbrightness.HDRBrightnessPatch">

View File

@@ -32,6 +32,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
-->
<resources>
<app id="shared">
<patch id="misc.checks.BaseCheckEnvironmentPatch">
</patch>
<patch id="misc.settings.BaseSettingsResourcePatch">
<string name="revanced_settings_confirm_user_dialog_title">āφāĻĒāύāĻŋ āĻ•āĻŋ āĻāĻ—āĻŋā§Ÿā§‡ āϝ⧇āϤ⧇ āχāĻšā§āϛ⧁āĻ•?</string>
<string name="revanced_settings_reset">āφāĻŦāĻžāϰ āϏ⧇āϟ āĻ•āϰ⧁āύ</string>
@@ -42,6 +44,13 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_import_reset">ReVanced āϏ⧇āϟāĻŋāĻ‚ āĻĄāĻŋāĻĢāĻ˛ā§āϟ āϏ⧇āϟ āĻ•āϰāĻž āĻšā§Ÿā§‡āϛ⧇</string>
<string name="revanced_settings_import_success">%d āϏ⧇āϟāĻŋāĻ‚ āφāĻŽāĻĻāĻžāύāĻŋ āĻšā§Ÿā§‡āϛ⧇</string>
<string name="revanced_settings_import_failure_parse">āφāĻŽāĻĻāĻžāύāĻŋ āĻ•āϰāĻž āϝāĻžā§ŸāύāĻŋ: %s</string>
<string name="revanced_pref_import_export_title">āφāĻŽāĻĻāĻžāύāĻŋ āĻāĻŦāĻ‚ āϰāĻĒā§āϤāĻžāύāĻŋ</string>
<string name="revanced_pref_import_export_summary">ReVanced āϏ⧇āϟāĻŋāĻ‚ āφāĻŽāĻĻāĻžāύāĻŋ āĻŦāĻž āϰāĻĒā§āϤāĻžāύāĻŋ āĻ•āϰ⧁āύ</string>
<!-- Settings about dialog. -->
<string name="revanced_settings_about_links_body">āφāĻĒāύāĻŋ ReVanced āĻĒā§āϝāĻžāϚ āϏāĻ‚āĻ¸ā§āĻ•āϰāĻŖ &lt;i&gt;%s&lt;/i&gt; āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϛ⧇āύ</string>
<string name="revanced_settings_about_links_dev_header">āĻĻā§āϰāĻˇā§āϟāĻŦā§āϝ</string>
<string name="revanced_settings_about_links_dev_body">āĻāχ āϏāĻ‚āĻ¸ā§āĻ•āϰāĻŖ āĻāĻ•āϟāĻŋ āĻĒā§āϰāĻžāĻ•-āĻĒā§āϰāĻ•āĻžāĻļāύāĻž āĻāĻŦāĻ‚ āĻāϤ⧇ āφāĻĒāύāĻŋ āĻ…āύāĻžāĻ•āĻžāĻ™ā§āĻ–āĻŋāϤ āϏāĻŽāĻ¸ā§āϝāĻžāϰ āϏāĻŽā§āĻŽā§āĻ–āĻŋāύ āĻšāϤ⧇ āĻĒāĻžāϰ⧇āύ</string>
<string name="revanced_settings_about_links_header">āĻ…āĻĢāĻŋāĻļā§āϝāĻžāϞ āϞāĻŋāĻ‚āĻ•āϏāĻŽā§‚āĻš</string>
</patch>
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@@ -54,14 +63,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch>
</app>
<app id="youtube">
<patch id="misc.settings.SettingsResourcePatch">
<string name="revanced_settings_about_links_body">āφāĻĒāύāĻŋ ReVanced āĻĒā§āϝāĻžāϚ āϏāĻ‚āĻ¸ā§āĻ•āϰāĻŖ &lt;i&gt;%s&lt;/i&gt; āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϛ⧇āύ</string>
<string name="revanced_settings_about_links_dev_header">āĻĻā§āϰāĻˇā§āϟāĻŦā§āϝ</string>
<string name="revanced_settings_about_links_dev_body">āĻāχ āϏāĻ‚āĻ¸ā§āĻ•āϰāĻŖ āĻāĻ•āϟāĻŋ āĻĒā§āϰāĻžāĻ•-āĻĒā§āϰāĻ•āĻžāĻļāύāĻž āĻāĻŦāĻ‚ āĻāϤ⧇ āφāĻĒāύāĻŋ āĻ…āύāĻžāĻ•āĻžāĻ™ā§āĻ–āĻŋāϤ āϏāĻŽāĻ¸ā§āϝāĻžāϰ āϏāĻŽā§āĻŽā§āĻ–āĻŋāύ āĻšāϤ⧇ āĻĒāĻžāϰ⧇āύ</string>
<string name="revanced_settings_about_links_header">āĻ…āĻĢāĻŋāĻļā§āϝāĻžāϞ āϞāĻŋāĻ‚āĻ•āϏāĻŽā§‚āĻš</string>
<string name="revanced_pref_import_export_title">āφāĻŽāĻĻāĻžāύāĻŋ āĻāĻŦāĻ‚ āϰāĻĒā§āϤāĻžāύāĻŋ</string>
<string name="revanced_pref_import_export_summary">ReVanced āϏ⧇āϟāĻŋāĻ‚ āφāĻŽāĻĻāĻžāύāĻŋ āĻŦāĻž āϰāĻĒā§āϤāĻžāύāĻŋ āĻ•āϰ⧁āύ</string>
</patch>
<patch id="misc.settings.SettingsPatch">
<string name="revanced_settings_screen_00_about_title">āϏāĻŽā§āĻĒāĻ°ā§āĻ•āĻŋāϤ</string>
<string name="revanced_settings_screen_01_ads_title">āĻŦāĻŋāĻœā§āĻžāĻžāĻĒāύ</string>
@@ -232,13 +233,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_hide_keyword_content_phrases_title">āϞ⧁āĻ•āĻžāύ⧋āϰ āϜāĻ¨ā§āϝ āϕ⧀āĻ“ā§ŸāĻžāĻ°ā§āĻĄ</string>
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
<string name="revanced_hide_keyword_content_phrases_summary">āϞ⧁āĻ•āĻžāύ⧋āϰ āϜāĻ¨ā§āϝ āϕ⧀āĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻāĻŦāĻ‚ āĻŦāĻžāĻ•ā§āϝāĻžāĻ‚āĻļ, āύāϤ⧁āύ āϞāĻžāχāύ⧇ āĻĒ⧃āĻĨāĻ• āĻ•āϰāĻž\n\nāĻļāĻŦā§āĻĻ⧇āϰ āĻŽāĻžāĻā§‡ āĻŦ⧜ āĻšāĻžāϤ⧇āϰ āĻ…āĻ•ā§āώāϰ āĻĨāĻžāĻ•āϞ⧇ āϤāĻž āĻ…āĻŦāĻļā§āϝāχ āϏāĻ āĻŋāĻ• āφāĻŦāϰāϪ⧇ āϞāĻŋāĻ–āϤ⧇ āĻšāĻŦ⧇ (āωāĻĻāĻžāĻšāϰāĻŖ: iPhone, TikTok, LeBlanc)</string>
<string name="revanced_hide_keyword_content_about_title">āϕ⧀āĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĢāĻŋāĻ˛ā§āϟāĻžāϰāĻŋāĻ‚ āϏāĻŽā§āĻĒāĻ°ā§āϕ⧇</string>
<string name="revanced_hide_keyword_content_about_summary">āĻĒā§āϰāϧāĻžāύ āĻĒāĻžāϤāĻž/āϏāĻžāĻŦāĻ¸ā§āĻ•ā§āϰāĻŋāĻĒāĻļāύ/āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻĢāϞāĻžāĻĢāϞ āϗ⧁āϞ⧋ āϕ⧀āĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻŦāĻžāĻ•ā§āϝāĻžāĻ‚āĻļ⧇āϰ āϏāĻžāĻĨ⧇ āĻŽāĻŋāϞāĻŋā§Ÿā§‡ āϞ⧁āĻ•āĻžāύ⧋āϰ āϜāĻ¨ā§āϝ āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ āĻ•āϰāĻž āĻšā§Ÿā§‡āϛ⧇\n\nāϏ⧀āĻŽāĻžāĻŦāĻĻā§āϧāϤāĻž\nâ€ĸ āĻ•āĻŋāϛ⧁ Shorts āύāĻžāĻ“ āϞ⧁āĻ•āĻžāύ⧋ āĻšāϤ⧇ āĻĒāĻžāϰ⧇\nâ€ĸ āĻ•āĻŋāϛ⧁ āχāωāφāχ āωāĻĒāĻžāĻĻāĻžāύ āύāĻžāĻ“ āϞ⧁āĻ•āĻžāύ⧋ āĻšāϤ⧇ āĻĒāĻžāϰ⧇\nâ€ĸ āϕ⧋āύ āϕ⧀āĻ“ā§ŸāĻžāĻ°ā§āĻĄ āϏāĻžāĻ°ā§āϚ āĻ•āϰāϞ⧇ āϕ⧋āύ āĻĢāϞāĻžāĻĢāϞ āύāĻžāĻ“ āĻĻ⧇āĻ–āĻžāϤ⧇ āĻĒāĻžāϰ⧇</string>
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_hide_keyword_toast_invalid_common">āĻ…āĻŦ⧈āϧ āϕ⧀āĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ \'%s\' āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻž āϝāĻžāĻŦ⧇ āύāĻž</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_hide_keyword_toast_invalid_length">āĻ¤ā§āϰ⧁āϟāĻŋāĻĒā§‚āĻ°ā§āĻŖ āϕ⧀āĻ“ā§ŸāĻžāĻ°ā§āĻĄāĨ¤ \'%1$s\' āϟāĻŋ %2$d āĻ…āĻ•ā§āώāϰ āĻĨ⧇āϕ⧇ āĻ•āĻŽ</string>
</patch>
<patch id="ad.general.HideAdsResourcePatch">
<string name="revanced_hide_general_ads_title">āϏāĻžāϧāĻžāϰāĻŖ āĻŦāĻŋāĻœā§āĻžāĻžāĻĒāύ āϞ⧁āĻ•āĻžāύ</string>
@@ -604,7 +601,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_player_overlay_opacity_invalid_toast">āĻĒā§āĻ˛ā§‡ā§ŸāĻžāϰ āĻ“āĻ­āĻžāϰāϞ⧇ āĻ…āĻ¸ā§āĻŦāĻšā§āĻ›āϤāĻž āĻ…āĻŦāĻļā§āϝāχ ā§Ļ-ā§§ā§Ļā§Ļ āĻāϰ āĻŽāĻ§ā§āϝ⧇ āĻšāϤ⧇ āĻšāĻŦ⧇</string>
</patch>
<patch id="layout.returnyoutubedislike.ReturnYouTubeDislikeResourcePatch">
<string name="revanced_ryd_video_likes_hidden_by_video_owner">āϞ⧁āĻ•āĻŋā§Ÿā§‡ āĻ°ā§Ÿā§‡āϛ⧇</string>
<!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. -->
<string name="revanced_ryd_failure_connection_timeout">āĻ…āĻĒāĻ›āĻ¨ā§āĻĻ āϏāĻžāĻŽā§ŸāĻŋāĻ•āĻ­āĻžāĻŦ⧇ āωāĻĒāϞāĻ­ā§āϝ āύ⧟ (API āϏāĻŽā§Ÿ āĻļ⧇āώ āĻšā§Ÿā§‡āϛ⧇)</string>
<string name="revanced_ryd_failure_connection_status_code">āĻ…āĻĒāĻ›āĻ¨ā§āĻĻ āωāĻĒāϞāĻ­ā§āϝ āύ⧟ (āĻ…āĻŦāĻ¸ā§āĻĨāĻž %d)</string>
@@ -1035,19 +1031,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_slide_to_seek_summary_on">āĻ­āĻŋāĻĄāĻŋāĻ“āϰ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻ…āĻ‚āĻļ⧇ āϝ⧇āϤ⧇ āϟāĻžāύ⧁āύ āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰāĻž āĻšā§Ÿā§‡āϛ⧇</string>
<string name="revanced_slide_to_seek_summary_off">āĻ­āĻŋāĻĄāĻŋāĻ“āϰ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻ…āĻ‚āĻļ⧇ āϝ⧇āϤ⧇ āϟāĻžāύ⧁āύ āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰāĻž āĻšā§ŸāύāĻŋ</string>
</patch>
<patch id="misc.fix.playback.SpoofClientPatch">
<string name="revanced_spoof_client_screen_title">āĻ•ā§āϞāĻžā§Ÿā§‡āĻ¨ā§āϟ āĻ¸ā§āĻĒ⧁āĻĢ āĻ•āϰ⧁āύ</string>
<string name="revanced_spoof_client_screen_summary">āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āϏāĻŽāĻ¸ā§āϝāĻž āĻĒā§āϰāϤāĻŋāϰ⧋āϧ āĻ•āϰāϤ⧇ āĻ•ā§āϞāĻžā§Ÿā§‡āĻ¨ā§āϟ āĻ¸ā§āĻĒ⧁āĻĢ āĻ•āϰ⧁āύ</string>
<string name="revanced_spoof_client_title">āĻ•ā§āϞāĻžā§Ÿā§‡āĻ¨ā§āϟ āĻ¸ā§āĻĒ⧁āĻĢ āĻ•āϰ⧁āύ</string>
<string name="revanced_spoof_client_summary_on">āĻ•ā§āϞāĻžā§Ÿā§‡āĻ¨ā§āϟ āĻ¸ā§āĻĒ⧁āĻĢ āĻ•āϰāĻž āĻšā§Ÿā§‡āϛ⧇</string>
<string name="revanced_spoof_client_summary_off">āĻ•ā§āϞāĻžā§Ÿā§‡āĻ¨ā§āϟ āĻ¸ā§āĻĒ⧁āĻĢ āĻ•āϰāĻž āĻšā§ŸāύāĻŋ\n\nāĻ­āĻŋāĻĄāĻŋāĻ“ āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āĻ āĻŋāĻ•āĻŽāϤ⧋ āĻ•āĻžāϜ āύāĻžāĻ“ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇</string>
<string name="revanced_spoof_client_user_dialog_message">āĻāχ āϏ⧇āϟāĻŋāĻ‚āϟāĻŋ āĻŦāĻ¨ā§āϧ āĻ•āϰāĻžāϰ āĻĢāϞ⧇ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤</string>
<string name="revanced_spoof_client_about_android_vr_summary">â€ĸ āϕ⧋āύāĻ“ HDR āĻ­āĻŋāĻĄāĻŋāĻ“ āύ⧇āχ\nâ€ĸ āĻŦāĻžāĻšā§āϚāĻžāĻĻ⧇āϰ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āĻšāϝāĻŧ āύāĻž\nâ€ĸ āĻŦāĻŋāϰāϤāĻŋ āĻĻ⧇āĻ“āϝāĻŧāĻž āĻ­āĻŋāĻĄāĻŋāĻ“āϗ⧁āϞāĻŋ āĻāϞ⧋āĻŽā§‡āϞ⧋āĻ­āĻžāĻŦ⧇ āφāĻŦāĻžāϰ āĻļ⧁āϰ⧁ āĻšāϤ⧇ āĻĒāĻžāϰ⧇\nâ€ĸ āύāĻŋāĻŽā§āύāĻŽāĻžāύ⧇āϰ āĻļāĻ°ā§āϟāϏ āϏāĻŋāĻ•āĻŦāĻžāϰ āĻĨāĻžāĻŽā§āĻŦāύ⧇āϞ\nâ€ĸ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ…ā§āϝāĻžāĻ•āĻļāύ āĻŦā§‹āϤāĻžāĻŽ āϏāĻŦāϏāĻŽāϝāĻŧ āϞ⧁āĻ•āĻžāύ⧋ āĻĨāĻžāϕ⧇\nâ€ĸ āĻļ⧇āώ āĻ¸ā§āĻ•ā§āϰāĻŋāύ āĻ•āĻžāĻ°ā§āĻĄ āϏāĻŦāϏāĻŽāϝāĻŧ āϞ⧁āĻ•āĻžāύ⧋ āĻĨāĻžāϕ⧇</string>
<string name="revanced_spoof_client_storyboard_timeout">āĻ•ā§āϞāĻžā§Ÿā§‡āĻ¨ā§āϟ āĻ¸ā§āĻĒ⧁āĻĢ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϏāĻžāĻŽā§ŸāĻŋāĻ•āĻ­āĻžāĻŦ⧇ āωāĻĒāϞāĻ­ā§āϝ āύ⧟ (API āϏāĻŽā§Ÿ āĻļ⧇āώ āĻšā§Ÿā§‡āϛ⧇)</string>
<string name="revanced_spoof_client_storyboard_io_exception">āĻ¸ā§āĻĒ⧁āĻĢ āĻ•ā§āϞāĻžā§Ÿā§‡āĻ¨ā§āϟ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϏāĻžāĻŽā§ŸāĻŋāĻ•āĻ­āĻžāĻŦ⧇ āωāĻĒāϞāĻ­ā§āϝ āύ⧟: %s</string>
</patch>
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
<patch id="misc.fix.playback.SpoofSignaturePatch">
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
<string name="revanced_spoof_video_streams_user_dialog_message">āĻāχ āϏ⧇āϟāĻŋāĻ‚āϟāĻŋ āĻŦāĻ¨ā§āϧ āĻ•āϰāĻžāϰ āĻĢāϞ⧇ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤</string>
</patch>
<!-- This patch is no longer used and these strings will soon be deleted. -->
<patch id="video.hdrbrightness.HDRBrightnessPatch">

View File

@@ -32,15 +32,16 @@ This is because Crowdin requires temporarily flattening this file and removing t
-->
<resources>
<app id="shared">
<patch id="misc.checks.BaseCheckEnvironmentPatch">
</patch>
<patch id="misc.settings.BaseSettingsResourcePatch">
<!-- Settings about dialog. -->
</patch>
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
</patch>
</app>
<app id="youtube">
<patch id="misc.settings.SettingsResourcePatch">
</patch>
<patch id="misc.settings.SettingsPatch">
</patch>
<patch id="misc.debugging.DebuggingPatch">
@@ -57,7 +58,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<!-- 'Component path builder strings' is the technical name for identifying the Litho UI layout items to hide. This is an advanced feature and most users will never use this. -->
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
</patch>
<patch id="ad.general.HideAdsResourcePatch">
@@ -232,10 +233,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch>
<patch id="interaction.seekbar.EnableSlideToSeekPatch">
</patch>
<patch id="misc.fix.playback.SpoofClientPatch">
</patch>
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
<patch id="misc.fix.playback.SpoofSignaturePatch">
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
</patch>
<!-- This patch is no longer used and these strings will soon be deleted. -->
<patch id="video.hdrbrightness.HDRBrightnessPatch">

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