Compare commits

...

47 Commits

Author SHA1 Message Date
semantic-release-bot
53b25ea7e9 chore: Release v5.8.0-dev.5 [skip ci]
# [5.8.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.4...v5.8.0-dev.5) (2024-12-27)

### Features

* **YouTube:** Add `Change form factor` patch ([#4217](https://github.com/ReVanced/revanced-patches/issues/4217)) ([189e1c9](189e1c90c4))
2024-12-27 06:51:57 +00:00
LisoUseInAIKyrios
189e1c90c4 feat(YouTube): Add Change form factor patch (#4217) 2024-12-27 10:48:14 +04:00
github-actions[bot]
f01603b3f3 chore: Sync translations (#4229) 2024-12-27 10:46:32 +04:00
semantic-release-bot
3db5651e5c chore: Release v5.8.0-dev.4 [skip ci]
# [5.8.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.3...v5.8.0-dev.4) (2024-12-27)

### Bug Fixes

* **GmsCore support:** Do not show battery optimization error on Android Automotive devices (Google built-in) ([#4218](https://github.com/ReVanced/revanced-patches/issues/4218)) ([fa4aa54](fa4aa54f0c))

### Features

* **Swipe controls:** Add option to enable/disable fullscreen swipe to next video ([#4222](https://github.com/ReVanced/revanced-patches/issues/4222)) ([29dbc9f](29dbc9ffbf))
* **YouTube:** Add `Exit fullscreen mode` patch ([#4223](https://github.com/ReVanced/revanced-patches/issues/4223)) ([f3c4d6f](f3c4d6fd64))
2024-12-27 06:28:58 +00:00
LisoUseInAIKyrios
f3c4d6fd64 feat(YouTube): Add Exit fullscreen mode patch (#4223) 2024-12-27 10:25:17 +04:00
LisoUseInAIKyrios
29dbc9ffbf feat(Swipe controls): Add option to enable/disable fullscreen swipe to next video (#4222) 2024-12-27 10:23:30 +04:00
LisoUseInAIKyrios
fa4aa54f0c fix(GmsCore support): Do not show battery optimization error on Android Automotive devices (Google built-in) (#4218) 2024-12-27 10:22:50 +04:00
github-actions[bot]
1d89ada07f chore: Sync translations (#4228) 2024-12-27 10:22:25 +04:00
LisoUseInAIKyrios
8c529abad5 ci: Fix Crowdin cron task? 2024-12-27 10:17:28 +04:00
LisoUseInAIKyrios
4ade7c7329 ci: Fix Crowdin cron task? 2024-12-26 14:08:46 +04:00
semantic-release-bot
f35247a872 chore: Release v5.8.0-dev.3 [skip ci]
# [5.8.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.2...v5.8.0-dev.3) (2024-12-26)

### Bug Fixes

* **YouTube - Force original audio:** If stream spoofing to Android then show a summary text why force audio is not available ([#4220](https://github.com/ReVanced/revanced-patches/issues/4220)) ([4de768f](4de768febf))
2024-12-26 10:02:26 +00:00
LisoUseInAIKyrios
4de768febf fix(YouTube - Force original audio): If stream spoofing to Android then show a summary text why force audio is not available (#4220) 2024-12-26 13:58:29 +04:00
github-actions[bot]
1a5c86db93 chore: Sync translations (#4216) 2024-12-26 13:58:13 +04:00
LisoUseInAIKyrios
dbba795468 chore(YouTube): Fix inconsistent strings 2024-12-25 04:59:12 +04:00
semantic-release-bot
0a9320551d chore: Release v5.8.0-dev.2 [skip ci]
# [5.8.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.1...v5.8.0-dev.2) (2024-12-24)

### Features

* **YouTube - Spoof app version:** Add 'Restore old navigation and toolbar icons' ([9fac161](9fac1614e7))
2024-12-24 22:11:57 +00:00
LisoUseInAIKyrios
9fac1614e7 feat(YouTube - Spoof app version): Add 'Restore old navigation and toolbar icons' 2024-12-25 02:09:10 +04:00
semantic-release-bot
2de3523c59 chore: Release v5.8.0-dev.1 [skip ci]
# [5.8.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.2...v5.8.0-dev.1) (2024-12-24)

### Features

* **YouTube - Hide Shorts components:** Add option to hide Shorts in watch history ([#4214](https://github.com/ReVanced/revanced-patches/issues/4214)) ([094a6aa](094a6aa6de))
2024-12-24 21:37:46 +00:00
github-actions[bot]
ad1e40b130 chore: Sync translations (#4215) 2024-12-25 01:34:11 +04:00
LisoUseInAIKyrios
094a6aa6de feat(YouTube - Hide Shorts components): Add option to hide Shorts in watch history (#4214) 2024-12-25 01:32:42 +04:00
LisoUseInAIKyrios
a14e03e4bb chore(YouTube - Spoof video streams): Update iOS side effects text 2024-12-24 18:40:55 +04:00
semantic-release-bot
6f40b6d30f chore: Release v5.7.2 [skip ci]
## [5.7.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.1...v5.7.2) (2024-12-24)

### Bug Fixes

* **YouTube - Hide layout components:** Don't hide Shorts channel bar when toggling for video player ([87e1c7f](87e1c7f4c8))
* **YouTube - Spoof video streams:** Add iOS TV client, restore iOS 'force AVC', show client type in stats for nerds ([#4202](https://github.com/ReVanced/revanced-patches/issues/4202)) ([ca21a69](ca21a69550))
2024-12-24 06:55:57 +00:00
LisoUseInAIKyrios
1711e1c39d chore: Merge branch dev to main (#4205) 2024-12-24 10:52:54 +04:00
github-actions[bot]
25372828d1 chore: Sync translations (#4210) 2024-12-24 10:52:18 +04:00
semantic-release-bot
f58245c6cd chore: Release v5.7.2-dev.2 [skip ci]
## [5.7.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.2-dev.1...v5.7.2-dev.2) (2024-12-23)

### Bug Fixes

* **YouTube - Hide layout components:** Don't hide Shorts channel bar when toggling for video player ([87e1c7f](87e1c7f4c8))
2024-12-23 23:00:32 +00:00
oSumAtrIX
87e1c7f4c8 fix(YouTube - Hide layout components): Don't hide Shorts channel bar when toggling for video player 2024-12-23 23:57:53 +01:00
semantic-release-bot
55d01c92d1 chore: Release v5.7.2-dev.1 [skip ci]
## [5.7.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.1...v5.7.2-dev.1) (2024-12-23)

### Bug Fixes

* **YouTube - Spoof video streams:** Add iOS TV client, restore iOS 'force AVC', show client type in stats for nerds ([#4202](https://github.com/ReVanced/revanced-patches/issues/4202)) ([ca21a69](ca21a69550))
2024-12-23 18:42:40 +00:00
LisoUseInAIKyrios
ca21a69550 fix(YouTube - Spoof video streams): Add iOS TV client, restore iOS 'force AVC', show client type in stats for nerds (#4202) 2024-12-23 22:39:27 +04:00
semantic-release-bot
634d0b4058 chore: Release v5.7.1 [skip ci]
## [5.7.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.0...v5.7.1) (2024-12-23)

### Bug Fixes

* **YouTube - SponsorBlock:** Show a toast and not a dialog if segment submitted successfully ([838edb4](838edb48e7))
* **YouTube - Spoof video streams:** Use 2 letter device language code ([e174113](e1741130af))
* **YouTube - Spoof video streams:** Use Android VR authentication if using default audio language ([#4191](https://github.com/ReVanced/revanced-patches/issues/4191)) ([99334d1](99334d1e53))
* **YouTube - Theme:** Use dark theme color for status and navigation bar ([4b81f70](4b81f7009b))
* **YouTube:** Do not reset playback speed to 1.0x after closing comment thread (Fixes stock YouTube bug) ([#4195](https://github.com/ReVanced/revanced-patches/issues/4195)) ([0ae756b](0ae756b0fc))
2024-12-23 01:16:40 +00:00
LisoUseInAIKyrios
47ea8d5ec8 chore: Merge branch dev to main (#4192) 2024-12-23 05:13:45 +04:00
github-actions[bot]
9509ed53f3 chore: Sync translations (#4198) 2024-12-23 04:53:27 +04:00
semantic-release-bot
39542ddf55 chore: Release v5.7.1-dev.5 [skip ci]
## [5.7.1-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.4...v5.7.1-dev.5) (2024-12-22)

### Bug Fixes

* **YouTube - Spoof video streams:** Use 2 letter device language code ([e174113](e1741130af))
2024-12-22 23:40:44 +00:00
LisoUseInAIKyrios
e1741130af fix(YouTube - Spoof video streams): Use 2 letter device language code 2024-12-23 03:37:21 +04:00
semantic-release-bot
e54eb3ce87 chore: Release v5.7.1-dev.4 [skip ci]
## [5.7.1-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.3...v5.7.1-dev.4) (2024-12-22)

### Bug Fixes

* **YouTube:** Do not reset playback speed to 1.0x after closing comment thread (Fixes stock YouTube bug) ([#4195](https://github.com/ReVanced/revanced-patches/issues/4195)) ([0ae756b](0ae756b0fc))
2024-12-22 17:45:50 +00:00
LisoUseInAIKyrios
0ae756b0fc fix(YouTube): Do not reset playback speed to 1.0x after closing comment thread (Fixes stock YouTube bug) (#4195) 2024-12-22 21:42:41 +04:00
github-actions[bot]
77a0ac5c9c chore: Sync translations (#4196) 2024-12-22 21:42:29 +04:00
semantic-release-bot
899121b9de chore: Release v5.7.1-dev.3 [skip ci]
## [5.7.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.2...v5.7.1-dev.3) (2024-12-22)

### Bug Fixes

* **YouTube - SponsorBlock:** Show a toast and not a dialog if segment submitted successfully ([838edb4](838edb48e7))
2024-12-22 17:34:19 +00:00
LisoUseInAIKyrios
838edb48e7 fix(YouTube - SponsorBlock): Show a toast and not a dialog if segment submitted successfully 2024-12-22 21:31:41 +04:00
semantic-release-bot
b2665c916a chore: Release v5.7.1-dev.2 [skip ci]
## [5.7.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.1...v5.7.1-dev.2) (2024-12-22)

### Bug Fixes

* **YouTube - Theme:** Use dark theme color for status and navigation bar ([4b81f70](4b81f7009b))
2024-12-22 11:29:57 +00:00
LisoUseInAIKyrios
4b81f7009b fix(YouTube - Theme): Use dark theme color for status and navigation bar 2024-12-22 15:27:02 +04:00
semantic-release-bot
1a4c39a2ee chore: Release v5.7.1-dev.1 [skip ci]
## [5.7.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.0...v5.7.1-dev.1) (2024-12-22)

### Bug Fixes

* **YouTube - Spoof video streams:** Use Android VR authentication if using default audio language ([#4191](https://github.com/ReVanced/revanced-patches/issues/4191)) ([99334d1](99334d1e53))
2024-12-22 10:26:04 +00:00
LisoUseInAIKyrios
99334d1e53 fix(YouTube - Spoof video streams): Use Android VR authentication if using default audio language (#4191) 2024-12-22 14:22:49 +04:00
semantic-release-bot
2850a6ed4e chore: Release v5.7.0 [skip ci]
# [5.7.0](https://github.com/ReVanced/revanced-patches/compare/v5.6.0...v5.7.0) (2024-12-22)

### Bug Fixes

* **YouTube - Force original audio:** Use correct availability for settings UI ([a0b63df](a0b63dfa23))
* **YouTube - Spoof video stream:** Remove UI client type setting.  Allow setting default audio language. ([#4184](https://github.com/ReVanced/revanced-patches/issues/4184)) ([aeedec7](aeedec7fed))
* **YouTube - Spoof video streams:** Remove iOS, add clients Android TV and Android Creator ([#4180](https://github.com/ReVanced/revanced-patches/issues/4180)) ([cdb6820](cdb68209d1))
* **YouTube:** Change fingerprints to support a wider range of target versions ([25d7cc6](25d7cc68ae))

### Features

* **YouTube:** Support version `19.47.53` ([#4182](https://github.com/ReVanced/revanced-patches/issues/4182)) ([b8635d0](b8635d0b88))
2024-12-22 07:53:45 +00:00
LisoUseInAIKyrios
f28eb5105b chore: Merge branch dev to main (#4188) 2024-12-22 11:50:32 +04:00
github-actions[bot]
69bed4d9fa chore: Sync translations (#4187) 2024-12-22 11:49:37 +04:00
oSumAtrIX
a5f1efac27 chore: Merge branch dev to main (#4183) 2024-12-22 08:48:36 +01:00
semantic-release-bot
b51be82cff chore: Release v5.7.0-dev.1 [skip ci]
# [5.7.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.6.1-dev.4...v5.7.0-dev.1) (2024-12-21)

### Features

* **YouTube:** Support version `19.47.53` ([#4182](https://github.com/ReVanced/revanced-patches/issues/4182)) ([b8635d0](b8635d0b88))
2024-12-21 15:09:41 +00:00
LisoUseInAIKyrios
b8635d0b88 feat(YouTube): Support version 19.47.53 (#4182) 2024-12-21 19:06:51 +04:00
190 changed files with 3906 additions and 1895 deletions

View File

@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: dev
ref: feat/translations
- name: Pull strings
uses: crowdin/github-action@v2

View File

@@ -1,3 +1,134 @@
# [5.8.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.4...v5.8.0-dev.5) (2024-12-27)
### Features
* **YouTube:** Add `Change form factor` patch ([#4217](https://github.com/ReVanced/revanced-patches/issues/4217)) ([644ac5b](https://github.com/ReVanced/revanced-patches/commit/644ac5baa68b209a32300149a2efa009b776f9a7))
# [5.8.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.3...v5.8.0-dev.4) (2024-12-27)
### Bug Fixes
* **GmsCore support:** Do not show battery optimization error on Android Automotive devices (Google built-in) ([#4218](https://github.com/ReVanced/revanced-patches/issues/4218)) ([d6e389c](https://github.com/ReVanced/revanced-patches/commit/d6e389cc43bc40724f032b230f70048276349a19))
### Features
* **Swipe controls:** Add option to enable/disable fullscreen swipe to next video ([#4222](https://github.com/ReVanced/revanced-patches/issues/4222)) ([119092f](https://github.com/ReVanced/revanced-patches/commit/119092fafa4129849246df15fe8076ed3b491b85))
* **YouTube:** Add `Exit fullscreen mode` patch ([#4223](https://github.com/ReVanced/revanced-patches/issues/4223)) ([bb5d03b](https://github.com/ReVanced/revanced-patches/commit/bb5d03bd89a3f932c77e4e9de90174c374933688))
# [5.8.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.2...v5.8.0-dev.3) (2024-12-26)
### Bug Fixes
* **YouTube - Force original audio:** If stream spoofing to Android then show a summary text why force audio is not available ([#4220](https://github.com/ReVanced/revanced-patches/issues/4220)) ([029aee8](https://github.com/ReVanced/revanced-patches/commit/029aee8023f096413fc80a2c583b4fe55ecb10ac))
# [5.8.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.1...v5.8.0-dev.2) (2024-12-24)
### Features
* **YouTube - Spoof app version:** Add 'Restore old navigation and toolbar icons' ([f84e459](https://github.com/ReVanced/revanced-patches/commit/f84e459d3d54b3001586796ab4e114ebadf09043))
# [5.8.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.2...v5.8.0-dev.1) (2024-12-24)
### Features
* **YouTube - Hide Shorts components:** Add option to hide Shorts in watch history ([#4214](https://github.com/ReVanced/revanced-patches/issues/4214)) ([19c2742](https://github.com/ReVanced/revanced-patches/commit/19c2742aa367367c77bb50ddad6f8a20fef8ea0a))
## [5.7.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.1...v5.7.2) (2024-12-24)
### Bug Fixes
* **YouTube - Hide layout components:** Don't hide Shorts channel bar when toggling for video player ([9af6412](https://github.com/ReVanced/revanced-patches/commit/9af6412d92ec31e612eaabba6578453da0fc61d6))
* **YouTube - Spoof video streams:** Add iOS TV client, restore iOS 'force AVC', show client type in stats for nerds ([#4202](https://github.com/ReVanced/revanced-patches/issues/4202)) ([ab29f80](https://github.com/ReVanced/revanced-patches/commit/ab29f808a9f55b5ab0055533c1a6de549b0631a6))
## [5.7.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.2-dev.1...v5.7.2-dev.2) (2024-12-23)
### Bug Fixes
* **YouTube - Hide layout components:** Don't hide Shorts channel bar when toggling for video player ([9af6412](https://github.com/ReVanced/revanced-patches/commit/9af6412d92ec31e612eaabba6578453da0fc61d6))
## [5.7.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.1...v5.7.2-dev.1) (2024-12-23)
### Bug Fixes
* **YouTube - Spoof video streams:** Add iOS TV client, restore iOS 'force AVC', show client type in stats for nerds ([#4202](https://github.com/ReVanced/revanced-patches/issues/4202)) ([ab29f80](https://github.com/ReVanced/revanced-patches/commit/ab29f808a9f55b5ab0055533c1a6de549b0631a6))
## [5.7.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.0...v5.7.1) (2024-12-23)
### Bug Fixes
* **YouTube - SponsorBlock:** Show a toast and not a dialog if segment submitted successfully ([134b189](https://github.com/ReVanced/revanced-patches/commit/134b189791113dcf1a1cb7c87b8a0954f432730c))
* **YouTube - Spoof video streams:** Use 2 letter device language code ([33ff997](https://github.com/ReVanced/revanced-patches/commit/33ff9972000581aca92262f984efb114eeeb9537))
* **YouTube - Spoof video streams:** Use Android VR authentication if using default audio language ([#4191](https://github.com/ReVanced/revanced-patches/issues/4191)) ([98773cc](https://github.com/ReVanced/revanced-patches/commit/98773cc7d46e5c9c7715b82c8006f1ccbcc5443c))
* **YouTube - Theme:** Use dark theme color for status and navigation bar ([0240efe](https://github.com/ReVanced/revanced-patches/commit/0240efe33e5444625ca2b760c861c9046d3dc836))
* **YouTube:** Do not reset playback speed to 1.0x after closing comment thread (Fixes stock YouTube bug) ([#4195](https://github.com/ReVanced/revanced-patches/issues/4195)) ([dda788c](https://github.com/ReVanced/revanced-patches/commit/dda788c58c789d4f91646ea8e8a8077f590ab6b3))
## [5.7.1-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.4...v5.7.1-dev.5) (2024-12-22)
### Bug Fixes
* **YouTube - Spoof video streams:** Use 2 letter device language code ([33ff997](https://github.com/ReVanced/revanced-patches/commit/33ff9972000581aca92262f984efb114eeeb9537))
## [5.7.1-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.3...v5.7.1-dev.4) (2024-12-22)
### Bug Fixes
* **YouTube:** Do not reset playback speed to 1.0x after closing comment thread (Fixes stock YouTube bug) ([#4195](https://github.com/ReVanced/revanced-patches/issues/4195)) ([dda788c](https://github.com/ReVanced/revanced-patches/commit/dda788c58c789d4f91646ea8e8a8077f590ab6b3))
## [5.7.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.2...v5.7.1-dev.3) (2024-12-22)
### Bug Fixes
* **YouTube - SponsorBlock:** Show a toast and not a dialog if segment submitted successfully ([134b189](https://github.com/ReVanced/revanced-patches/commit/134b189791113dcf1a1cb7c87b8a0954f432730c))
## [5.7.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.1...v5.7.1-dev.2) (2024-12-22)
### Bug Fixes
* **YouTube - Theme:** Use dark theme color for status and navigation bar ([0240efe](https://github.com/ReVanced/revanced-patches/commit/0240efe33e5444625ca2b760c861c9046d3dc836))
## [5.7.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.0...v5.7.1-dev.1) (2024-12-22)
### Bug Fixes
* **YouTube - Spoof video streams:** Use Android VR authentication if using default audio language ([#4191](https://github.com/ReVanced/revanced-patches/issues/4191)) ([98773cc](https://github.com/ReVanced/revanced-patches/commit/98773cc7d46e5c9c7715b82c8006f1ccbcc5443c))
# [5.7.0](https://github.com/ReVanced/revanced-patches/compare/v5.6.0...v5.7.0) (2024-12-22)
### Bug Fixes
* **YouTube - Force original audio:** Use correct availability for settings UI ([a7eedcb](https://github.com/ReVanced/revanced-patches/commit/a7eedcb4cca6b7b12629c478c24c0899c80e3615))
* **YouTube - Spoof video stream:** Remove UI client type setting. Allow setting default audio language. ([#4184](https://github.com/ReVanced/revanced-patches/issues/4184)) ([99f3f29](https://github.com/ReVanced/revanced-patches/commit/99f3f29c649bf7693c05bbce2bb49bd53e05f050))
* **YouTube - Spoof video streams:** Remove iOS, add clients Android TV and Android Creator ([#4180](https://github.com/ReVanced/revanced-patches/issues/4180)) ([86abfb2](https://github.com/ReVanced/revanced-patches/commit/86abfb2b0d4675f0a1cb9ab244783075bfe89281))
* **YouTube:** Change fingerprints to support a wider range of target versions ([8a09174](https://github.com/ReVanced/revanced-patches/commit/8a09174def205a26ce49cb7815097e235069526a))
### Features
* **YouTube:** Support version `19.47.53` ([#4182](https://github.com/ReVanced/revanced-patches/issues/4182)) ([2089e61](https://github.com/ReVanced/revanced-patches/commit/2089e613d36c45352db7d852aaee0087b1c3e1a4))
# [5.7.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.6.1-dev.4...v5.7.0-dev.1) (2024-12-21)
### Features
* **YouTube:** Support version `19.47.53` ([#4182](https://github.com/ReVanced/revanced-patches/issues/4182)) ([2089e61](https://github.com/ReVanced/revanced-patches/commit/2089e613d36c45352db7d852aaee0087b1c3e1a4))
## [5.6.1-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.6.1-dev.3...v5.6.1-dev.4) (2024-12-21)

View File

@@ -106,7 +106,11 @@ public class GmsCoreSupport {
}
// Check if GmsCore is whitelisted from battery optimizations.
if (batteryOptimizationsEnabled(context)) {
if (isAndroidAutomotive(context)) {
// Ignore Android Automotive devices (Google built-in),
// as there is no way to disable battery optimizations.
Logger.printDebug(() -> "Device is Android Automotive");
} else if (batteryOptimizationsEnabled(context)) {
Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations");
showBatteryOptimizationDialog(context,
@@ -147,6 +151,10 @@ public class GmsCoreSupport {
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
}
private static boolean isAndroidAutomotive(Context context) {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
private static String getGmsCoreDownload() {
final var vendorGroupId = getGmsCoreVendorGroupId();
//noinspection SwitchStatementWithTooFewBranches

View File

@@ -523,6 +523,11 @@ public class Utils {
return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
}
public static boolean isLandscapeOrientation() {
final int orientation = context.getResources().getConfiguration().orientation;
return orientation == Configuration.ORIENTATION_LANDSCAPE;
}
/**
* Automatically logs any exceptions the runnable throws.
*
@@ -595,7 +600,7 @@ public class Utils {
|| networkType == NetworkType.OTHER;
}
@SuppressLint("MissingPermission") // permission already included in YouTube
@SuppressLint({"MissingPermission", "deprecation"}) // Permission already included in YouTube.
public static NetworkType getNetworkType() {
Context networkContext = getContext();
if (networkContext == null) {

View File

@@ -3,8 +3,11 @@ package app.revanced.extension.shared.settings;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.settings.Setting.parent;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability;
import app.revanced.extension.shared.spoof.AudioStreamLanguage;
import app.revanced.extension.shared.spoof.ClientType;
/**
* Settings shared across multiple apps.
@@ -20,5 +23,11 @@ public class BaseSettings {
public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false);
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
public static final EnumSetting<AudioStreamLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT, parent(SPOOF_VIDEO_STREAMS));
public static final EnumSetting<AudioStreamLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
// Client type must be last spoof setting due to cyclic references.
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
}

View File

@@ -153,7 +153,6 @@ public abstract class Setting<T> {
/**
* Confirmation message to display, if the user tries to change the setting from the default value.
* Currently this works only for Boolean setting types.
*/
@Nullable
public final StringRef userDialogMessage;
@@ -244,6 +243,7 @@ public abstract class Setting<T> {
*
* This method will be deleted in the future.
*/
@SuppressWarnings("rawtypes")
public static void migrateFromOldPreferences(@NonNull SharedPrefCategory oldPrefs, @NonNull Setting setting, String settingKey) {
if (!oldPrefs.preferences.contains(settingKey)) {
return; // Nothing to do.
@@ -419,6 +419,7 @@ public abstract class Setting<T> {
boolean rebootSettingChanged = false;
int numberOfSettingsImported = 0;
//noinspection rawtypes
for (Setting setting : SETTINGS) {
String key = setting.getImportExportKey();
if (json.has(key)) {

View File

@@ -42,7 +42,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
try {
Setting<?> setting = Setting.getSettingFromPath(str);
Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
if (setting == null) {
return;
}
@@ -52,23 +52,21 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
}
Logger.printDebug(() -> "Preference changed: " + setting.key);
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
updatePreference(pref, setting, true, settingImportInProgress);
// Update any other preference availability that may now be different.
updateUIAvailability();
if (settingImportInProgress) {
return;
}
if (!showingUserDialogMessage) {
if (setting.userDialogMessage != null && ((SwitchPreference) pref).isChecked() != (Boolean) setting.defaultValue) {
showSettingUserDialogConfirmation((SwitchPreference) pref, (BooleanSetting) setting);
if (!settingImportInProgress && !showingUserDialogMessage) {
if (setting.userDialogMessage != null && !prefIsSetToDefault(pref, setting)) {
// Do not change the setting yet, to allow preserving whatever
// list/text value was previously set if it needs to be reverted.
showSettingUserDialogConfirmation(pref, setting);
return;
} else if (setting.rebootApp) {
showRestartDialog(getContext());
}
}
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
updatePreference(pref, setting, true, settingImportInProgress);
// Update any other preference availability that may now be different.
updateUIAvailability();
} catch (Exception ex) {
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
}
@@ -92,7 +90,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
Utils.setPreferenceTitlesToMultiLineIfNeeded(screen);
}
private void showSettingUserDialogConfirmation(SwitchPreference switchPref, BooleanSetting setting) {
private void showSettingUserDialogConfirmation(Preference pref, Setting<?> setting) {
Utils.verifyOnMainThread();
final var context = getContext();
@@ -104,12 +102,19 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
.setTitle(confirmDialogTitle)
.setMessage(Objects.requireNonNull(setting.userDialogMessage).toString())
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
// User confirmed, save to the Setting.
updatePreference(pref, setting, true, false);
// Update availability of other preferences that may be changed.
updateUIAvailability();
if (setting.rebootApp) {
showRestartDialog(context);
}
})
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
switchPref.setChecked(setting.defaultValue); // Recursive call that resets the Setting value.
// Restore whatever the setting was before the change.
updatePreference(pref, setting, true, true);
})
.setOnDismissListener(dialog -> {
showingUserDialogMessage = false;
@@ -132,6 +137,24 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
updatePreferenceScreen(getPreferenceScreen(), false, false);
}
/**
* @return If the preference is currently set to the default value of the Setting.
*/
protected boolean prefIsSetToDefault(Preference pref, Setting<?> setting) {
if (pref instanceof SwitchPreference switchPref) {
return switchPref.isChecked() == (Boolean) setting.defaultValue;
}
if (pref instanceof EditTextPreference editPreference) {
return editPreference.getText().equals(setting.defaultValue.toString());
}
if (pref instanceof ListPreference listPref) {
return listPref.getValue().equals(setting.defaultValue.toString());
}
throw new IllegalStateException("Must override method to handle "
+ "preference type: " + pref.getClass());
}
/**
* Syncs all UI Preferences to any {@link Setting} they represent.
*/
@@ -170,23 +193,20 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
protected void syncSettingWithPreference(@NonNull Preference pref,
@NonNull Setting<?> setting,
boolean applySettingToPreference) {
if (pref instanceof SwitchPreference) {
SwitchPreference switchPref = (SwitchPreference) pref;
if (pref instanceof SwitchPreference switchPref) {
BooleanSetting boolSetting = (BooleanSetting) setting;
if (applySettingToPreference) {
switchPref.setChecked(boolSetting.get());
} else {
BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked());
}
} else if (pref instanceof EditTextPreference) {
EditTextPreference editPreference = (EditTextPreference) pref;
} else if (pref instanceof EditTextPreference editPreference) {
if (applySettingToPreference) {
editPreference.setText(setting.get().toString());
} else {
Setting.privateSetValueFromString(setting, editPreference.getText());
}
} else if (pref instanceof ListPreference) {
ListPreference listPref = (ListPreference) pref;
} else if (pref instanceof ListPreference listPref) {
if (applySettingToPreference) {
listPref.setValue(setting.get().toString());
} else {

View File

@@ -4,14 +4,12 @@ import java.util.Locale;
public enum AudioStreamLanguage {
/**
* YouTube default.
* Can be the original language or can be app language,
* depending on what YouTube decides to pick as the default.
* The current app language.
*/
DEFAULT,
// Language codes found in locale_config.xml
// Region specific variants of Chinese/English/Spanish/French have been removed.
// All region specific variants have been removed.
AF,
AM,
AR,
@@ -65,8 +63,7 @@ public enum AudioStreamLanguage {
OR,
PA,
PL,
PT_BR,
PT_PT,
PT,
RO,
RU,
SI,
@@ -88,26 +85,22 @@ public enum AudioStreamLanguage {
ZH,
ZU;
private final String iso639_1;
private final String language;
AudioStreamLanguage() {
String name = name();
final int regionSeparatorIndex = name.indexOf('_');
if (regionSeparatorIndex >= 0) {
iso639_1 = name.substring(0, regionSeparatorIndex).toLowerCase(Locale.US)
+ name.substring(regionSeparatorIndex);
} else {
iso639_1 = name().toLowerCase(Locale.US);
}
language = name().toLowerCase(Locale.US);
}
public String getIso639_1() {
/**
* @return The 2 letter ISO 639_1 language code.
*/
public String getLanguage() {
// Changing the app language does not force the app to completely restart,
// so the default needs to be the current language and not a static field.
if (this == DEFAULT) {
return Locale.getDefault().toLanguageTag();
return Locale.getDefault().getLanguage();
}
return iso639_1;
return language;
}
}

View File

@@ -4,9 +4,11 @@ import android.os.Build;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.settings.BaseSettings;
public enum ClientType {
// https://dumps.tadiphone.dev/dumps/oculus/eureka
ANDROID_VR_NO_AUTH( // Must be first so a default audio language can be set.
ANDROID_VR_NO_AUTH(
28,
"ANDROID_VR",
"Quest 3",
@@ -14,17 +16,9 @@ public enum ClientType {
"com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12; GB) gzip",
"32", // Android 12.1
"1.56.21",
false),
// Fall over to authenticated ('hl' is ignored and audio is same as language set in users Google account).
ANDROID_VR(
ANDROID_VR_NO_AUTH.id,
ANDROID_VR_NO_AUTH.clientName,
ANDROID_VR_NO_AUTH.deviceModel,
ANDROID_VR_NO_AUTH.osVersion,
ANDROID_VR_NO_AUTH.userAgent,
ANDROID_VR_NO_AUTH.androidSdkVersion,
ANDROID_VR_NO_AUTH.clientVersion,
true),
false,
"Android VR No auth"
),
ANDROID_UNPLUGGED(
29,
"ANDROID_UNPLUGGED",
@@ -33,16 +27,49 @@ public enum ClientType {
"com.google.android.apps.youtube.unplugged/8.49.0 (Linux; U; Android 14; GB) gzip",
"34",
"8.49.0",
true), // Requires login.
ANDROID_CREATOR(
14,
"ANDROID_CREATOR",
"Android",
"11",
"com.google.android.apps.youtube.creator/24.45.100 (Linux; U; Android 11) gzip",
"30",
"24.45.100",
true); // Requires login.
true,
"Android TV"
),
ANDROID_VR(
ANDROID_VR_NO_AUTH.id,
ANDROID_VR_NO_AUTH.clientName,
ANDROID_VR_NO_AUTH.deviceModel,
ANDROID_VR_NO_AUTH.osVersion,
ANDROID_VR_NO_AUTH.userAgent,
ANDROID_VR_NO_AUTH.androidSdkVersion,
ANDROID_VR_NO_AUTH.clientVersion,
true,
"Android VR"
),
IOS_UNPLUGGED(33,
"IOS_UNPLUGGED",
forceAVC()
? "iPhone12,5" // 11 Pro Max (last device with iOS 13)
: "iPhone16,2", // 15 Pro Max
// iOS 13 and earlier uses only AVC. 14+ adds VP9 and AV1.
forceAVC()
? "13.7.17H35" // Last release of iOS 13.
: "18.1.1.22B91",
forceAVC()
? "com.google.ios.youtubeunplugged/6.45 (iPhone; U; CPU iOS 13_7 like Mac OS X)"
: "com.google.ios.youtubeunplugged/8.33 (iPhone; U; CPU iOS 18_1_1 like Mac OS X)",
null,
// Version number should be a valid iOS release.
// https://www.ipa4fun.com/history/152043/
// Some newer versions can also force AVC,
// but 6.45 is the last version that supports iOS 13.
forceAVC()
? "6.45"
: "8.33",
true,
forceAVC()
? "iOS TV Force AVC"
: "iOS TV"
);
private static boolean forceAVC() {
return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
}
/**
* YouTube
@@ -84,6 +111,11 @@ public enum ClientType {
*/
public final boolean canLogin;
/**
* Friendly name displayed in stats for nerds.
*/
public final String friendlyName;
ClientType(int id,
String clientName,
String deviceModel,
@@ -91,7 +123,8 @@ public enum ClientType {
String userAgent,
@Nullable String androidSdkVersion,
String clientVersion,
boolean canLogin) {
boolean canLogin,
String friendlyName) {
this.id = id;
this.clientName = clientName;
this.deviceModel = deviceModel;
@@ -100,5 +133,7 @@ public enum ClientType {
this.androidSdkVersion = androidSdkVersion;
this.clientVersion = clientVersion;
this.canLogin = canLogin;
this.friendlyName = friendlyName;
}
}

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.shared.spoof;
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.Nullable;
@@ -17,6 +18,9 @@ import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
public class SpoofVideoStreamsPatch {
private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_VIDEO_STREAMS.get();
private static final boolean FIX_HLS_CURRENT_TIME = SPOOF_STREAMING_DATA
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
/**
* Any unreachable ip address. Used to intentionally fail requests.
*/
@@ -30,15 +34,10 @@ public class SpoofVideoStreamsPatch {
return false; // Modified during patching.
}
public static final class NotSpoofingAndroidAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
if (SpoofVideoStreamsPatch.isPatchIncluded()) {
return !BaseSettings.SPOOF_VIDEO_STREAMS.get();
}
return true;
}
public static boolean notSpoofingToAndroid() {
return !isPatchIncluded()
|| !BaseSettings.SPOOF_VIDEO_STREAMS.get()
|| BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
}
/**
@@ -97,6 +96,17 @@ public class SpoofVideoStreamsPatch {
return SPOOF_STREAMING_DATA;
}
/**
* Injection point.
* Only invoked when playing a livestream on an iOS client.
*/
public static boolean fixHLSCurrentTime(boolean original) {
if (!SPOOF_STREAMING_DATA) {
return original;
}
return false;
}
/**
* Injection point.
*/
@@ -183,4 +193,38 @@ public class SpoofVideoStreamsPatch {
return postData;
}
/**
* Injection point.
*/
public static String appendSpoofedClient(String videoFormat) {
try {
if (SPOOF_STREAMING_DATA && BaseSettings.SPOOF_STREAMING_DATA_STATS_FOR_NERDS.get()
&& !TextUtils.isEmpty(videoFormat)) {
// Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages.
return "\u202D" + videoFormat + "\u2009(" // u202D = left to right override
+ StreamingDataRequest.getLastSpoofedClientName() + ")";
}
} catch (Exception ex) {
Logger.printException(() -> "appendSpoofedClient failure", ex);
}
return videoFormat;
}
public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
return !BaseSettings.SPOOF_VIDEO_STREAMS.get()
|| BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_VR_NO_AUTH;
}
}
public static final class SpoofiOSAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
return BaseSettings.SPOOF_VIDEO_STREAMS.get()
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
}
}
}

View File

@@ -10,6 +10,7 @@ import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.spoof.AudioStreamLanguage;
import app.revanced.extension.shared.spoof.ClientType;
final class PlayerRoutes {
@@ -36,8 +37,17 @@ final class PlayerRoutes {
try {
JSONObject context = new JSONObject();
// Can override default language only if no login is used.
// Could use preferred audio for all clients that do not login,
// but if this is a fall over client it will set the language even though
// the audio language is not selectable in the UI.
ClientType userSelectedClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
AudioStreamLanguage language = userSelectedClient == ClientType.ANDROID_VR_NO_AUTH
? BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get()
: AudioStreamLanguage.DEFAULT;
JSONObject client = new JSONObject();
client.put("hl", BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get().getIso639_1());
client.put("hl", language.getLanguage());
client.put("clientName", clientType.clientName);
client.put("clientVersion", clientType.clientVersion);
client.put("deviceModel", clientType.deviceModel);

View File

@@ -35,21 +35,41 @@ import app.revanced.extension.shared.spoof.ClientType;
*/
public class StreamingDataRequest {
private static final ClientType[] CLIENT_ORDER_TO_USE = ClientType.values();
private static final ClientType[] CLIENT_ORDER_TO_USE;
static {
ClientType[] allClientTypes = ClientType.values();
ClientType preferredClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length];
CLIENT_ORDER_TO_USE[0] = preferredClient;
int i = 1;
for (ClientType c : allClientTypes) {
if (c != preferredClient) {
CLIENT_ORDER_TO_USE[i++] = c;
}
}
}
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String[] REQUEST_HEADER_KEYS = {
AUTHORIZATION_HEADER, // Available only to logged-in users.
"X-GOOG-API-FORMAT-VERSION",
"X-Goog-Visitor-Id"
};
/**
* TCP connection and HTTP read timeout.
*/
private static final int HTTP_TIMEOUT_MILLISECONDS = 10 * 1000;
/**
* Any arbitrarily large value, but must be at least twice {@link #HTTP_TIMEOUT_MILLISECONDS}
*/
private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000;
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
new LinkedHashMap<>(100) {
/**
@@ -67,7 +87,15 @@ public class StreamingDataRequest {
}
});
private static volatile ClientType lastSpoofedClientType;
public static String getLastSpoofedClientName() {
ClientType client = lastSpoofedClientType;
return client == null ? "Unknown" : client.friendlyName;
}
private final String videoId;
private final Future<ByteBuffer> future;
private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
@@ -154,7 +182,7 @@ public class StreamingDataRequest {
// Retry with different client if empty response body is received.
int i = 0;
for (ClientType clientType : CLIENT_ORDER_TO_USE) {
// Show an error if the last client type fails, or if the debug is enabled then show for all attempts.
// Show an error if the last client type fails, or if debug is enabled then show for all attempts.
final boolean showErrorToast = (++i == CLIENT_ORDER_TO_USE.length) || debugEnabled;
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
@@ -163,7 +191,9 @@ public class StreamingDataRequest {
// gzip encoding doesn't response with content length (-1),
// but empty response body does.
if (connection.getContentLength() == 0) {
Logger.printDebug(() -> "Received empty response for client: " + clientType);
if (BaseSettings.DEBUG.get()) {
Logger.printException(() -> "Ignoring empty client: " + clientType);
}
} else {
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream());
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
@@ -173,6 +203,7 @@ public class StreamingDataRequest {
while ((bytesRead = inputStream.read(buffer)) >= 0) {
baos.write(buffer, 0, bytesRead);
}
lastSpoofedClientType = clientType;
return ByteBuffer.wrap(baos.toByteArray());
}
@@ -183,7 +214,8 @@ public class StreamingDataRequest {
}
}
handleConnectionError("Could not fetch any client streams", null, debugEnabled);
lastSpoofedClientType = null;
handleConnectionError("Could not fetch any client streams", null, true);
return null;
}

View File

@@ -176,14 +176,13 @@ public final class AlternativeThumbnailsPatch {
// Unknown tab, treat as the home tab;
return homeOption;
}
if (selectedNavButton == NavigationButton.HOME) {
return homeOption;
}
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS || selectedNavButton == NavigationButton.NOTIFICATIONS) {
return subscriptionsOption;
}
// A library tab variant is active.
return libraryOption;
return switch (selectedNavButton) {
case SUBSCRIPTIONS, NOTIFICATIONS -> subscriptionsOption;
case LIBRARY -> libraryOption;
// Home or explore tab.
default -> homeOption;
};
}
/**

View File

@@ -0,0 +1,54 @@
package app.revanced.extension.youtube.patches;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class ChangeFormFactorPatch {
public enum FormFactor {
/**
* Unmodified, and same as un-patched.
*/
DEFAULT(null),
/**
* <pre>
* Some changes include:
* - Explore tab is present.
* - watch history is missing.
* - feed thumbnails fade in.
*/
UNKNOWN(0),
SMALL(1),
LARGE(2),
/**
* Cars with 'Google built-in'.
* Layout seems identical to {@link #UNKNOWN}
* even when using an Android Automotive device.
*/
AUTOMOTIVE(3),
WEARABLE(4);
@Nullable
final Integer formFactorType;
FormFactor(@Nullable Integer formFactorType) {
this.formFactorType = formFactorType;
}
}
@Nullable
private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType;
/**
* Injection point.
*/
public static int getFormFactor(int original) {
return FORM_FACTOR_TYPE == null
? original
: FORM_FACTOR_TYPE;
}
}

View File

@@ -0,0 +1,49 @@
package app.revanced.extension.youtube.patches;
import android.widget.ImageView;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public class ExitFullscreenPatch {
public enum FullscreenMode {
DISABLED,
PORTRAIT,
LANDSCAPE,
PORTRAIT_LANDSCAPE,
}
/**
* Injection point.
*/
public static void endOfVideoReached() {
try {
FullscreenMode mode = Settings.EXIT_FULLSCREEN.get();
if (mode == FullscreenMode.DISABLED) {
return;
}
if (PlayerType.getCurrent() == PlayerType.WATCH_WHILE_FULLSCREEN) {
if (Utils.isLandscapeOrientation()) {
if (mode == FullscreenMode.PORTRAIT) {
return;
}
} else if (mode == FullscreenMode.LANDSCAPE) {
return;
}
ImageView fullscreenButton = PlayerControlsPatch.fullscreenButtonRef.get();
if (fullscreenButton != null) {
Logger.printDebug(() -> "Clicking fullscreen button");
fullscreenButton.performClick();
}
}
} catch (Exception ex) {
Logger.printException(() -> "endOfVideoReached failure", ex);
}
}
}

View File

@@ -0,0 +1,24 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public class FixPlaybackSpeedWhilePlayingPatch {
private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
public static boolean playbackSpeedChanged(float playbackSpeed) {
if (playbackSpeed == DEFAULT_YOUTUBE_PLAYBACK_SPEED &&
PlayerType.getCurrent().isMaximizedOrFullscreen()) {
Logger.printDebug(() -> "Blocking call to change playback speed to 1.0x");
return true;
}
return false;
}
}

View File

@@ -1,6 +1,8 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
@@ -8,6 +10,20 @@ public class ForceOriginalAudioPatch {
private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4";
/**
* If the conditions to use this patch were present when the app launched.
*/
public static boolean PATCH_AVAILABLE = SpoofVideoStreamsPatch.notSpoofingToAndroid();
public static final class ForceOriginalAudioAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
// Check conditions of launch and now. Otherwise if spoofing is changed
// without a restart the setting will show as available when it's not.
return PATCH_AVAILABLE && SpoofVideoStreamsPatch.notSpoofingToAndroid();
}
}
/**
* Injection point.
*/

View File

@@ -4,15 +4,29 @@ import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import java.lang.ref.WeakReference;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class PlayerControlsPatch {
public static WeakReference<ImageView> fullscreenButtonRef = new WeakReference<>(null);
private static boolean fullscreenButtonVisibilityCallbacksExist() {
return false; // Modified during patching if needed.
}
/**
* Injection point.
*/
public static void setFullscreenCloseButton(ImageView imageButton) {
fullscreenButtonRef = new WeakReference<>(imageButton);
if (!fullscreenButtonVisibilityCallbacksExist()) {
return;
}
// Add a global listener, since the protected method
// View#onVisibilityChanged() does not have any call backs.
imageButton.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@@ -39,7 +53,7 @@ public class PlayerControlsPatch {
}
// noinspection EmptyMethod
public static void fullscreenButtonVisibilityChanged(boolean isVisible) {
private static void fullscreenButtonVisibilityChanged(boolean isVisible) {
// Code added during patching.
}

View File

@@ -1,16 +0,0 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class TabletLayoutPatch {
private static final boolean TABLET_LAYOUT_ENABLED = Settings.TABLET_LAYOUT.get();
/**
* Injection point.
*/
public static boolean getTabletLayoutEnabled() {
return TABLET_LAYOUT_ENABLED;
}
}

View File

@@ -528,14 +528,13 @@ final class KeywordContentFilter extends Filter {
if (selectedNavButton == null) {
return hideHome; // Unknown tab, treat the same as home.
}
if (selectedNavButton == NavigationButton.HOME) {
return hideHome;
}
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) {
return hideSubscriptions;
}
// User is in the Library or Notifications tab.
return false;
return switch (selectedNavButton) {
case HOME, EXPLORE -> hideHome;
case SUBSCRIPTIONS -> hideSubscriptions;
// User is in the Library or notifications.
default -> false;
};
}
private void updateStats(boolean videoWasHidden, @Nullable String keyword) {

View File

@@ -161,9 +161,9 @@ public final class LayoutComponentsFilter extends Filter {
"inline_expander"
);
final var channelBar = new StringFilterGroup(
final var compactChannelBar = new StringFilterGroup(
Settings.HIDE_CHANNEL_BAR,
"channel_bar"
"compact_channel_bar"
);
final var relatedVideos = new StringFilterGroup(
@@ -252,7 +252,7 @@ public final class LayoutComponentsFilter extends Filter {
inFeedSurvey,
notifyMe,
likeSubscribeGlow,
channelBar,
compactChannelBar,
communityPosts,
paidPromotion,
searchResultVideo,

View File

@@ -297,7 +297,7 @@ public final class ShortsFilter extends Filter {
if (matchedGroup == suggestedAction) {
// Skip searching the buffer if all suggested actions are set to hidden.
// This has a secondary effect of hiding all new un-identified actions
// under the assumption that the user wants all actions hidden.
// under the assumption that the user wants all suggestions hidden.
if (isEverySuggestedActionFilterEnabled()) {
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
@@ -324,19 +324,22 @@ public final class ShortsFilter extends Filter {
}
private static boolean shouldHideShortsFeedItems() {
// Known issue if hide home is on but at least one other hide is off:
//
// Shorts suggestions will load in the background if a video is opened and
// immediately minimized before any suggestions are loaded.
// In this state the player type will show minimized, which cannot
// distinguish between Shorts suggestions loading in the player and between
// scrolling thru search/home/subscription tabs while a player is minimized.
final boolean hideHome = Settings.HIDE_SHORTS_HOME.get();
final boolean hideSubscriptions = Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
final boolean hideSearch = Settings.HIDE_SHORTS_SEARCH.get();
final boolean hideHistory = Settings.HIDE_SHORTS_HISTORY.get();
if (hideHome && hideSubscriptions && hideSearch) {
// Shorts suggestions can load in the background if a video is opened and
// then immediately minimized before any suggestions are loaded.
// In this state the player type will show minimized, which makes it not possible to
// distinguish between Shorts suggestions loading in the player and between
// scrolling thru search/home/subscription tabs while a player is minimized.
//
// To avoid this situation for users that never want to show Shorts (all hide Shorts options are enabled)
// then hide all Shorts everywhere including the Library history and Library playlists.
if (!hideHome && !hideSubscriptions && !hideSearch && !hideHistory) {
return false;
}
if (hideHome && hideSubscriptions && hideSearch && hideHistory) {
return true;
}
@@ -352,24 +355,29 @@ public final class ShortsFilter extends Filter {
}
// Avoid checking navigation button status if all other Shorts should show.
if (!hideHome && !hideSubscriptions) {
if (!hideHome && !hideSubscriptions && !hideHistory) {
return false;
}
// Check navigation absolutely last since the check may block this thread.
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
if (selectedNavButton == null) {
return hideHome; // Unknown tab, treat the same as home.
}
if (selectedNavButton == NavigationButton.HOME) {
return hideHome;
}
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) {
return hideSubscriptions;
}
// User must be in the library tab. Don't hide the history or any playlists here.
return false;
return switch (selectedNavButton) {
case HOME, EXPLORE -> hideHome;
case SUBSCRIPTIONS -> hideSubscriptions;
case LIBRARY -> hideHistory;
default -> false;
};
}
/**
* Injection point. Only used if patching older than 19.03.
* This hook may be obsolete even for old versions
* as they now use a litho layout like newer versions.
*/
public static void hideShortsShelf(final View shortsShelfView) {
if (shouldHideShortsFeedItems()) {
Utils.hideViewByLayoutParams(shortsShelfView);

View File

@@ -7,8 +7,10 @@ import static app.revanced.extension.shared.settings.Setting.migrateFromOldPrefe
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
import static app.revanced.extension.shared.settings.Setting.parent;
import static app.revanced.extension.shared.settings.Setting.parentsAny;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.NotSpoofingAndroidAvailability;
import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor;
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.FullscreenMode;
import static app.revanced.extension.youtube.patches.ForceOriginalAudioPatch.ForceOriginalAudioAvailability;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideExpandCloseAvailability;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
@@ -54,7 +56,7 @@ public class Settings extends BaseSettings {
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true);
// Audio
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, new NotSpoofingAndroidAvailability());
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, new ForceOriginalAudioAvailability());
// Ads
public static final BooleanSetting HIDE_BUTTONED_ADS = new BooleanSetting("revanced_hide_buttoned_ads", TRUE);
@@ -120,6 +122,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE);
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE, true);
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
@@ -139,10 +142,10 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE);
public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE);
public static final BooleanSetting HIDE_VIDEO_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE);
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true);
public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE);
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true);
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
// Miniplayer
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.DEFAULT, true);
private static final Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4);
@@ -200,15 +203,15 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_PLAYER_FLYOUT_WATCH_IN_VR = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE);
// General layout
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
public static final BooleanSetting TABLET_LAYOUT = new BooleanSetting("revanced_tablet_layout", FALSE, true, "revanced_tablet_layout_user_dialog_message");
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
public static final EnumSetting<StartPage> CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true);
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", IS_19_17_OR_GREATER ? "19.35.36" : "17.33.42", true, parent(SPOOF_APP_VERSION));
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", IS_19_17_OR_GREATER ? "19.26.42" : "17.33.42", true, parent(SPOOF_APP_VERSION));
// Custom filter
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
@@ -233,6 +236,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_SHORTS_FULL_VIDEO_LINK_LABEL = new BooleanSetting("revanced_hide_shorts_full_video_link_label", FALSE);
public static final BooleanSetting HIDE_SHORTS_GREEN_SCREEN_BUTTON = new BooleanSetting("revanced_hide_shorts_green_screen_button", TRUE);
public static final BooleanSetting HIDE_SHORTS_HASHTAG_BUTTON = new BooleanSetting("revanced_hide_shorts_hashtag_button", TRUE);
public static final BooleanSetting HIDE_SHORTS_HISTORY = new BooleanSetting("revanced_hide_shorts_history", FALSE);
public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE);
public static final BooleanSetting HIDE_SHORTS_INFO_PANEL = new BooleanSetting("revanced_hide_shorts_info_panel", TRUE);
public static final BooleanSetting HIDE_SHORTS_JOIN_BUTTON = new BooleanSetting("revanced_hide_shorts_join_button", TRUE);
@@ -293,8 +297,9 @@ public class Settings extends BaseSettings {
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, parent(BaseSettings.DEBUG));
// Swipe controls
public static final BooleanSetting SWIPE_BRIGHTNESS = new BooleanSetting("revanced_swipe_brightness", TRUE);
public static final BooleanSetting SWIPE_VOLUME = new BooleanSetting("revanced_swipe_volume", TRUE);
public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true);
public static final BooleanSetting SWIPE_BRIGHTNESS = new BooleanSetting("revanced_swipe_brightness", FALSE);
public static final BooleanSetting SWIPE_VOLUME = new BooleanSetting("revanced_swipe_volume", FALSE);
public static final BooleanSetting SWIPE_PRESS_TO_ENGAGE = new BooleanSetting("revanced_swipe_press_to_engage", FALSE, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final BooleanSetting SWIPE_HAPTIC_FEEDBACK = new BooleanSetting("revanced_swipe_haptic_feedback", TRUE, true,

View File

@@ -0,0 +1,36 @@
package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import android.content.Context;
import android.preference.SwitchPreference;
import android.util.AttributeSet;
import app.revanced.extension.youtube.patches.ForceOriginalAudioPatch;
@SuppressWarnings({"deprecation", "unused"})
public class ForceOriginalAudioSwitchPreference extends SwitchPreference {
{
if (!ForceOriginalAudioPatch.PATCH_AVAILABLE) {
// Show why force audio is not available.
String summary = str("revanced_force_original_audio_not_available");
setSummary(summary);
setSummaryOn(summary);
setSummaryOff(summary);
}
}
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ForceOriginalAudioSwitchPreference(Context context) {
super(context);
}
}

View File

@@ -0,0 +1,87 @@
package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.spoof.ClientType;
@SuppressWarnings({"deprecation", "unused"})
public class SpoofStreamingDataSideEffectsPreference extends Preference {
@Nullable
private ClientType currentClientType;
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
// Because this listener may run before the ReVanced settings fragment updates Settings,
// this could show the prior config and not the current.
//
// Push this call to the end of the main run queue,
// so all other listeners are done and Settings is up to date.
Utils.runOnMainThread(this::updateUI);
};
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SpoofStreamingDataSideEffectsPreference(Context context) {
super(context);
}
private void addChangeListener() {
Setting.preferences.preferences.registerOnSharedPreferenceChangeListener(listener);
}
private void removeChangeListener() {
Setting.preferences.preferences.unregisterOnSharedPreferenceChangeListener(listener);
}
@Override
protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
super.onAttachedToHierarchy(preferenceManager);
updateUI();
addChangeListener();
}
@Override
protected void onPrepareForRemoval() {
super.onPrepareForRemoval();
removeChangeListener();
}
private void updateUI() {
ClientType clientType = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
if (currentClientType == clientType) {
return;
}
Logger.printDebug(() -> "Updating spoof stream side effects preference");
setEnabled(BaseSettings.SPOOF_VIDEO_STREAMS.get());
String key = "revanced_spoof_video_streams_about_" +
(clientType == ClientType.IOS_UNPLUGGED
? "ios_tv"
: "android");
setTitle(str(key + "_title"));
setSummary(str(key + "_summary"));
}
}

View File

@@ -3,12 +3,15 @@ package app.revanced.extension.youtube.shared;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton.CREATE;
import android.app.Activity;
import android.os.Build;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
@@ -54,20 +57,21 @@ public final class NavigationBar {
* How long to wait for the set nav button latch to be released. Maximum wait time must
* be as small as possible while still allowing enough time for the nav bar to update.
*
* YT calls it's back button handlers out of order,
* and litho starts filtering before the navigation bar is updated.
* YT calls it's back button handlers out of order, and litho starts filtering before the
* navigation bar is updated. Fixing this situation and not needlessly waiting requires
* somehow detecting if a back button key/gesture will not change the active tab.
*
* Fixing this situation and not needlessly waiting requires somehow
* detecting if a back button key-press will cause a tab change.
* On average the time between pressing the back button and the first litho event is
* about 10-20ms. Waiting up to 75-150ms should be enough time to handle normal use cases
* and not be noticeable, since YT typically takes 100-200ms (or more) to update the view.
*
* Typically after pressing the back button, the time between the first litho event and
* when the nav button is updated is about 10-20ms. Using 50-100ms here should be enough time
* and not noticeable, since YT typically takes 100-200ms (or more) to update the view anyways.
* This delay is only noticeable when the device back button/gesture will not
* change the current navigation tab, such as backing out of the watch history.
*
* This issue can also be avoided on a patch by patch basis, by avoiding calls to
* {@link NavigationButton#getSelectedNavigationButton()} unless absolutely necessary.
*/
private static final long LATCH_AWAIT_TIMEOUT_MILLISECONDS = 75;
private static final long LATCH_AWAIT_TIMEOUT_MILLISECONDS = 120;
/**
* Used as a workaround to fix the issue of YT calling back button handlers out of order.
@@ -113,7 +117,8 @@ public final class NavigationBar {
// The latch is released from the main thread, and waiting from the main thread will always timeout.
// This situation has only been observed when navigating out of a submenu and not changing tabs.
// and for that use case the nav bar does not change so it's safe to return here.
Logger.printDebug(() -> "Cannot block main thread waiting for nav button. Using last known navbar button status.");
Logger.printDebug(() -> "Cannot block main thread waiting for nav button. " +
"Using last known navbar button status.");
return;
}
@@ -131,7 +136,9 @@ public final class NavigationBar {
Logger.printDebug(() -> "Latch wait timed out");
} catch (InterruptedException ex) {
Logger.printException(() -> "Latch wait interrupted failure", ex); // Will never happen.
// Calling YouTube thread was interrupted.
Logger.printException(() -> "Latch wait interrupted", ex);
Thread.currentThread().interrupt(); // Restore interrupt status flag.
}
}
@@ -238,6 +245,30 @@ public final class NavigationBar {
// Code is added during patching.
}
/**
* Use the bundled non cairo filled icon instead of a custom icon.
* Use the old non cairo filled icon, which is almost identical to
* the what would be the filled cairo icon.
*/
private static final int fillBellCairoBlack = Utils.getResourceIdentifier(
"yt_fill_bell_black_24", "drawable");
/**
* Injection point.
* Fixes missing drawable.
*/
@RequiresApi(api = Build.VERSION_CODES.N)
@SuppressWarnings({"unchecked", "rawtypes"})
public static void setCairoNotificationFilledIcon(EnumMap enumMap, Enum tabActivityCairo) {
if (fillBellCairoBlack != 0) {
// Show a popup informing this fix is no longer needed to those who might care.
if (BaseSettings.DEBUG.get() && enumMap.containsKey(tabActivityCairo)) {
Logger.printException(() -> "YouTube fixed the cairo notification icons");
}
enumMap.putIfAbsent(tabActivityCairo, fillBellCairoBlack);
}
}
public enum NavigationButton {
HOME("PIVOT_HOME", "TAB_HOME_CAIRO"),
SHORTS("TAB_SHORTS", "TAB_SHORTS_CAIRO"),
@@ -246,6 +277,10 @@ public final class NavigationBar {
* This tab will never be in a selected state, even if the create video UI is on screen.
*/
CREATE("CREATION_TAB_LARGE", "CREATION_TAB_LARGE_CAIRO"),
/**
* Only shown to automotive layout.
*/
EXPLORE("TAB_EXPLORE"),
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", "TAB_SUBSCRIPTIONS_CAIRO"),
/**
* Notifications tab. Only present when
@@ -283,8 +318,8 @@ public final class NavigationBar {
*
* All code calling this method should handle a null return value.
*
* <b>Due to issues with how YT processes physical back button events,
* this patch uses workarounds that can cause this method to take up to 75ms
* <b>Due to issues with how YT processes physical back button/gesture events,
* this patch uses workarounds that can cause this method to take up to 120ms
* if the device back button was recently pressed.</b>
*
* @return The active navigation tab.

View File

@@ -73,7 +73,7 @@ enum class PlayerType {
onChange(currentPlayerType)
}
@Volatile // value is read/write from different threads
@Volatile // Read/write from different threads.
private var currentPlayerType = NONE
/**

View File

@@ -46,6 +46,7 @@ enum class VideoState {
currentVideoState = value
}
@Volatile // Read/write from different threads.
private var currentVideoState: VideoState? = null
}
}

View File

@@ -150,11 +150,16 @@ public class SBRequester {
String end = String.format(Locale.US, TIME_TEMPLATE, endTime / 1000f);
String duration = String.format(Locale.US, TIME_TEMPLATE, videoLength / 1000f);
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.SUBMIT_SEGMENTS, privateUserId, videoId, category, start, end, duration);
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.SUBMIT_SEGMENTS,
privateUserId, videoId, category, start, end, duration);
final int responseCode = connection.getResponseCode();
String userMessage = switch (responseCode) {
case HTTP_STATUS_CODE_SUCCESS -> str("revanced_sb_submit_succeeded");
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
Utils.showToastLong(str("revanced_sb_submit_succeeded"));
return;
}
String userErrorMessage = switch (responseCode) {
case 409 -> str("revanced_sb_submit_failed_duplicate");
case 403 -> str("revanced_sb_submit_failed_forbidden",
Requester.parseErrorStringAndDisconnect(connection));
@@ -167,7 +172,7 @@ public class SBRequester {
// Message might be about the users account or an error too large to show in a toast.
// Use a dialog instead.
SponsorBlockUtils.showErrorDialog(userMessage);
SponsorBlockUtils.showErrorDialog(userErrorMessage);
} catch (SocketTimeoutException ex) {
Logger.printDebug(() -> "Timeout", ex);
Utils.showToastLong(str("revanced_sb_submit_failed_timeout"));

View File

@@ -8,6 +8,7 @@ import android.view.MotionEvent
import android.view.ViewGroup
import app.revanced.extension.shared.Logger.printDebug
import app.revanced.extension.shared.Logger.printException
import app.revanced.extension.youtube.settings.Settings
import app.revanced.extension.youtube.shared.PlayerType
import app.revanced.extension.youtube.swipecontrols.controller.AudioVolumeController
import app.revanced.extension.youtube.swipecontrols.controller.ScreenBrightnessController
@@ -232,5 +233,12 @@ class SwipeControlsHostActivity : Activity() {
@JvmStatic
var currentHost: WeakReference<SwipeControlsHostActivity> = WeakReference(null)
private set
/**
* Injection point.
*/
@Suppress("unused")
@JvmStatic
fun allowSwipeChangeVideo(original: Boolean): Boolean = Settings.SWIPE_CHANGE_VIDEO.get()
}
}

View File

@@ -1,5 +0,0 @@
package com.google.protos.youtube.api.innertube;
public class InnertubeContext$ClientInfo {
public int r;
}

View File

@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true
android.useAndroidX = true
kotlin.code.style = official
version = 5.6.1-dev.4
version = 5.8.0-dev.5

View File

@@ -461,10 +461,6 @@ public final class app/revanced/patches/reddit/customclients/joeyforreddit/detec
public static final fun getDisablePiracyDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/redditisfun/api/FingerprintsKt {
public static final fun baseClientIdFingerprint (Ljava/lang/String;)Lapp/revanced/patcher/Fingerprint;
}
public final class app/revanced/patches/reddit/customclients/redditisfun/api/SpoofClientPatchKt {
public static final fun getSpoofClientPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -548,7 +544,6 @@ public final class app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentP
}
public final class app/revanced/patches/shared/misc/extension/ExtensionHook {
public final fun getFingerprint ()Lapp/revanced/patcher/Fingerprint;
public final fun invoke (Lapp/revanced/patcher/patch/BytecodePatchContext;Ljava/lang/String;)V
}
@@ -1106,6 +1101,10 @@ public final class app/revanced/patches/youtube/layout/buttons/overlay/HidePlaye
public static final fun getHidePlayerOverlayButtonsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatchKt {
public static final fun getChangeFormFactorPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/layout/hide/endscreencards/HideEndscreenCardsPatchKt {
public static final fun getHideEndscreenCardsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1235,7 +1234,6 @@ public final class app/revanced/patches/youtube/layout/startupshortsreset/Disabl
}
public final class app/revanced/patches/youtube/layout/tablet/EnableTabletLayoutPatchKt {
public static final field EXTENSION_CLASS_DESCRIPTOR Ljava/lang/String;
public static final fun getEnableTabletLayoutPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1268,10 +1266,6 @@ public final class app/revanced/patches/youtube/misc/backgroundplayback/Backgrou
public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/misc/check/CheckEnvironmentPatchKt {
public static final fun getCheckEnvironmentPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatchKt {
public static final fun getEnableDebuggingPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1296,6 +1290,10 @@ public final class app/revanced/patches/youtube/misc/fix/playback/UserAgentClien
public static final fun getUserAgentClientSpoofPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/misc/fix/playbackspeed/FIxPlaybackSpeedWhilePlayingPatchKt {
public static final fun getFixPlaybackSpeedWhilePlayingPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatchKt {
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1404,10 +1402,6 @@ public final class app/revanced/patches/youtube/misc/zoomhaptics/ZoomHapticsPatc
public static final fun getZoomHapticsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/shared/FingerprintsKt {
public static final fun getRollingNumberTextViewAnimationUpdateFingerprint ()Lapp/revanced/patcher/Fingerprint;
}
public final class app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatchKt {
public static final fun getForceOriginalAudioPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}

View File

@@ -4,7 +4,7 @@ import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
fun baseClientIdFingerprint(string: String) = fingerprint {
internal fun baseClientIdFingerprint(string: String) = fingerprint {
strings("yyOCBp.RHJhDKd", string)
}

View File

@@ -92,7 +92,7 @@ fun sharedExtensionPatch(
}
class ExtensionHook internal constructor(
val fingerprint: Fingerprint,
private val fingerprint: Fingerprint,
private val insertIndexResolver: ((Method) -> Int),
private val contextRegisterResolver: (Method) -> String,
) {

View File

@@ -1,6 +1,7 @@
package app.revanced.patches.shared.misc.spoof
import app.revanced.patcher.fingerprint
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@@ -111,6 +112,23 @@ internal val buildMediaDataSourceFingerprint = fingerprint {
)
}
internal const val HLS_CURRENT_TIME_FEATURE_FLAG = 45355374L
internal val hlsCurrentTimeFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters("Z", "L")
literal {
HLS_CURRENT_TIME_FEATURE_FLAG
}
}
internal val nerdsStatsVideoFormatBuilderFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("Ljava/lang/String;")
parameters("L")
strings("codecs=\"")
}
internal val patchIncludedExtensionMethodFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Z")

View File

@@ -10,8 +10,10 @@ import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.insertFeatureFlagBooleanOverride
import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@@ -206,6 +208,34 @@ fun spoofVideoStreamsPatch(
""",
)
}
// endregion
// region Append spoof info.
nerdsStatsVideoFormatBuilderFingerprint.method.apply {
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$register
"""
)
}
}
// endregion
// region Fix iOS livestream current time.
hlsCurrentTimeFingerprint.method.insertFeatureFlagBooleanOverride(
HLS_CURRENT_TIME_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->fixHLSCurrentTime(Z)Z"
)
// endregion
executeBlock()

View File

@@ -77,6 +77,7 @@ val hideAdsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -33,6 +33,7 @@ val hideGetPremiumPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -31,6 +31,7 @@ val videoAdsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -61,6 +61,7 @@ val copyVideoUrlPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -32,6 +32,7 @@ val removeViewerDiscretionDialogPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -76,6 +76,7 @@ val downloadsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -31,6 +31,7 @@ val disablePreciseSeekingGesturePatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -34,6 +34,7 @@ val enableSeekbarTappingPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -44,6 +44,7 @@ val enableSlideToSeekPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -37,6 +37,7 @@ val seekbarThumbnailsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
)
)

View File

@@ -1,12 +1,23 @@
package app.revanced.patches.youtube.interaction.swipecontrols
import app.revanced.patcher.fingerprint
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
internal val swipeControlsHostActivityFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameters()
custom { method, _ ->
method.definingClass == "Lapp/revanced/extension/youtube/swipecontrols/SwipeControlsHostActivity;"
method.definingClass == EXTENSION_CLASS_DESCRIPTOR
}
}
internal const val SWIPE_CHANGE_VIDEO_FEATURE_FLAG = 45631116L
internal val swipeChangeVideoFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameters("L")
literal {
SWIPE_CHANGE_VIDEO_FEATURE_FLAG
}
}

View File

@@ -10,6 +10,8 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.playservice.is_19_23_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityFingerprint
@@ -17,6 +19,8 @@ import app.revanced.util.*
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/swipecontrols/SwipeControlsHostActivity;"
private val swipeControlsResourcePatch = resourcePatch {
dependsOn(
settingsPatch,
@@ -26,6 +30,12 @@ private val swipeControlsResourcePatch = resourcePatch {
execute {
addResources("youtube", "interaction.swipecontrols.swipeControlsResourcePatch")
if (is_19_25_or_greater) {
PreferenceScreen.SWIPE_CONTROLS.addPreferences(
SwitchPreference("revanced_swipe_change_video")
)
}
PreferenceScreen.SWIPE_CONTROLS.addPreferences(
SwitchPreference("revanced_swipe_brightness"),
SwitchPreference("revanced_swipe_volume"),
@@ -73,6 +83,7 @@ val swipeControlsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)
@@ -100,5 +111,16 @@ val swipeControlsPatch = bytecodePatch(
).toMutable()
}
}
// region patch to enable/disable swipe to change video.
if (is_19_23_or_greater) {
swipeChangeVideoFingerprint.method.insertFeatureFlagBooleanOverride(
SWIPE_CHANGE_VIDEO_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->allowSwipeChangeVideo(Z)Z"
)
}
// endregion
}
}

View File

@@ -30,6 +30,7 @@ val autoCaptionsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -51,6 +51,7 @@ val customBrandingPatch = resourcePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -49,6 +49,7 @@ val changeHeaderPatch = resourcePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
)
)

View File

@@ -30,6 +30,7 @@ val hideButtonsPatch = resourcePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -48,6 +48,7 @@ val navigationButtonsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -62,6 +62,7 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -0,0 +1,75 @@
package app.revanced.patches.youtube.layout.formfactor
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
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.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/ChangeFormFactorPatch;"
@Suppress("unused")
val changeFormFactorPatch = bytecodePatch(
name = "Change form factor",
description = "Adds an option to change the UI appearance to a phone, tablet, or automotive device.",
) {
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.youtube"(
"18.38.44",
"18.49.37",
"19.16.39",
"19.25.37",
"19.34.42",
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)
execute {
addResources("youtube", "layout.formfactor.changeFormFactorPatch")
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
ListPreference(
"revanced_change_form_factor",
summaryKey = null,
)
)
createPlayerRequestBodyWithModelFingerprint.method.apply {
val formFactorEnumClass = formFactorEnumConstructorFingerprint.originalClassDef.type
val index = indexOfFirstInstructionOrThrow {
val reference = getReference<FieldReference>()
opcode == Opcode.IGET &&
reference?.definingClass == formFactorEnumClass &&
reference.type == "I"
}
val register = getInstruction<TwoRegisterInstruction>(index).registerA
addInstructions(
index + 1,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getFormFactor(I)I
move-result v$register
"""
)
}
}
}

View File

@@ -0,0 +1,49 @@
package app.revanced.patches.youtube.layout.formfactor
import app.revanced.patcher.fingerprint
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.FieldReference
internal val formFactorEnumConstructorFingerprint = fingerprint {
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
strings(
"UNKNOWN_FORM_FACTOR",
"SMALL_FORM_FACTOR",
"LARGE_FORM_FACTOR",
"AUTOMOTIVE_FORM_FACTOR"
)
}
internal val createPlayerRequestBodyWithModelFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("L")
parameters()
opcodes(Opcode.OR_INT_LIT16)
custom { method, _ ->
method.indexOfModelInstruction() >= 0 &&
method.indexOfReleaseInstruction() >= 0
}
}
private fun Method.indexOfModelInstruction() =
indexOfFirstInstruction {
val reference = getReference<FieldReference>()
reference?.definingClass == "Landroid/os/Build;" &&
reference.name == "MODEL" &&
reference.type == "Ljava/lang/String;"
}
internal fun Method.indexOfReleaseInstruction(): Int =
indexOfFirstInstruction {
val reference = getReference<FieldReference>()
reference?.definingClass == "Landroid/os/Build${'$'}VERSION;" &&
reference.name == "RELEASE" &&
reference.type == "Ljava/lang/String;"
}

View File

@@ -64,6 +64,7 @@ val hideEndscreenCardsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -37,6 +37,7 @@ val disableFullscreenAmbientModePatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -133,6 +133,7 @@ val hideLayoutComponentsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -65,6 +65,7 @@ val hideInfoCardsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -32,6 +32,7 @@ val hidePlayerFlyoutMenuPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -37,6 +37,7 @@ val disableRollingNumberAnimationPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -33,6 +33,7 @@ val hideSeekbarPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -71,6 +71,7 @@ private val hideShortsComponentsResourcePatch = resourcePatch {
SwitchPreference("revanced_hide_shorts_home"),
SwitchPreference("revanced_hide_shorts_subscriptions"),
SwitchPreference("revanced_hide_shorts_search"),
SwitchPreference("revanced_hide_shorts_history"),
PreferenceScreenPreference(
key = "revanced_shorts_player_screen",
@@ -190,6 +191,7 @@ val hideShortsComponentsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -62,6 +62,7 @@ val disableSuggestedVideoEndScreenPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -29,6 +29,7 @@ val hideTimestampPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -170,6 +170,7 @@ val miniplayerPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -29,6 +29,7 @@ val playerPopupPanelsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -20,6 +20,7 @@ val playerControlsBackgroundPatch = resourcePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -0,0 +1,65 @@
package app.revanced.patches.youtube.layout.player.fullscreen
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playercontrols.playerControlsPatch
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.autoRepeatFingerprint
import app.revanced.patches.youtube.shared.autoRepeatParentFingerprint
@Suppress("unused")
internal val exitFullscreenPatch = bytecodePatch(
name = "Exit fullscreen mode",
description = "Adds options to automatically exit fullscreen mode when a video reaches the end."
) {
compatibleWith(
"com.google.android.youtube"(
"18.38.44",
"18.49.37",
"19.16.39",
"19.25.37",
"19.34.42",
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
)
)
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
playerTypeHookPatch,
playerControlsPatch
)
// Cannot declare as top level since this patch is in the same package as
// other patches that declare same constant name with internal visibility.
@Suppress("LocalVariableName")
val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/ExitFullscreenPatch;"
execute {
addResources("youtube", "layout.player.fullscreen.exitFullscreenPatch")
PreferenceScreen.PLAYER.addPreferences(
ListPreference(
"revanced_exit_fullscreen",
summaryKey = null,
)
)
autoRepeatFingerprint.match(autoRepeatParentFingerprint.originalClassDef).method.addInstruction(
0,
"invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->endOfVideoReached()V",
)
}
}

View File

@@ -26,6 +26,7 @@ val openVideosFullscreenPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.46.42",
"19.47.53",
)
)

View File

@@ -60,6 +60,7 @@ val customPlayerOverlayOpacityPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -61,6 +61,7 @@ val returnYouTubeDislikePatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -37,6 +37,7 @@ val wideSearchbarPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -40,6 +40,7 @@ val shortsAutoplayPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -49,6 +49,7 @@ val openShortsInRegularPlayerPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -119,6 +119,7 @@ val sponsorBlockPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -40,6 +40,7 @@ val spoofAppVersionPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -37,6 +37,7 @@ val changeStartPagePatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -39,6 +39,7 @@ val disableResumingShortsOnStartupPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -1,65 +1,9 @@
package app.revanced.patches.youtube.layout.tablet
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.layout.formfactor.changeFormFactorPatch
const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/TabletLayoutPatch;"
val enableTabletLayoutPatch = bytecodePatch(
name = "Enable tablet layout",
description = "Adds an option to enable tablet layout.",
) {
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.youtube"(
"18.38.44",
"18.49.37",
"19.16.39",
"19.25.37",
"19.34.42",
"19.43.41",
"19.45.38",
"19.46.42",
),
)
execute {
addResources("youtube", "layout.tablet.enableTabletLayoutPatch")
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
SwitchPreference("revanced_tablet_layout"),
)
getFormFactorFingerprint.method.apply {
val returnIsLargeFormFactorIndex = instructions.lastIndex - 4
val returnIsLargeFormFactorLabel = getInstruction(returnIsLargeFormFactorIndex)
addInstructionsWithLabels(
0,
"""
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getTabletLayoutEnabled()Z
move-result v0
if-nez v0, :is_large_form_factor
""",
ExternalLabel(
"is_large_form_factor",
returnIsLargeFormFactorLabel,
),
)
}
}
@Deprecated("Use 'Change form factor' instead.")
val enableTabletLayoutPatch = bytecodePatch {
dependsOn(changeFormFactorPatch)
}

View File

@@ -1,25 +0,0 @@
package app.revanced.patches.youtube.layout.tablet
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.AccessFlags
import app.revanced.patcher.fingerprint
internal val getFormFactorFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("L")
parameters("Landroid/content/Context;", "Ljava/util/List;")
opcodes(
Opcode.SGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_EQZ,
Opcode.SGET_OBJECT,
Opcode.RETURN_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.RETURN_OBJECT,
)
strings("")
}

View File

@@ -163,22 +163,31 @@ val themePatch = bytecodePatch(
}
// Fix the splash screen dark mode background color.
// In earlier versions of the app this is white and makes no sense for dark mode.
// This is only required for 19.32 and greater, but is applied to all targets.
// Only dark mode needs this fix as light mode correctly uses the custom color.
// In 19.32+ the dark mode splash screen is white and fades to black.
// Maybe it's a bug in YT, or maybe it intentionally. Who knows.
document("res/values-night/styles.xml").use { document ->
// Create a night mode specific override for the splash screen background.
val style = document.createElement("style")
style.setAttribute("name", "Theme.YouTube.Home")
style.setAttribute("parent", "@style/Base.V23.Theme.YouTube.Home")
val windowItem = document.createElement("item")
windowItem.setAttribute("name", "android:windowBackground")
windowItem.textContent = "@color/$splashBackgroundColor"
style.appendChild(windowItem)
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
resourcesNode.appendChild(style)
val childNodes = resourcesNode.childNodes
for (i in 0 until childNodes.length) {
val node = childNodes.item(i) as? Element ?: continue
val nodeAttributeName = node.getAttribute("name")
if (nodeAttributeName.startsWith("Theme.YouTube.Launcher")) {
val nodeAttributeParent = node.getAttribute("parent")
val style = document.createElement("style")
style.setAttribute("name", "Theme.YouTube.Home")
style.setAttribute("parent", nodeAttributeParent)
val windowItem = document.createElement("item")
windowItem.setAttribute("name", "android:windowBackground")
windowItem.textContent = "@color/$splashBackgroundColor"
style.appendChild(windowItem)
resourcesNode.removeChild(node)
resourcesNode.appendChild(style)
}
}
}
}
}
@@ -198,6 +207,7 @@ val themePatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -41,6 +41,7 @@ val alternativeThumbnailsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -35,6 +35,7 @@ val bypassImageRegionRestrictionsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -31,6 +31,7 @@ val announcementsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -32,6 +32,7 @@ val autoRepeatPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -56,6 +56,7 @@ val backgroundPlaybackPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -4,7 +4,7 @@ import app.revanced.patches.shared.misc.checks.checkEnvironmentPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
val checkEnvironmentPatch = checkEnvironmentPatch(
internal val checkEnvironmentPatch = checkEnvironmentPatch(
mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint,
extensionPatch = sharedExtensionPatch,
"com.google.android.youtube",

View File

@@ -40,6 +40,7 @@ val enableDebuggingPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -32,6 +32,7 @@ val spoofDeviceDimensionsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
private const val EXTENSION_CLASS_DESCRIPTOR =
@@ -13,7 +14,10 @@ val checkWatchHistoryDomainNameResolutionPatch = bytecodePatch(
name = "Check watch history domain name resolution",
description = "Checks if the device DNS server is preventing user watch history from being saved.",
) {
dependsOn(addResourcesPatch)
dependsOn(
sharedExtensionPatch,
addResourcesPatch
)
compatibleWith(
"com.google.android.youtube"(
@@ -25,6 +29,7 @@ val checkWatchHistoryDomainNameResolutionPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -0,0 +1,61 @@
package app.revanced.patches.youtube.misc.fix.playbackspeed
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/FixPlaybackSpeedWhilePlayingPatch;"
/**
* Fixes a bug in YouTube 19.34+ where the playback speed
* can incorrectly reset to 1.0x under certain conditions.
*
* Reproduction steps using 19.34+
* 1. Open a video and start playback
* 2. Change the speed to any value that is not 1.0x.
* 3. Open the comments panel.
* 4. Tap any "N more replies" link at the bottom of a comment, or tap on a timestamp of a comment.
* 5. Pause the video
* 6. Resume the video
* 7. Playback speed will incorrectly change to 1.0x.
*/
@Suppress("unused")
val fixPlaybackSpeedWhilePlayingPatch = bytecodePatch{
dependsOn(
sharedExtensionPatch,
playerTypeHookPatch,
versionCheckPatch,
)
execute {
if (!is_19_34_or_greater) {
return@execute
}
playbackSpeedInFeedsFingerprint.method.apply {
val freeRegister = implementation!!.registerCount - parameters.size - 2
val playbackSpeedIndex = indexOfGetPlaybackSpeedInstruction(this)
val playbackSpeedRegister = getInstruction<TwoRegisterInstruction>(playbackSpeedIndex).registerA
val returnIndex = indexOfFirstInstructionOrThrow(playbackSpeedIndex, Opcode.RETURN_VOID)
addInstructionsWithLabels(
playbackSpeedIndex + 1,
"""
invoke-static { v$playbackSpeedRegister }, $EXTENSION_CLASS_DESCRIPTOR->playbackSpeedChanged(F)Z
move-result v$freeRegister
if-nez v$freeRegister, :do_not_change
""",
ExternalLabel("do_not_change", getInstruction(returnIndex))
)
}
}
}

View File

@@ -0,0 +1,38 @@
package app.revanced.patches.youtube.misc.fix.playbackspeed
import app.revanced.patcher.fingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversed
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
/**
* This method is usually used to set the initial speed (1.0x) when playback starts from the feed.
* For some reason, in the latest YouTube, it is invoked even after the video has already started.
*/
internal val playbackSpeedInFeedsFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters("L")
opcodes(
Opcode.IGET,
Opcode.MUL_INT_LIT16,
Opcode.IGET_WIDE,
Opcode.CONST_WIDE_16,
Opcode.CMP_LONG,
Opcode.IF_EQZ,
Opcode.IF_LEZ,
Opcode.SUB_LONG_2ADDR,
)
custom { method, _ ->
indexOfGetPlaybackSpeedInstruction(method) >= 0
}
}
internal fun indexOfGetPlaybackSpeedInstruction(method: Method) =
method.indexOfFirstInstructionReversed {
opcode == Opcode.IGET &&
getReference<FieldReference>()?.type == "F"
}

View File

@@ -43,6 +43,7 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)
}

View File

@@ -38,6 +38,7 @@ val bypassURLRedirectsPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -49,6 +49,7 @@ val openLinksExternallyPatch = bytecodePatch(
"19.43.41",
"19.45.38",
"19.46.42",
"19.47.53",
),
)

View File

@@ -105,3 +105,15 @@ internal val pivotBarConstructorFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
strings("com.google.android.apps.youtube.app.endpoint.flags")
}
internal val imageEnumConstructorFingerprint = fingerprint {
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
strings("TAB_ACTIVITY_CAIRO")
}
internal val setEnumMapFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
literal {
ytFillBellId
}
}

View File

@@ -1,6 +1,7 @@
package app.revanced.patches.youtube.misc.navigation
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.patch.PatchException
@@ -12,13 +13,16 @@ import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.playservice.is_19_35_or_greater
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.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.reference.MethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil
@@ -26,6 +30,8 @@ internal var imageOnlyTabResourceId = -1L
private set
internal var actionBarSearchResultsViewMicId = -1L
private set
internal var ytFillBellId = -1L
private set
private val navigationBarHookResourcePatch = resourcePatch {
dependsOn(resourceMappingPatch)
@@ -33,6 +39,7 @@ private val navigationBarHookResourcePatch = resourcePatch {
execute {
imageOnlyTabResourceId = resourceMappings["layout", "image_only_tab"]
actionBarSearchResultsViewMicId = resourceMappings["layout", "action_bar_search_results_view_mic"]
ytFillBellId = resourceMappings["drawable", "yt_fill_bell_black_24"]
}
}
@@ -144,6 +151,36 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
"(${EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR}Landroid/view/View;)V",
)
}
// Fix YT bug of notification tab missing the filled icon.
if (is_19_35_or_greater) {
val cairoNotificationEnumReference = with(imageEnumConstructorFingerprint) {
val stringIndex = stringMatches!!.first().index
val cairoNotificationEnumIndex = method.indexOfFirstInstructionOrThrow(stringIndex) {
opcode == Opcode.SPUT_OBJECT
}
method.getInstruction<ReferenceInstruction>(cairoNotificationEnumIndex).reference
}
setEnumMapFingerprint.method.apply {
val enumMapIndex = indexOfFirstInstructionReversedOrThrow {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_VIRTUAL &&
reference?.definingClass == "Ljava/util/EnumMap;" &&
reference.name == "put" &&
reference.parameterTypes.firstOrNull() == "Ljava/lang/Enum;"
}
val instruction = getInstruction<FiveRegisterInstruction>(enumMapIndex)
addInstructions(
enumMapIndex + 1,
"""
sget-object v${instruction.registerD}, $cairoNotificationEnumReference
invoke-static { v${instruction.registerC}, v${instruction.registerD} }, $EXTENSION_CLASS_DESCRIPTOR->setCairoNotificationFilledIcon(Ljava/util/EnumMap;Ljava/lang/Enum;)V
"""
)
}
}
}
}

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