Compare commits

...

42 Commits

Author SHA1 Message Date
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
192 changed files with 3840 additions and 3807 deletions

View File

@@ -1,3 +1,161 @@
## [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.15.1-dev.2

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

@@ -38,6 +38,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"),

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ı</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>
@@ -678,7 +693,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 +907,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 +1130,11 @@ 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">ранични ефекти от подмяната на 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_user_dialog_message">Деактивирането на тази настройка ще доведе до проблеми с възпроизвеждането на видео.</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">ранични ефекти от подмяната на iOS</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Странични ефекти от подправяне на Android VR</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">

View File

@@ -32,16 +32,17 @@ 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_reset">Restablir</string>
<!-- 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">
<string name="revanced_settings_screen_00_about_title">Quant a</string>
</patch>
@@ -59,7 +60,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">
@@ -241,10 +242,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