mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-08 02:13:55 +01:00
Compare commits
85 Commits
v4.13.0-de
...
v4.16.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b49012130 | ||
|
|
4c7b018878 | ||
|
|
5ddd957313 | ||
|
|
bb0dcbe83d | ||
|
|
163736fb26 | ||
|
|
0c6db43bde | ||
|
|
317e9a80eb | ||
|
|
464e6a3673 | ||
|
|
2e9142eda4 | ||
|
|
b4c6d0a7d2 | ||
|
|
c0ee85e12a | ||
|
|
2d326072e2 | ||
|
|
586770aa3a | ||
|
|
9f314c2425 | ||
|
|
c8b3456738 | ||
|
|
e8cb6ee028 | ||
|
|
3e796eb7c2 | ||
|
|
303d2de81d | ||
|
|
0ab7344295 | ||
|
|
ff8fe46685 | ||
|
|
a104eeaf68 | ||
|
|
18b09168cc | ||
|
|
f7209f0a53 | ||
|
|
1fb3fc4857 | ||
|
|
e03c14cc01 | ||
|
|
bed29d00dc | ||
|
|
d36982e245 | ||
|
|
13031f0534 | ||
|
|
b5b6ef5d6f | ||
|
|
5b1e07d861 | ||
|
|
e3220cc10a | ||
|
|
02db9378ea | ||
|
|
f83e314dff | ||
|
|
d5e383b78a | ||
|
|
395e18d830 | ||
|
|
887684e7c7 | ||
|
|
2f7d751f9f | ||
|
|
4886a95713 | ||
|
|
58719239cf | ||
|
|
fcb68cc65e | ||
|
|
16217f012e | ||
|
|
d6f20ee67d | ||
|
|
bccd62e593 | ||
|
|
1322403698 | ||
|
|
a64270514f | ||
|
|
f5de555adf | ||
|
|
4c2ec2870c | ||
|
|
a73e2458e9 | ||
|
|
96e6f43ca0 | ||
|
|
f667d5a238 | ||
|
|
ff2c4564a0 | ||
|
|
b568207e49 | ||
|
|
70470a9162 | ||
|
|
9922f47a49 | ||
|
|
ed532eb528 | ||
|
|
74f3f82927 | ||
|
|
6544cd5fc6 | ||
|
|
4e323aa206 | ||
|
|
c1cee281ff | ||
|
|
0779f9fc5e | ||
|
|
0ee5cf98ab | ||
|
|
6bb848b991 | ||
|
|
188b66ffe7 | ||
|
|
a276425d83 | ||
|
|
e556c3f692 | ||
|
|
cb30248eab | ||
|
|
c5ce742ab4 | ||
|
|
bdd2f7cb0f | ||
|
|
b7600f448d | ||
|
|
5c4bf7676d | ||
|
|
fcd2f9b4c4 | ||
|
|
aa3487aa92 | ||
|
|
ada642f4a7 | ||
|
|
eac758588a | ||
|
|
5d047eae77 | ||
|
|
ed92bf1be6 | ||
|
|
c6318e890f | ||
|
|
7f3b1c54da | ||
|
|
3d0d94b6c8 | ||
|
|
b84e6afebd | ||
|
|
3eab130276 | ||
|
|
95f8e9b3a9 | ||
|
|
d7be94a193 | ||
|
|
e4e20bec6c | ||
|
|
b8bd63a34c |
284
CHANGELOG.md
284
CHANGELOG.md
@@ -1,3 +1,287 @@
|
||||
# [4.16.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.16.0-dev.1...v4.16.0-dev.2) (2024-09-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Add patch option to hide Shorts from app launcher widget Beta ([#3707](https://github.com/ReVanced/revanced-patches/issues/3707)) ([838f183](https://github.com/ReVanced/revanced-patches/commit/838f1834a5df547ce2c3217b874c0594b6878a67))
|
||||
|
||||
# [4.16.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.15.1-dev.2...v4.16.0-dev.1) (2024-09-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Add patch option to hide Shorts app shortcut (long press app icon) ([#3699](https://github.com/ReVanced/revanced-patches/issues/3699)) ([0d4e1f5](https://github.com/ReVanced/revanced-patches/commit/0d4e1f5d03cf3dcc06fd41165e26a1ce901b976b))
|
||||
|
||||
## [4.15.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.15.1-dev.1...v4.15.1-dev.2) (2024-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube:** Show video chapter titles without clipping when overlay buttons are enabled ([#3674](https://github.com/ReVanced/revanced-patches/issues/3674)) ([4b88c31](https://github.com/ReVanced/revanced-patches/commit/4b88c316ed90c56e83e2aee266561833b36fc37d))
|
||||
|
||||
## [4.15.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.15.0...v4.15.1-dev.1) (2024-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Twitter - Open links with app chooser:** Fix incorrect version in compatibility list ([#3683](https://github.com/ReVanced/revanced-patches/issues/3683)) ([adafe85](https://github.com/ReVanced/revanced-patches/commit/adafe85d77f6a0031a5523b9b7da69475959d78d))
|
||||
|
||||
# [4.15.0](https://github.com/ReVanced/revanced-patches/compare/v4.14.1...v4.15.0) (2024-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **TikTok - Playback speed:** Prevent crash by fixing invalid patch ([82d53cb](https://github.com/ReVanced/revanced-patches/commit/82d53cbc3bbfa585ba4337fdfaec9f0f19c802e6))
|
||||
* **TikTok - Settings:** Prevent crash by fixing invalid patch ([8074032](https://github.com/ReVanced/revanced-patches/commit/8074032fad3eff1c03296a882d2e2820da99b592))
|
||||
* **Twitter - Open links with app chooser:** Constrain patch to last working version `10.48.0-release` ([b9955d5](https://github.com/ReVanced/revanced-patches/commit/b9955d5ff6e456593b01f0f25d80ff660d02082a))
|
||||
* **YouTube - Spoof video streams:** Change default client type to Android VR ([74c8637](https://github.com/ReVanced/revanced-patches/commit/74c8637943347078955f51325bc6af92a35d4463))
|
||||
* **YouTube - Spoof video streams:** Change default client type to Android VR ([#3672](https://github.com/ReVanced/revanced-patches/issues/3672)) ([a3306f6](https://github.com/ReVanced/revanced-patches/commit/a3306f6717a09b734354f00363a96abad0ae14e7))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **TikTok:** Bump patches to support the latest version 36.5.4 ([e5dcb72](https://github.com/ReVanced/revanced-patches/commit/e5dcb72597092fb32003f11fdf6f861ede4e7ff3))
|
||||
|
||||
# [4.15.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.2-dev.2...v4.15.0-dev.1) (2024-09-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **TikTok:** Bump patches to support the latest version 36.5.4 ([e5dcb72](https://github.com/ReVanced/revanced-patches/commit/e5dcb72597092fb32003f11fdf6f861ede4e7ff3))
|
||||
|
||||
## [4.14.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.14.2-dev.1...v4.14.2-dev.2) (2024-09-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof video streams:** Change default client type to Android VR ([74c8637](https://github.com/ReVanced/revanced-patches/commit/74c8637943347078955f51325bc6af92a35d4463))
|
||||
* **YouTube - Spoof video streams:** Change default client type to Android VR ([#3672](https://github.com/ReVanced/revanced-patches/issues/3672)) ([a3306f6](https://github.com/ReVanced/revanced-patches/commit/a3306f6717a09b734354f00363a96abad0ae14e7))
|
||||
|
||||
## [4.14.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.1...v4.14.2-dev.1) (2024-09-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **TikTok - Playback speed:** Prevent crash by fixing invalid patch ([82d53cb](https://github.com/ReVanced/revanced-patches/commit/82d53cbc3bbfa585ba4337fdfaec9f0f19c802e6))
|
||||
* **TikTok - Settings:** Prevent crash by fixing invalid patch ([8074032](https://github.com/ReVanced/revanced-patches/commit/8074032fad3eff1c03296a882d2e2820da99b592))
|
||||
* **Twitter - Open links with app chooser:** Constrain patch to last working version `10.48.0-release` ([b9955d5](https://github.com/ReVanced/revanced-patches/commit/b9955d5ff6e456593b01f0f25d80ff660d02082a))
|
||||
|
||||
## [4.14.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.0...v4.14.1) (2024-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Check environment:** Only use fields available since Android 8 ([#3655](https://github.com/ReVanced/revanced-patches/issues/3655)) ([4413533](https://github.com/ReVanced/revanced-patches/commit/441353306572340131030e1c4fee1ab6acb63cd9))
|
||||
|
||||
## [4.14.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.0...v4.14.1-dev.1) (2024-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Check environment:** Only use fields available since Android 8 ([#3655](https://github.com/ReVanced/revanced-patches/issues/3655)) ([4413533](https://github.com/ReVanced/revanced-patches/commit/441353306572340131030e1c4fee1ab6acb63cd9))
|
||||
|
||||
# [4.14.0](https://github.com/ReVanced/revanced-patches/compare/v4.13.3...v4.14.0) (2024-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Pixiv - Hide ads:** Fix for latest version ([#3616](https://github.com/ReVanced/revanced-patches/issues/3616)) ([98956e8](https://github.com/ReVanced/revanced-patches/commit/98956e8f1a41347bb435720bbf984969469a7110))
|
||||
* **Soundcloud - Hide ads:** Support latest version ([#3628](https://github.com/ReVanced/revanced-patches/issues/3628)) ([66e7e33](https://github.com/ReVanced/revanced-patches/commit/66e7e33efce9b702fdfcc2b9803e9da8491c1f08))
|
||||
* **SwissID:** Rename `Remove Google Play Integrity Integrity check` to `Remove Google Play Integrity check` ([#3558](https://github.com/ReVanced/revanced-patches/issues/3558)) ([0f5a771](https://github.com/ReVanced/revanced-patches/commit/0f5a771a5cff5684b4a8fd317f4938fe2cf3cbbe))
|
||||
* **YouTube - ReturnYouTubeDislike:** Show estimated like count for videos with hidden likes ([#3601](https://github.com/ReVanced/revanced-patches/issues/3601)) ([005be82](https://github.com/ReVanced/revanced-patches/commit/005be82d71b2a42387b1b57035930b20f4663794))
|
||||
* **YouTube - SponsorBlock:** Add summary text to 'view my segments' button ([df80b9f](https://github.com/ReVanced/revanced-patches/commit/df80b9f92f0d981b9a40b7756d74f8ccc3dcb1e9))
|
||||
* **YouTube - SponsorBlock:** Handle if the user enters an invalid number into any SB settings ([37b3dd1](https://github.com/ReVanced/revanced-patches/commit/37b3dd1e789f8bb16fa1b9dd582e39c89dbe730c))
|
||||
* **YouTube:** Fix issues related to playback by replace streaming data ([#3582](https://github.com/ReVanced/revanced-patches/issues/3582)) ([dfa94d7](https://github.com/ReVanced/revanced-patches/commit/dfa94d70f65150d6ef24ea6378b8e6a317055186))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add `Change data directory location` patch ([#3602](https://github.com/ReVanced/revanced-patches/issues/3602)) ([5998029](https://github.com/ReVanced/revanced-patches/commit/59980292809cc0626bf49a160eeb05a1523c4eda))
|
||||
* Add `Check environment` patch ([#3610](https://github.com/ReVanced/revanced-patches/issues/3610)) ([fbcbdaf](https://github.com/ReVanced/revanced-patches/commit/fbcbdafa4938a35b5fdec46aae7b250a84b9c139))
|
||||
* **Duolingo:** Add `Disable ads` and `Enable debug menu` patch ([#3422](https://github.com/ReVanced/revanced-patches/issues/3422)) ([d0a8599](https://github.com/ReVanced/revanced-patches/commit/d0a8599f76ce653e5d7c98069ad3c58b9ab9c5eb))
|
||||
* **Sync for Reddit:** Add `Fix /user/ endpoint` patch ([46d11f3](https://github.com/ReVanced/revanced-patches/commit/46d11f3530fcdae9ed08b7e93aac235638a92dff))
|
||||
* **Sync for Reddit:** Rename patch to `Use /user/ endpoint` ([98ead49](https://github.com/ReVanced/revanced-patches/commit/98ead493380932cb105530f4ba992673fd364d82))
|
||||
* **YouTube - Hide Shorts components:** Hide 'Use this sound' button ([#3647](https://github.com/ReVanced/revanced-patches/issues/3647)) ([33fc090](https://github.com/ReVanced/revanced-patches/commit/33fc09061431d4aa457d743c09a0de31ec566df1))
|
||||
* **YouTube - Keyword filter:** Add syntax to match whole keywords and not substrings ([#3592](https://github.com/ReVanced/revanced-patches/issues/3592)) ([f5fb351](https://github.com/ReVanced/revanced-patches/commit/f5fb3512cfafe214ba6a6d25ba0825ae1884a0ff))
|
||||
* **YouTube - Spoof client:** Allow forcing AVC codec with iOS ([#3570](https://github.com/ReVanced/revanced-patches/issues/3570)) ([1a49d1f](https://github.com/ReVanced/revanced-patches/commit/1a49d1f3c2a343d05d0abc07c143add486246fd0))
|
||||
* **YouTube Music:** Make working patches compatible with latest versions ([#3556](https://github.com/ReVanced/revanced-patches/issues/3556)) ([12f6f19](https://github.com/ReVanced/revanced-patches/commit/12f6f1966ad04631451940f7b64d785c3ef481a0))
|
||||
* **YouTube:** Add donation link to settings about screen ([#3626](https://github.com/ReVanced/revanced-patches/issues/3626)) ([0684ab5](https://github.com/ReVanced/revanced-patches/commit/0684ab5f183631de5720352049cfd293daa58eb0))
|
||||
|
||||
# [4.14.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.14...v4.14.0-dev.15) (2024-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube:** Fix issues related to playback by replace streaming data ([#3582](https://github.com/ReVanced/revanced-patches/issues/3582)) ([dfa94d7](https://github.com/ReVanced/revanced-patches/commit/dfa94d70f65150d6ef24ea6378b8e6a317055186))
|
||||
|
||||
# [4.14.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.13...v4.14.0-dev.14) (2024-09-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube Music:** Make working patches compatible with latest versions ([#3556](https://github.com/ReVanced/revanced-patches/issues/3556)) ([12f6f19](https://github.com/ReVanced/revanced-patches/commit/12f6f1966ad04631451940f7b64d785c3ef481a0))
|
||||
|
||||
# [4.14.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.12...v4.14.0-dev.13) (2024-09-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Hide 'Use this sound' button ([#3647](https://github.com/ReVanced/revanced-patches/issues/3647)) ([33fc090](https://github.com/ReVanced/revanced-patches/commit/33fc09061431d4aa457d743c09a0de31ec566df1))
|
||||
|
||||
# [4.14.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.11...v4.14.0-dev.12) (2024-09-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Soundcloud - Hide ads:** Support latest version ([#3628](https://github.com/ReVanced/revanced-patches/issues/3628)) ([66e7e33](https://github.com/ReVanced/revanced-patches/commit/66e7e33efce9b702fdfcc2b9803e9da8491c1f08))
|
||||
|
||||
# [4.14.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.10...v4.14.0-dev.11) (2024-09-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Sync for Reddit:** Rename patch to `Use /user/ endpoint` ([98ead49](https://github.com/ReVanced/revanced-patches/commit/98ead493380932cb105530f4ba992673fd364d82))
|
||||
|
||||
# [4.14.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.9...v4.14.0-dev.10) (2024-09-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Sync for Reddit:** Add `Fix /user/ endpoint` patch ([46d11f3](https://github.com/ReVanced/revanced-patches/commit/46d11f3530fcdae9ed08b7e93aac235638a92dff))
|
||||
|
||||
# [4.14.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.8...v4.14.0-dev.9) (2024-09-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube:** Add donation link to settings about screen ([#3626](https://github.com/ReVanced/revanced-patches/issues/3626)) ([0684ab5](https://github.com/ReVanced/revanced-patches/commit/0684ab5f183631de5720352049cfd293daa58eb0))
|
||||
|
||||
# [4.14.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.7...v4.14.0-dev.8) (2024-09-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - SponsorBlock:** Add summary text to 'view my segments' button ([df80b9f](https://github.com/ReVanced/revanced-patches/commit/df80b9f92f0d981b9a40b7756d74f8ccc3dcb1e9))
|
||||
|
||||
# [4.14.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.6...v4.14.0-dev.7) (2024-09-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add `Check environment` patch ([#3610](https://github.com/ReVanced/revanced-patches/issues/3610)) ([fbcbdaf](https://github.com/ReVanced/revanced-patches/commit/fbcbdafa4938a35b5fdec46aae7b250a84b9c139))
|
||||
|
||||
# [4.14.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.5...v4.14.0-dev.6) (2024-09-06)
|
||||
|
||||
|
||||
### 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))
|
||||
|
||||
# [4.14.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.4...v4.14.0-dev.5) (2024-09-06)
|
||||
|
||||
|
||||
### 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))
|
||||
|
||||
# [4.14.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.3...v4.14.0-dev.4) (2024-09-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **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))
|
||||
|
||||
# [4.14.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.2...v4.14.0-dev.3) (2024-08-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **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))
|
||||
|
||||
# [4.14.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.1...v4.14.0-dev.2) (2024-08-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **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))
|
||||
|
||||
# [4.14.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.13.4-dev.2...v4.14.0-dev.1) (2024-08-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **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))
|
||||
|
||||
## [4.13.4-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.13.4-dev.1...v4.13.4-dev.2) (2024-08-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - SponsorBlock:** Handle if the user enters an invalid number into any SB settings ([37b3dd1](https://github.com/ReVanced/revanced-patches/commit/37b3dd1e789f8bb16fa1b9dd582e39c89dbe730c))
|
||||
|
||||
## [4.13.4-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.13.3...v4.13.4-dev.1) (2024-08-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **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))
|
||||
|
||||
## [4.13.3](https://github.com/ReVanced/revanced-patches/compare/v4.13.2...v4.13.3) (2024-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube:** Remove translated string that breaks patching ([a48c2db](https://github.com/ReVanced/revanced-patches/commit/a48c2db53d84767c8fd5d569f9ce1c46c2bfd9a1))
|
||||
|
||||
## [4.13.3-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.13.2...v4.13.3-dev.1) (2024-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube:** Remove translated string that breaks patching ([a48c2db](https://github.com/ReVanced/revanced-patches/commit/a48c2db53d84767c8fd5d569f9ce1c46c2bfd9a1))
|
||||
|
||||
## [4.13.2](https://github.com/ReVanced/revanced-patches/compare/v4.13.1...v4.13.2) (2024-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - GmsCore Support:** Fix patch exception by using correct patch offset ([#3543](https://github.com/ReVanced/revanced-patches/issues/3543)) ([b2b8454](https://github.com/ReVanced/revanced-patches/commit/b2b8454aa992bcb217fb03eb4de5532e0a9bd354))
|
||||
|
||||
## [4.13.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.13.1...v4.13.2-dev.1) (2024-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - GmsCore Support:** Fix patch exception by using correct patch offset ([#3543](https://github.com/ReVanced/revanced-patches/issues/3543)) ([b2b8454](https://github.com/ReVanced/revanced-patches/commit/b2b8454aa992bcb217fb03eb4de5532e0a9bd354))
|
||||
|
||||
## [4.13.1](https://github.com/ReVanced/revanced-patches/compare/v4.13.0...v4.13.1) (2024-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Check watch history domain name resolution:** Add compatibility field ([6c598f0](https://github.com/ReVanced/revanced-patches/commit/6c598f084ed90ee1318e4c66d8c1751c797b8e3b))
|
||||
|
||||
## [4.13.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.13.0...v4.13.1-dev.1) (2024-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Check watch history domain name resolution:** Add compatibility field ([6c598f0](https://github.com/ReVanced/revanced-patches/commit/6c598f084ed90ee1318e4c66d8c1751c797b8e3b))
|
||||
|
||||
# [4.13.0](https://github.com/ReVanced/revanced-patches/compare/v4.12.0...v4.13.0) (2024-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - GmsCore support:** Fix notifications not working by using the correct permissions ([19ddae2](https://github.com/ReVanced/revanced-patches/commit/19ddae2d15e513e18eb1556c468cd94bd197685b))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Google Photos:** Add `Spoof features` patch ([#3459](https://github.com/ReVanced/revanced-patches/issues/3459)) ([7c218cd](https://github.com/ReVanced/revanced-patches/commit/7c218cd168aa72eb99bcb47d12dfa45616e8ad88))
|
||||
* **SCB Easy:** Remove broken `Remove debugging detection` patch ([#3518](https://github.com/ReVanced/revanced-patches/issues/3518)) ([f4e23cb](https://github.com/ReVanced/revanced-patches/commit/f4e23cbb8a24638318d8cee20a1991c51855d9d2))
|
||||
* **YouTube:** Add `Check watch history domain name resolution` patch ([#3537](https://github.com/ReVanced/revanced-patches/issues/3537)) ([2af1425](https://github.com/ReVanced/revanced-patches/commit/2af142525cda07a131335faadd4b3889979fd077))
|
||||
|
||||
# [4.13.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.13.0-dev.1...v4.13.0-dev.2) (2024-08-15)
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,14 @@ public final class app/revanced/patches/all/connectivity/wifi/spoof/SpoofWifiPat
|
||||
public fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lkotlin/Triple;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/all/directory/ChangeDataDirectoryLocationPatch : app/revanced/patches/all/misc/transformation/BaseTransformInstructionsPatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/all/directory/ChangeDataDirectoryLocationPatch;
|
||||
public 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/Integer;
|
||||
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;
|
||||
public fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
|
||||
public synthetic fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/Object;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/all/interaction/gestures/PredictiveBackGesturePatch : app/revanced/patcher/patch/ResourcePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/all/interaction/gestures/PredictiveBackGesturePatch;
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
@@ -243,6 +251,18 @@ public final class app/revanced/patches/cieid/restrictions/root/BypassRootChecks
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/duolingo/ad/DisableAdsPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/duolingo/ad/DisableAdsPatch;
|
||||
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/duolingo/debug/EnableDebugMenuPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/duolingo/debug/EnableDebugMenuPatch;
|
||||
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/facebook/ads/story/HideStoryAdsPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/facebook/ads/story/HideStoryAdsPatch;
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
@@ -808,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;
|
||||
}
|
||||
@@ -842,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
|
||||
@@ -1850,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
|
||||
@@ -1880,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;
|
||||
@@ -1958,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 {
|
||||
@@ -2132,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
|
||||
@@ -2159,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
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ dependencies {
|
||||
implementation(libs.guava)
|
||||
// Used in JsonGenerator.
|
||||
implementation(libs.gson)
|
||||
// Android API stubs defined here.
|
||||
compileOnly(project(":stub"))
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
kotlin.code.style = official
|
||||
version = 4.13.0-dev.2
|
||||
version = 4.16.0-dev.2
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,3 +5,5 @@ buildCache {
|
||||
isEnabled = "CI" !in System.getenv()
|
||||
}
|
||||
}
|
||||
|
||||
include(":stub")
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package app.revanced.patches.all.directory
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch
|
||||
import app.revanced.util.getReference
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
@Patch(
|
||||
name = "Change data directory location",
|
||||
description = "Changes the data directory in the application from " +
|
||||
"the app internal storage directory to /sdcard/android/data accessible by root-less devices." +
|
||||
"Using this patch can cause unexpected issues with some apps.",
|
||||
use = false,
|
||||
)
|
||||
@Suppress("unused")
|
||||
object ChangeDataDirectoryLocationPatch : BaseTransformInstructionsPatch<Int>() {
|
||||
override fun filterMap(
|
||||
classDef: ClassDef,
|
||||
method: Method,
|
||||
instruction: Instruction,
|
||||
instructionIndex: Int,
|
||||
): Int? {
|
||||
val reference = instruction.getReference<MethodReference>() ?: return null
|
||||
|
||||
if (!MethodUtil.methodSignaturesMatch(reference, MethodCall.GetDir.reference)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return instructionIndex
|
||||
}
|
||||
|
||||
override fun transform(
|
||||
mutableMethod: MutableMethod,
|
||||
entry: Int,
|
||||
) = transformMethodCall(entry, mutableMethod)
|
||||
|
||||
private fun transformMethodCall(
|
||||
instructionIndex: Int,
|
||||
mutableMethod: MutableMethod,
|
||||
) {
|
||||
val getDirInstruction = mutableMethod.getInstruction<Instruction35c>(instructionIndex)
|
||||
val contextRegister = getDirInstruction.registerC
|
||||
val dataRegister = getDirInstruction.registerD
|
||||
|
||||
mutableMethod.replaceInstruction(
|
||||
instructionIndex,
|
||||
"invoke-virtual { v$contextRegister, v$dataRegister }, " +
|
||||
"Landroid/content/Context;->getExternalFilesDir(Ljava/lang/String;)Ljava/io/File;",
|
||||
)
|
||||
}
|
||||
|
||||
private enum class MethodCall(
|
||||
val reference: MethodReference,
|
||||
) {
|
||||
GetDir(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/content/Context;",
|
||||
"getDir",
|
||||
listOf("Ljava/lang/String;", "I"),
|
||||
"Ljava/io/File;",
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -83,8 +83,9 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
|
||||
"eu-rES" to "eu",
|
||||
"fa-rIR" to "fa",
|
||||
"fi-rFI" to "fi",
|
||||
"tl-rPH" to "tl",
|
||||
"fil-rPH" to "tl",
|
||||
"fr-rFR" to "fr",
|
||||
"ga-rIE" to "ga",
|
||||
"gl-rES" to "gl",
|
||||
"gu-rIN" to "gu",
|
||||
"hi-rIN" to "hi",
|
||||
@@ -139,7 +140,6 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
|
||||
"uz-rUZ" to "uz",
|
||||
"vi-rVN" to "vi",
|
||||
"zh-rCN" to "zh-rCN",
|
||||
"zh-rHK" to "zh-rHK",
|
||||
"zh-rTW" to "zh-rTW",
|
||||
"zu-rZA" to "zu",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package app.revanced.patches.duolingo.ad
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.duolingo.ad.fingerprints.InitializeMonetizationDebugSettingsFingerprint
|
||||
import app.revanced.util.resultOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
|
||||
@Patch(
|
||||
name = "Disable ads",
|
||||
compatiblePackages = [CompatiblePackage("com.duolingo")]
|
||||
)
|
||||
@Suppress("unused")
|
||||
object DisableAdsPatch : BytecodePatch(
|
||||
setOf(InitializeMonetizationDebugSettingsFingerprint)
|
||||
) {
|
||||
override fun execute(context: BytecodeContext) {
|
||||
// Couple approaches to remove ads exist:
|
||||
//
|
||||
// MonetizationDebugSettings has a boolean value for "disableAds".
|
||||
// OnboardingState has a getter to check if the user has any "adFreeSessions".
|
||||
// SharedPreferences has a debug boolean value with key "disable_ads", which maps to "DebugCategory.DISABLE_ADS".
|
||||
//
|
||||
// MonetizationDebugSettings seems to be the most general setting to work fine.
|
||||
InitializeMonetizationDebugSettingsFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val insertIndex = it.scanResult.patternScanResult!!.startIndex
|
||||
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"const/4 v$register, 0x1"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.duolingo.ad.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 InitializeMonetizationDebugSettingsFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
parameters = listOf(
|
||||
"Z", // disableAds
|
||||
"Z", // useDebugBilling
|
||||
"Z", // showManageSubscriptions
|
||||
"Z", // alwaysShowSuperAds
|
||||
"Lcom/duolingo/debug/FamilyQuestOverride;",
|
||||
),
|
||||
opcodes = listOf(
|
||||
Opcode.IPUT_BOOLEAN
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
package app.revanced.patches.duolingo.debug
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.duolingo.debug.fingerprints.InitializeBuildConfigProviderFingerprint
|
||||
import app.revanced.util.resultOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
|
||||
@Patch(
|
||||
name = "Enable debug menu",
|
||||
compatiblePackages = [CompatiblePackage("com.duolingo", ["5.158.4"])],
|
||||
use = false
|
||||
)
|
||||
@Suppress("unused")
|
||||
object EnableDebugMenuPatch : BytecodePatch(
|
||||
setOf(InitializeBuildConfigProviderFingerprint)
|
||||
) {
|
||||
override fun execute(context: BytecodeContext) {
|
||||
InitializeBuildConfigProviderFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val insertIndex = it.scanResult.patternScanResult!!.startIndex
|
||||
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"const/4 v$register, 0x1"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package app.revanced.patches.duolingo.debug.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
|
||||
|
||||
/**
|
||||
* The `BuildConfigProvider` class has two booleans:
|
||||
*
|
||||
* - `isChina`: (usually) compares "play" with "china"...except for builds in China
|
||||
* - `isDebug`: compares "release" with "debug" <-- we want to force this to `true`
|
||||
*/
|
||||
internal object InitializeBuildConfigProviderFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
strings = listOf(
|
||||
"debug",
|
||||
"release",
|
||||
"china",
|
||||
),
|
||||
opcodes = listOf(
|
||||
Opcode.IPUT_BOOLEAN
|
||||
)
|
||||
)
|
||||
@@ -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")
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
package app.revanced.patches.pixiv.ads
|
||||
|
||||
import app.revanced.util.exception
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.pixiv.ads.fingerprints.IsNotPremiumFingerprint
|
||||
import app.revanced.patches.pixiv.ads.fingerprints.ShouldShowAdsFingerprint
|
||||
import app.revanced.util.exception
|
||||
|
||||
@Patch(
|
||||
name = "Hide ads",
|
||||
compatiblePackages = [CompatiblePackage("jp.pxv.android")]
|
||||
compatiblePackages = [CompatiblePackage("jp.pxv.android")],
|
||||
)
|
||||
@Suppress("unused")
|
||||
object HideAdsPatch : BytecodePatch(setOf(IsNotPremiumFingerprint)) {
|
||||
// Always return false in the "isNotPremium" method which normally returns !this.accountManager.isPremium.
|
||||
// However, this is not the method that controls the user's premium status.
|
||||
// Instead, this method is used to determine whether ads should be shown.
|
||||
object HideAdsPatch : BytecodePatch(setOf(ShouldShowAdsFingerprint)) {
|
||||
override fun execute(context: BytecodeContext) =
|
||||
IsNotPremiumFingerprint.result?.mutableClass?.virtualMethods?.first()?.addInstructions(
|
||||
ShouldShowAdsFingerprint.result?.mutableMethod?.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x0
|
||||
return v0
|
||||
"""
|
||||
) ?: throw IsNotPremiumFingerprint.exception
|
||||
}
|
||||
""",
|
||||
) ?: throw ShouldShowAdsFingerprint.exception
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package app.revanced.patches.pixiv.ads.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
|
||||
internal object IsNotPremiumFingerprint : MethodFingerprint(
|
||||
"V",
|
||||
AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
listOf("L"),
|
||||
strings = listOf("pixivAccountManager"),
|
||||
customFingerprint = custom@{ _, classDef ->
|
||||
// The "isNotPremium" method is the only method in the class.
|
||||
if (classDef.virtualMethods.count() != 1) return@custom false
|
||||
|
||||
classDef.virtualMethods.first().let { isNotPremiumMethod ->
|
||||
isNotPremiumMethod.parameterTypes.size == 0 && isNotPremiumMethod.returnType == "Z"
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
package app.revanced.patches.pixiv.ads.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
|
||||
internal object ShouldShowAdsFingerprint : MethodFingerprint(
|
||||
"Z",
|
||||
AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
customFingerprint = { methodDef, classDef ->
|
||||
classDef.type.endsWith("AdUtils;") && methodDef.name == "shouldShowAds"
|
||||
}
|
||||
)
|
||||
@@ -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}\"",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
|
||||
|
||||
internal object OAuthFriendRequestFingerprint : BaseUserEndpointFingerprint("OAuthFriendRequest.java")
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
|
||||
|
||||
internal object OAuthUnfriendRequestFingerprint : BaseUserEndpointFingerprint("OAuthUnfriendRequest.java")
|
||||
@@ -0,0 +1,3 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
|
||||
|
||||
internal object OAuthUserIdRequestFingerprint : BaseUserEndpointFingerprint("OAuthUserIdRequest.java")
|
||||
@@ -0,0 +1,3 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
|
||||
|
||||
internal object OAuthUserInfoRequestFingerprint : BaseUserEndpointFingerprint("OAuthUserInfoRequest.java")
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;" },
|
||||
)
|
||||
@@ -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;"
|
||||
},
|
||||
)
|
||||
@@ -18,7 +18,7 @@ import app.revanced.patches.shared.misc.gms.fingerprints.GooglePlayUtilityFinger
|
||||
import app.revanced.patches.shared.misc.gms.fingerprints.ServiceCheckFingerprint
|
||||
import app.revanced.util.exception
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.returnEarly
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
|
||||
@@ -115,15 +115,16 @@ abstract class BaseGmsCoreSupportPatch(
|
||||
|
||||
// Verify GmsCore is installed and whitelisted for power optimizations and background usage.
|
||||
mainActivityOnCreateFingerprint.result?.mutableMethod?.apply {
|
||||
val setContextIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>() ?: return@indexOfFirstInstructionOrThrow false
|
||||
// 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"
|
||||
}
|
||||
|
||||
// Add after setContext call, because this patch needs the context.
|
||||
addInstructions(
|
||||
setContextIndex + 1,
|
||||
if (setContextIndex < 0) 0 else setContextIndex + 1,
|
||||
"invoke-static/range { p0 .. p0 }, Lapp/revanced/integrations/shared/GmsCoreSupport;->" +
|
||||
"checkGmsCore(Landroid/app/Activity;)V",
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ import app.revanced.patches.swissid.integritycheck.fingerprints.CheckIntegrityFi
|
||||
import app.revanced.util.resultOrThrow
|
||||
|
||||
@Patch(
|
||||
name = "Remove Google Play Integrity Integrity check",
|
||||
name = "Remove Google Play Integrity check",
|
||||
description = "Removes the Google Play Integrity check. With this it's possible to use SwissID on custom ROMS." +
|
||||
"If the device is rooted, root permissions must be hidden from the app.",
|
||||
compatiblePackages = [CompatiblePackage("com.swisssign.swissid.mobile")],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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("/", ".")
|
||||
}
|
||||
|
||||
@@ -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;")
|
||||
},
|
||||
)
|
||||
@@ -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;")
|
||||
},
|
||||
)
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,8 +138,10 @@ object HideLayoutComponentsPatch : BytecodePatch(
|
||||
SwitchPreference("revanced_hide_keyword_content_search"),
|
||||
TextPreference("revanced_hide_keyword_content_phrases", inputType = InputType.TEXT_MULTI_LINE),
|
||||
NonInteractivePreference("revanced_hide_keyword_content_about"),
|
||||
),
|
||||
),
|
||||
NonInteractivePreference(key = "revanced_hide_keyword_content_about_whole_words",
|
||||
tag = "app.revanced.integrations.youtube.settings.preference.HtmlPreference")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
SettingsPatch.PreferenceScreen.GENERAL_LAYOUT.addPreferences(
|
||||
|
||||
@@ -6,6 +6,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
|
||||
import app.revanced.patches.youtube.layout.hide.shorts.fingerprints.*
|
||||
@@ -76,6 +77,20 @@ object HideShortsComponentsPatch : BytecodePatch(
|
||||
) {
|
||||
private const val FILTER_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/youtube/patches/components/ShortsFilter;"
|
||||
|
||||
internal val hideShortsAppShortcut by booleanPatchOption(
|
||||
key = "hideShortsAppShortcut",
|
||||
default = false,
|
||||
title = "Hide Shorts app shortcut",
|
||||
description = "Permanently hides the shortcut to open Shorts when long pressing the app icon in your launcher."
|
||||
)
|
||||
|
||||
internal val hideShortsWidget by booleanPatchOption(
|
||||
key = "hideShortsWidget",
|
||||
default = false,
|
||||
title = "Hide Shorts widget",
|
||||
description = "Permanently hides the launcher widget Shorts button."
|
||||
)
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
// region Hide the Shorts shelf.
|
||||
|
||||
|
||||
@@ -6,7 +6,11 @@ import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.layout.hide.shorts.HideShortsComponentsPatch.hideShortsAppShortcut
|
||||
import app.revanced.patches.youtube.layout.hide.shorts.HideShortsComponentsPatch.hideShortsWidget
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.util.findElementByAttributeValueOrThrow
|
||||
import org.w3c.dom.Element
|
||||
|
||||
@Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class, AddResourcesPatch::class])
|
||||
object HideShortsComponentsResourcePatch : ResourcePatch() {
|
||||
@@ -38,6 +42,7 @@ object HideShortsComponentsResourcePatch : ResourcePatch() {
|
||||
SwitchPreference("revanced_hide_shorts_subscribe_button"),
|
||||
SwitchPreference("revanced_hide_shorts_paused_overlay_buttons"),
|
||||
SwitchPreference("revanced_hide_shorts_save_sound_button"),
|
||||
SwitchPreference("revanced_hide_shorts_use_this_sound_button"),
|
||||
SwitchPreference("revanced_hide_shorts_shop_button"),
|
||||
SwitchPreference("revanced_hide_shorts_tagged_products"),
|
||||
SwitchPreference("revanced_hide_shorts_search_suggestions"),
|
||||
@@ -51,6 +56,28 @@ object HideShortsComponentsResourcePatch : ResourcePatch() {
|
||||
SwitchPreference("revanced_hide_shorts_navigation_bar"),
|
||||
)
|
||||
|
||||
if (hideShortsAppShortcut == true) {
|
||||
context.xmlEditor["res/xml/main_shortcuts.xml"].use { editor ->
|
||||
val shortsItem = editor.file.childNodes.findElementByAttributeValueOrThrow(
|
||||
"android:shortcutId",
|
||||
"shorts-shortcut"
|
||||
)
|
||||
|
||||
shortsItem.parentNode.removeChild(shortsItem)
|
||||
}
|
||||
}
|
||||
|
||||
if (hideShortsWidget == true) {
|
||||
context.xmlEditor["res/layout/appwidget_two_rows.xml"].use { editor ->
|
||||
val shortsItem = editor.file.childNodes.findElementByAttributeValueOrThrow(
|
||||
"android:id",
|
||||
"@id/button_shorts_container"
|
||||
)
|
||||
|
||||
shortsItem.parentNode.removeChild(shortsItem)
|
||||
}
|
||||
}
|
||||
|
||||
reelPlayerRightCellButtonHeight = ResourceMappingPatch[
|
||||
"dimen",
|
||||
"reel_player_right_cell_button_height",
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")),
|
||||
)
|
||||
@@ -3,6 +3,7 @@ package app.revanced.patches.youtube.misc.dns
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
|
||||
@@ -13,6 +14,37 @@ import app.revanced.util.resultOrThrow
|
||||
name = "Check watch history domain name resolution",
|
||||
description = "Checks if the device DNS server is preventing user watch history from being saved.",
|
||||
dependencies = [IntegrationsPatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.youtube",
|
||||
[
|
||||
"18.32.39",
|
||||
"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",
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@Suppress("unused")
|
||||
internal object CheckWatchHistoryDomainNameResolutionPatch : BytecodePatch(
|
||||
|
||||
@@ -1,390 +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.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"),
|
||||
SwitchPreference("revanced_spoof_client_use_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 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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;"
|
||||
)
|
||||
)
|
||||
@@ -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.
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
)
|
||||
@@ -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"),
|
||||
)
|
||||
@@ -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;"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;")
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -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,
|
||||
),
|
||||
)
|
||||
@@ -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
|
||||
},
|
||||
)
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
@@ -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)
|
||||
},
|
||||
)
|
||||
@@ -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)
|
||||
},
|
||||
)
|
||||
@@ -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)
|
||||
},
|
||||
)
|
||||
@@ -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" },
|
||||
)
|
||||
@@ -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 }
|
||||
)
|
||||
@@ -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 },
|
||||
)
|
||||
@@ -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"),
|
||||
)
|
||||
@@ -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#"),
|
||||
)
|
||||
@@ -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#"),
|
||||
)
|
||||
@@ -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("\\|"),
|
||||
)
|
||||
@@ -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.
|
||||
),
|
||||
)
|
||||
@@ -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 - "),
|
||||
)
|
||||
@@ -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",
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
)
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
}
|
||||
)
|
||||
@@ -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 }
|
||||
)
|
||||
@@ -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;"
|
||||
}
|
||||
)
|
||||
@@ -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 }
|
||||
)
|
||||
@@ -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 :
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user