Compare commits

..

62 Commits

Author SHA1 Message Date
semantic-release-bot
0eeaf7ad67 chore: Release v5.38.0-dev.3 [skip ci]
# [5.38.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.2...v5.38.0-dev.3) (2025-09-16)

### Features

* **YouTube - Spoof video streams:** Add iPadOS client ([2726231](2726231404))
2025-09-16 11:36:54 +00:00
LisoUseInAIKyrios
2726231404 feat(YouTube - Spoof video streams): Add iPadOS client 2025-09-16 15:33:55 +04:00
github-actions[bot]
9f0558e494 chore: Sync translations (#5905) 2025-09-16 15:11:04 +04:00
semantic-release-bot
01f7bc9f8d chore: Release v5.38.0-dev.2 [skip ci]
# [5.38.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.1...v5.38.0-dev.2) (2025-09-16)

### Features

* **YouTube Music:** Add `Settings` patch ([#5838](https://github.com/ReVanced/revanced-patches/issues/5838)) ([5e20bd8](5e20bd80f1))
2025-09-16 06:57:43 +00:00
MarcaD
5e20bd80f1 feat(YouTube Music): Add Settings patch (#5838)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-09-16 10:53:49 +04:00
semantic-release-bot
f304c178e2 chore: Release v5.38.0-dev.1 [skip ci]
# [5.38.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.3...v5.38.0-dev.1) (2025-09-15)

### Features

* **Instagram:** Add `Hide explore feed` patch ([#5856](https://github.com/ReVanced/revanced-patches/issues/5856)) ([1d65887](1d65887e01))
2025-09-15 19:30:52 +00:00
brosssh
1d65887e01 feat(Instagram): Add Hide explore feed patch (#5856) 2025-09-15 23:28:01 +04:00
github-actions[bot]
6b6eea8414 chore: Sync translations (#5864) 2025-09-15 23:26:07 +04:00
semantic-release-bot
1db131e90e chore: Release v5.37.1-dev.3 [skip ci]
## [5.37.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.2...v5.37.1-dev.3) (2025-09-15)

### Bug Fixes

* **Spoof video streams:** Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for `Force original audio` to work with any spoof client ([#5861](https://github.com/ReVanced/revanced-patches/issues/5861)) ([abe3943](abe3943f98))
2025-09-15 17:02:01 +00:00
LisoUseInAIKyrios
abe3943f98 fix(Spoof video streams): Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for Force original audio to work with any spoof client (#5861) 2025-09-15 20:58:56 +04:00
semantic-release-bot
cb6d802de3 chore: Release v5.37.1-dev.2 [skip ci]
## [5.37.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.1...v5.37.1-dev.2) (2025-09-15)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Support v397.1.0.52.81 ([#5855](https://github.com/ReVanced/revanced-patches/issues/5855)) ([f11d1ef](f11d1ef990))
2025-09-15 12:52:54 +00:00
brosssh
f11d1ef990 fix(Instagram - Hide navigation buttons): Support v397.1.0.52.81 (#5855) 2025-09-15 16:48:55 +04:00
semantic-release-bot
3d25da18bc chore: Release v5.37.1-dev.1 [skip ci]
## [5.37.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.37.0...v5.37.1-dev.1) (2025-09-15)

### Bug Fixes

* **YouTube Music - Spoof video streams:** Fix playback issues when using a cellular network ([fa04c8e](fa04c8eecf))
2025-09-15 12:47:02 +00:00
LisoUseInAIKyrios
fa04c8eecf fix(YouTube Music - Spoof video streams): Fix playback issues when using a cellular network
Code adapted from 5f35e51a27
2025-09-15 16:43:04 +04:00
semantic-release-bot
105f6e0e97 chore: Release v5.37.0 [skip ci]
# [5.37.0](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0) (2025-09-15)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](e6c79f1383))
* Resolve patching with dev branch ([09b941a](09b941abf0))
* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](dcd42454bd))
* **Viber - Hide ads:** Add constrain to known working version ([2db0948](2db0948bea))
* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](2a85a3b290))

### Features

* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](0abfab79d7))
2025-09-15 06:45:56 +00:00
LisoUseInAIKyrios
7d59efe05d chore: Merge branch dev to main (#5830) 2025-09-15 10:43:05 +04:00
github-actions[bot]
81ff5576b0 chore: Sync translations (#5854) 2025-09-15 10:41:42 +04:00
semantic-release-bot
9a5c102c0d chore: Release v5.37.0-dev.6 [skip ci]
# [5.37.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.5...v5.37.0-dev.6) (2025-09-15)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](e6c79f1383))
2025-09-15 06:40:30 +00:00
LisoUseInAIKyrios
e6c79f1383 fix(Instagram - Hide navigation buttons): Add constrain to known working version 2025-09-15 10:36:57 +04:00
semantic-release-bot
2a582eced8 chore: Release v5.37.0-dev.5 [skip ci]
# [5.37.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.4...v5.37.0-dev.5) (2025-09-15)

### Bug Fixes

* **Viber - Hide ads:** Add constrain to known working version ([2db0948](2db0948bea))
2025-09-15 06:29:31 +00:00
LisoUseInAIKyrios
2db0948bea fix(Viber - Hide ads): Add constrain to known working version 2025-09-15 10:26:30 +04:00
semantic-release-bot
a3ba92e742 chore: Release v5.37.0-dev.4 [skip ci]
# [5.37.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.3...v5.37.0-dev.4) (2025-09-14)

### Bug Fixes

* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](2a85a3b290))
2025-09-14 18:22:57 +00:00
LisoUseInAIKyrios
2a85a3b290 fix(YouTube Music - Spoof streaming data): Fix audio playback stuttering (#5839) 2025-09-14 22:19:13 +04:00
semantic-release-bot
eee72208dd chore: Release v5.37.0-dev.3 [skip ci]
# [5.37.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.2...v5.37.0-dev.3) (2025-09-14)

### Bug Fixes

* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](dcd42454bd))
2025-09-14 17:15:28 +00:00
LisoUseInAIKyrios
dcd42454bd fix(Spotify): Remove broken Spoof client patch (#5833) 2025-09-14 21:11:15 +04:00
LisoUseInAIKyrios
782353c18a refactor(Spoof video streams): Handle migration of default spoof client for users upgrading from very old patches 2025-09-14 18:06:40 +04:00
semantic-release-bot
b53b870e8f chore: Release v5.37.0-dev.2 [skip ci]
# [5.37.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.1...v5.37.0-dev.2) (2025-09-14)

### Bug Fixes

* Resolve patching with dev branch ([09b941a](09b941abf0))
2025-09-14 12:00:38 +00:00
LisoUseInAIKyrios
09b941abf0 fix: Resolve patching with dev branch 2025-09-14 15:58:05 +04:00
semantic-release-bot
678ef4052e chore: Release v5.37.0-dev.1 [skip ci]
# [5.37.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0-dev.1) (2025-09-14)

### Features

* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](0abfab79d7))
2025-09-14 11:52:21 +00:00
Samo Hribar
0abfab79d7 feat(Viber): Add Hide ads patch (#5826) 2025-09-14 15:49:52 +04:00
LisoUseInAIKyrios
61cadf72cd refactor(Spoof video streams): Back port code from v22 branch to support patching the latest YT Music. Using any target above 7.49.52 is untested and only recommended for experimental or development purposes. 2025-09-14 15:49:35 +04:00
github-actions[bot]
e12359b94f chore: Sync translations (#5829) 2025-09-14 15:46:32 +04:00
semantic-release-bot
c001daba4a chore: Release v5.36.0 [skip ci]
# [5.36.0](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0) (2025-09-14)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](a53b00dd51))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-14 06:56:22 +00:00
LisoUseInAIKyrios
e136f62d6e chore: Merge branch dev to main (#5800) 2025-09-14 10:53:28 +04:00
semantic-release-bot
8ec405a359 chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](a53b00dd51))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-13 15:31:54 +00:00
github-actions[bot]
2f4b3a887b chore: Sync translations (#5821) 2025-09-13 19:28:15 +04:00
semantic-release-bot
d1fabb242b chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](a53b00dd51))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-13 15:15:40 +00:00
LisoUseInAIKyrios
a53b00dd51 fix(YouTube Music): Resolve playback issues, change recommended app target to 7.29.52 (#5813) 2025-09-13 19:12:00 +04:00
semantic-release-bot
850c13e98e chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-13 07:00:16 +00:00
LisoUseInAIKyrios
4310789a26 chore: Fix api 2025-09-13 10:56:43 +04:00
semantic-release-bot
c4a720fbd3 chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-12)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-12 15:08:03 +00:00
LisoUseInAIKyrios
3bdb8dbce0 chore(YouTube - SponsorBlock): Adjust strings for consistency / clarity
Strings taken from https://github.com/ajayyy/ExtensionTranslations/blob/master/en/messages.json
2025-09-12 18:49:34 +04:00
LisoUseInAIKyrios
4894f33c96 chore: fix compilation 2025-09-12 18:49:33 +04:00
semantic-release-bot
7f6093ee66 chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.1-dev.1...v5.36.0-dev.1) (2025-09-12)

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-12 01:59:47 +00:00
LisoUseInAIKyrios
9d4aa5cd16 feat(YouTube - SponsorBlock): Add 'Hook' segment category (#5783) 2025-09-12 05:56:50 +04:00
oSumAtrIX
5ace6f587c chore: Add ads.fund verification file [skip ci] (#5786) 2025-09-11 16:00:24 +02:00
semantic-release-bot
796f56745e chore: Release v5.35.1-dev.1 [skip ci]
## [5.35.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.35.1-dev.1) (2025-09-11)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
2025-09-11 01:29:16 +00:00
hoodles
88b47ef414 fix(Duolingo - Disable ads): Support latest app target (#5782) 2025-09-11 03:26:28 +02:00
LisoUseInAIKyrios
8cd8e59bbc fix(YouTube - Hide layout components): Hide new type of Playable shelf 2025-09-11 03:24:09 +02:00
LisoUseInAIKyrios
6e72b14d07 refactor(YouTube - Video Quality): Handle extremely slow internet connections that initially can use -1 quality index 2025-09-11 02:58:33 +02:00
LisoUseInAIKyrios
52b088327b chore: Fix api dump 2025-09-10 21:58:27 +02:00
semantic-release-bot
8e934cc56b chore: Release v5.35.0 [skip ci]
# [5.35.0](https://github.com/ReVanced/revanced-patches/compare/v5.34.0...v5.35.0) (2025-09-09)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0a8cd7a](0a8cd7a7db))
* **Proton mail:** Constrain patches to last working app target ([1895291](189529151a))
* Revert dependency updates to fix Manager pre-release patching ([9256aa4](9256aa4548))
* **Spotify - Unlock Premium:** Make compatible with latest versions again by fixing fingerprint ([#5684](https://github.com/ReVanced/revanced-patches/issues/5684)) ([23496c7](23496c7c36))
* **YouTube - Hide layout components:** Hide Playable shelf header ([1473db0](1473db0bef))

### Features

* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([144af2f](144af2f07e))
* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch  ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c66c42e](c66c42e946))
* **Instagram:** Add `Hide navigation buttons` patch ([#5678](https://github.com/ReVanced/revanced-patches/issues/5678)) ([1dbc2d4](1dbc2d4057))
* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([b8629aa](b8629aacb6))
2025-09-09 19:38:18 +00:00
LisoUseInAIKyrios
b3140d909b chore: Merge branch dev to main (#5691) 2025-09-09 21:34:30 +02:00
github-actions[bot]
97645aa9f4 chore: Sync translations (#5777) 2025-09-09 21:32:56 +02:00
semantic-release-bot
603e2d018c chore: Release v5.35.0-dev.5 [skip ci]
# [5.35.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.4...v5.35.0-dev.5) (2025-09-06)

### Features

* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([144af2f](144af2f07e))
* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([b8629aa](b8629aacb6))
2025-09-06 10:56:54 +00:00
Eric Ahn
144af2f07e feat(BaconReader): Add Fix Redgifs API patch (#5761) 2025-09-06 12:53:26 +02:00
PainfulPaladins
b8629aacb6 feat(Instagram): Add Hide Stories from Home patch (#5756) 2025-09-06 12:53:08 +02:00
github-actions[bot]
3951527f51 chore: Sync translations (#5768) 2025-09-06 12:52:48 +02:00
semantic-release-bot
7a8b618c4e chore: Release v5.35.0-dev.4 [skip ci]
# [5.35.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.3...v5.35.0-dev.4) (2025-09-04)

### Features

* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch  ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c66c42e](c66c42e946))
2025-09-04 21:33:33 +00:00
Eric Ahn
c66c42e946 feat(Boost/Sync for Reddit): Add Fix Redgifs patch (#5725) 2025-09-04 23:29:58 +02:00
semantic-release-bot
b340769cf3 chore: Release v5.35.0-dev.3 [skip ci]
# [5.35.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.2...v5.35.0-dev.3) (2025-09-04)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0a8cd7a](0a8cd7a7db))
2025-09-04 14:06:03 +00:00
LisoUseInAIKyrios
0a8cd7a7db fix(Instagram - Hide navigation buttons): Fix Manager patching error 2025-09-04 16:01:50 +02:00
193 changed files with 7020 additions and 3196 deletions

View File

@@ -1,3 +1,227 @@
# [5.38.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.2...v5.38.0-dev.3) (2025-09-16)
### Features
* **YouTube - Spoof video streams:** Add iPadOS client ([2726231](https://github.com/ReVanced/revanced-patches/commit/2726231404384d87f101d825e10a17c944e8f1bd))
# [5.38.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.1...v5.38.0-dev.2) (2025-09-16)
### Features
* **YouTube Music:** Add `Settings` patch ([#5838](https://github.com/ReVanced/revanced-patches/issues/5838)) ([5e20bd8](https://github.com/ReVanced/revanced-patches/commit/5e20bd80f138d7ca94f18857194c46e489c435dc))
# [5.38.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.3...v5.38.0-dev.1) (2025-09-15)
### Features
* **Instagram:** Add `Hide explore feed` patch ([#5856](https://github.com/ReVanced/revanced-patches/issues/5856)) ([1d65887](https://github.com/ReVanced/revanced-patches/commit/1d65887e015a067196f5a84db486fff355c96596))
## [5.37.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.2...v5.37.1-dev.3) (2025-09-15)
### Bug Fixes
* **Spoof video streams:** Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for `Force original audio` to work with any spoof client ([#5861](https://github.com/ReVanced/revanced-patches/issues/5861)) ([abe3943](https://github.com/ReVanced/revanced-patches/commit/abe3943f98fd86dcd74c7e07cf65d3c7fc24fef9))
## [5.37.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.1...v5.37.1-dev.2) (2025-09-15)
### Bug Fixes
* **Instagram - Hide navigation buttons:** Support v397.1.0.52.81 ([#5855](https://github.com/ReVanced/revanced-patches/issues/5855)) ([f11d1ef](https://github.com/ReVanced/revanced-patches/commit/f11d1ef9907082512f139d4ab0e2e9f707de7e48))
## [5.37.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.37.0...v5.37.1-dev.1) (2025-09-15)
### Bug Fixes
* **YouTube Music - Spoof video streams:** Fix playback issues when using a cellular network ([fa04c8e](https://github.com/ReVanced/revanced-patches/commit/fa04c8eecfbdd0b6ed082b464ca9032536d71762))
# [5.37.0](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0) (2025-09-15)
### Bug Fixes
* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](https://github.com/ReVanced/revanced-patches/commit/e6c79f13834c83fef04e4dee5e628cb0b9a27765))
* Resolve patching with dev branch ([09b941a](https://github.com/ReVanced/revanced-patches/commit/09b941abf0e8029999565082b02a88b5de507ec4))
* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](https://github.com/ReVanced/revanced-patches/commit/dcd42454bd5f87dddd720534f6120c4ef90063a3))
* **Viber - Hide ads:** Add constrain to known working version ([2db0948](https://github.com/ReVanced/revanced-patches/commit/2db0948beaf2b68391a1fe7f21e92d31c7df61e7))
* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](https://github.com/ReVanced/revanced-patches/commit/2a85a3b29092729ae16d1fd93803634ce5f08e95))
### Features
* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](https://github.com/ReVanced/revanced-patches/commit/0abfab79d7cda15bf17c53679fbfffb021662649))
# [5.37.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.5...v5.37.0-dev.6) (2025-09-15)
### Bug Fixes
* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](https://github.com/ReVanced/revanced-patches/commit/e6c79f13834c83fef04e4dee5e628cb0b9a27765))
# [5.37.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.4...v5.37.0-dev.5) (2025-09-15)
### Bug Fixes
* **Viber - Hide ads:** Add constrain to known working version ([2db0948](https://github.com/ReVanced/revanced-patches/commit/2db0948beaf2b68391a1fe7f21e92d31c7df61e7))
# [5.37.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.3...v5.37.0-dev.4) (2025-09-14)
### Bug Fixes
* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](https://github.com/ReVanced/revanced-patches/commit/2a85a3b29092729ae16d1fd93803634ce5f08e95))
# [5.37.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.2...v5.37.0-dev.3) (2025-09-14)
### Bug Fixes
* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](https://github.com/ReVanced/revanced-patches/commit/dcd42454bd5f87dddd720534f6120c4ef90063a3))
# [5.37.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.1...v5.37.0-dev.2) (2025-09-14)
### Bug Fixes
* Resolve patching with dev branch ([09b941a](https://github.com/ReVanced/revanced-patches/commit/09b941abf0e8029999565082b02a88b5de507ec4))
# [5.37.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0-dev.1) (2025-09-14)
### Features
* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](https://github.com/ReVanced/revanced-patches/commit/0abfab79d7cda15bf17c53679fbfffb021662649))
# [5.36.0](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0) (2025-09-14)
### Bug Fixes
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](https://github.com/ReVanced/revanced-patches/commit/a53b00dd514dbe2b3406f3c1013a4f58a7f481c5))
### Features
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe))
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)
### Bug Fixes
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](https://github.com/ReVanced/revanced-patches/commit/a53b00dd514dbe2b3406f3c1013a4f58a7f481c5))
### Features
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe))
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)
### Bug Fixes
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](https://github.com/ReVanced/revanced-patches/commit/a53b00dd514dbe2b3406f3c1013a4f58a7f481c5))
### Features
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe))
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)
### Bug Fixes
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0))
### Features
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe))
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-12)
### Bug Fixes
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0))
### Features
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe))
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.1-dev.1...v5.36.0-dev.1) (2025-09-12)
### Features
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([2e042c4](https://github.com/ReVanced/revanced-patches/commit/2e042c4b3366fa3daf991d5560fcae991d00ad12))
## [5.35.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.35.1-dev.1) (2025-09-11)
### Bug Fixes
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([8491516](https://github.com/ReVanced/revanced-patches/commit/849151637389b8f399356d0d331bb74482f3f05d))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([3af4126](https://github.com/ReVanced/revanced-patches/commit/3af41265338ddaab52d009f53370c57abddd4599))
# [5.35.0](https://github.com/ReVanced/revanced-patches/compare/v5.34.0...v5.35.0) (2025-09-09)
### Bug Fixes
* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0d10e94](https://github.com/ReVanced/revanced-patches/commit/0d10e94663283fac09f3efc57c9b9805c38c4e13))
* **Proton mail:** Constrain patches to last working app target ([21c34b9](https://github.com/ReVanced/revanced-patches/commit/21c34b908e07a97de8c31c7c828b44a8cc4739b6))
* Revert dependency updates to fix Manager pre-release patching ([4c7a1a8](https://github.com/ReVanced/revanced-patches/commit/4c7a1a8554c67797bf663e5230f566c5a9b229af))
* **Spotify - Unlock Premium:** Make compatible with latest versions again by fixing fingerprint ([#5684](https://github.com/ReVanced/revanced-patches/issues/5684)) ([30dcff1](https://github.com/ReVanced/revanced-patches/commit/30dcff13a56883efc499b71faadb403877cd1c67))
* **YouTube - Hide layout components:** Hide Playable shelf header ([fbb5046](https://github.com/ReVanced/revanced-patches/commit/fbb50463f0e3f533a278c5251cfbce59f09ce641))
### Features
* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([08868c0](https://github.com/ReVanced/revanced-patches/commit/08868c00d3c4f1f37f4a77f333a03ca5a3259b59))
* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c5e8079](https://github.com/ReVanced/revanced-patches/commit/c5e8079eab08075a72078cd0fa79f3beb1f75d98))
* **Instagram:** Add `Hide navigation buttons` patch ([#5678](https://github.com/ReVanced/revanced-patches/issues/5678)) ([415cf0f](https://github.com/ReVanced/revanced-patches/commit/415cf0fb5b9b3dcaf4592943a69eea1c10447b07))
* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([3ae3251](https://github.com/ReVanced/revanced-patches/commit/3ae3251dc0317b6ced136fe9aa14be369642f203))
# [5.35.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.4...v5.35.0-dev.5) (2025-09-06)
### Features
* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([08868c0](https://github.com/ReVanced/revanced-patches/commit/08868c00d3c4f1f37f4a77f333a03ca5a3259b59))
* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([3ae3251](https://github.com/ReVanced/revanced-patches/commit/3ae3251dc0317b6ced136fe9aa14be369642f203))
# [5.35.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.3...v5.35.0-dev.4) (2025-09-04)
### Features
* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c5e8079](https://github.com/ReVanced/revanced-patches/commit/c5e8079eab08075a72078cd0fa79f3beb1f75d98))
# [5.35.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.2...v5.35.0-dev.3) (2025-09-04)
### Bug Fixes
* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0d10e94](https://github.com/ReVanced/revanced-patches/commit/0d10e94663283fac09f3efc57c9b9805c38c4e13))
# [5.35.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.1...v5.35.0-dev.2) (2025-09-04)

8
adsfund.json Normal file
View File

@@ -0,0 +1,8 @@
{
"info": "This is verification file for ads.fund project",
"project": {
"name": "Revanced Patches",
"walletAddress": "0x7ab4091e00363654bf84B34151225742cd92FCE5",
"tokenAddress": "0xadf325f255083a3f3d9a9d01ffb3db52a148d802"
}
}

View File

@@ -0,0 +1,5 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,22 @@
package app.revanced.extension.baconreader;
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
import okhttp3.OkHttpClient;
/**
* @noinspection unused
*/
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
static {
INSTANCE = new FixRedgifsApiPatch();
}
public String getDefaultUserAgent() {
// BaconReader uses a static user agent for Redgifs API calls
return "BaconReader";
}
public static OkHttpClient install(OkHttpClient.Builder builder) {
return builder.addInterceptor(INSTANCE).build();
}
}

View File

@@ -1,4 +1,6 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:boostforreddit:stub"))
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -0,0 +1,22 @@
package app.revanced.extension.boostforreddit;
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
import okhttp3.OkHttpClient;
/**
* @noinspection unused
*/
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
static {
INSTANCE = new FixRedgifsApiPatch();
}
public String getDefaultUserAgent() {
// Boost uses a static user agent for Redgifs API calls
return "Boost";
}
public static OkHttpClient createClient() {
return new OkHttpClient.Builder().addInterceptor(INSTANCE).build();
}
}

View File

@@ -1,3 +1,9 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:youtube:stub"))
compileOnly(libs.annotation)
}
android {
defaultConfig {
minSdk = 26

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideCategoryBarPatch {
/**
* Injection point
*/
public static boolean hideCategoryBar() {
return Settings.HIDE_CATEGORY_BAR.get();
}
}

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideGetPremiumPatch {
/**
* Injection point
*/
public static boolean hideGetPremiumLabel() {
return Settings.HIDE_GET_PREMIUM_LABEL.get();
}
}

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideUpgradeButtonPatch {
/**
* Injection point
*/
public static boolean hideUpgradeButton() {
return Settings.HIDE_UPGRADE_BUTTON.get();
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideVideoAdsPatch {
/**
* Injection point
*/
public static boolean showVideoAds(boolean original) {
if (Settings.HIDE_VIDEO_ADS.get()) {
return false;
}
return original;
}
}

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class PermanentRepeatPatch {
/**
* Injection point
*/
public static boolean permanentRepeat() {
return Settings.PERMANENT_REPEAT.get();
}
}

View File

@@ -0,0 +1,27 @@
package app.revanced.extension.music.patches.spoof;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
import java.util.List;
import app.revanced.extension.shared.spoof.ClientType;
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
@SuppressWarnings("unused")
public class SpoofVideoStreamsPatch {
/**
* Injection point.
*/
public static void setClientOrderToUse() {
List<ClientType> availableClients = List.of(
ANDROID_VR_1_43_32,
ANDROID_VR_1_61_48,
VISIONOS
);
StreamingDataRequest.setClientOrderToUse(availableClients, ANDROID_VR_1_43_32);
}
}

View File

@@ -0,0 +1,84 @@
package app.revanced.extension.music.settings;
import android.app.Activity;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceFragment;
import android.view.View;
import app.revanced.extension.music.settings.preference.ReVancedPreferenceFragment;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook;
/**
* Hooks GoogleApiActivity to inject a custom ReVancedPreferenceFragment with a toolbar.
*/
public class GoogleApiActivityHook extends BaseActivityHook {
/**
* Injection point
* <p>
* Creates an instance of GoogleApiActivityHook for use in static initialization.
*/
@SuppressWarnings("unused")
public static GoogleApiActivityHook createInstance() {
// Must touch the Music settings to ensure the class is loaded and
// the values can be found when setting the UI preferences.
// Logging anything under non debug ensures this is set.
Logger.printInfo(() -> "Permanent repeat enabled: " + Settings.PERMANENT_REPEAT.get());
return new GoogleApiActivityHook();
}
/**
* Sets the fixed theme for the activity.
*/
@Override
protected void customizeActivityTheme(Activity activity) {
// Override the default YouTube Music theme to increase start padding of list items.
// Custom style located in resources/music/values/style.xml
activity.setTheme(Utils.getResourceIdentifier("Theme.ReVanced.YouTubeMusic.Settings", "style"));
}
/**
* Returns the resource ID for the YouTube Music settings layout.
*/
@Override
protected int getContentViewResourceId() {
return Utils.getResourceIdentifier("revanced_music_settings_with_toolbar", "layout");
}
/**
* Returns the fixed background color for the toolbar.
*/
@Override
protected int getToolbarBackgroundColor() {
return Utils.getResourceColor("ytm_color_black");
}
/**
* Returns the navigation icon with a color filter applied.
*/
@Override
protected Drawable getNavigationIcon() {
Drawable navigationIcon = ReVancedPreferenceFragment.getBackButtonDrawable();
navigationIcon.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
return navigationIcon;
}
/**
* Returns the click listener that finishes the activity when the navigation icon is clicked.
*/
@Override
protected View.OnClickListener getNavigationClickListener(Activity activity) {
return view -> activity.finish();
}
/**
* Creates a new ReVancedPreferenceFragment for the activity.
*/
@Override
protected PreferenceFragment createPreferenceFragment() {
return new ReVancedPreferenceFragment();
}
}

View File

@@ -0,0 +1,21 @@
package app.revanced.extension.music.settings;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting;
public class Settings extends BaseSettings {
// Ads
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);
public static final BooleanSetting HIDE_GET_PREMIUM_LABEL = new BooleanSetting("revanced_music_hide_get_premium_label", TRUE, true);
public static final BooleanSetting HIDE_UPGRADE_BUTTON = new BooleanSetting("revanced_music_hide_upgrade_button", TRUE, true);
// General
public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_music_hide_category_bar", FALSE, true);
// Player
public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("revanced_music_play_permanent_repeat", FALSE, true);
}

View File

@@ -0,0 +1,38 @@
package app.revanced.extension.music.settings.preference;
import android.widget.Toolbar;
import app.revanced.extension.music.settings.GoogleApiActivityHook;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
/**
* Preference fragment for ReVanced settings.
*/
@SuppressWarnings({"deprecation", "NewApi"})
public class ReVancedPreferenceFragment extends ToolbarPreferenceFragment {
/**
* Initializes the preference fragment.
*/
@Override
protected void initialize() {
super.initialize();
try {
Utils.sortPreferenceGroups(getPreferenceScreen());
setPreferenceScreenToolbar(getPreferenceScreen());
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* Sets toolbar for all nested preference screens.
*/
@Override
protected void customizeToolbar(Toolbar toolbar) {
GoogleApiActivityHook.setToolbarLayoutParams(toolbar);
}
}

View File

@@ -1,27 +0,0 @@
package app.revanced.extension.music.spoof;
/**
* @noinspection unused
*/
public class SpoofClientPatch {
private static final int CLIENT_TYPE_ID = 26;
private static final String CLIENT_VERSION = "6.21";
private static final String DEVICE_MODEL = "iPhone16,2";
private static final String OS_VERSION = "17.7.2.21H221";
public static int getClientId() {
return CLIENT_TYPE_ID;
}
public static String getClientVersion() {
return CLIENT_VERSION;
}
public static String getClientModel() {
return DEVICE_MODEL;
}
public static String getOsVersion() {
return OS_VERSION;
}
}

View File

@@ -1,3 +1,4 @@
dependencies {
implementation(project(":extensions:shared:library"))
compileOnly(libs.okhttp)
}

View File

@@ -18,4 +18,5 @@ android {
dependencies {
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -0,0 +1,71 @@
package app.revanced.extension.shared.fixes.redgifs;
import androidx.annotation.NonNull;
import org.json.JSONException;
import java.io.IOException;
import java.net.HttpURLConnection;
import app.revanced.extension.shared.Logger;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public abstract class BaseFixRedgifsApiPatch implements Interceptor {
protected static BaseFixRedgifsApiPatch INSTANCE;
public abstract String getDefaultUserAgent();
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request();
if (!request.url().host().equals("api.redgifs.com")) {
return chain.proceed(request);
}
String userAgent = getDefaultUserAgent();
if (request.header("Authorization") != null) {
Response response = chain.proceed(request.newBuilder().header("User-Agent", userAgent).build());
if (response.isSuccessful()) {
return response;
}
// It's possible that the user agent is being overwritten later down in the interceptor
// chain, so make sure we grab the new user agent from the request headers.
userAgent = response.request().header("User-Agent");
response.close();
}
try {
RedgifsTokenManager.RedgifsToken token = RedgifsTokenManager.refreshToken(userAgent);
// Emulate response for old OAuth endpoint
if (request.url().encodedPath().equals("/v2/oauth/client")) {
String responseBody = RedgifsTokenManager.getEmulatedOAuthResponseBody(token);
return new Response.Builder()
.message("OK")
.code(HttpURLConnection.HTTP_OK)
.protocol(Protocol.HTTP_1_1)
.request(request)
.header("Content-Type", "application/json")
.body(ResponseBody.create(
responseBody, MediaType.get("application/json")))
.build();
}
Request modifiedRequest = request.newBuilder()
.header("Authorization", "Bearer " + token.getAccessToken())
.header("User-Agent", userAgent)
.build();
return chain.proceed(modifiedRequest);
} catch (JSONException ex) {
Logger.printException(() -> "Could not parse Redgifs response", ex);
throw new IOException(ex);
}
}
}

View File

@@ -0,0 +1,94 @@
package app.revanced.extension.shared.fixes.redgifs;
import static app.revanced.extension.shared.requests.Route.Method.GET;
import androidx.annotation.GuardedBy;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import app.revanced.extension.shared.requests.Requester;
/**
* Manages Redgifs token lifecycle.
*/
public class RedgifsTokenManager {
public static class RedgifsToken {
// Expire after 23 hours to provide some breathing room
private static final long EXPIRY_SECONDS = 23 * 60 * 60;
private final String accessToken;
private final long refreshTimeInSeconds;
public RedgifsToken(String accessToken, long refreshTime) {
this.accessToken = accessToken;
this.refreshTimeInSeconds = refreshTime;
}
public String getAccessToken() {
return accessToken;
}
public long getExpiryTimeInSeconds() {
return refreshTimeInSeconds + EXPIRY_SECONDS;
}
public boolean isValid() {
if (accessToken == null) return false;
return getExpiryTimeInSeconds() >= System.currentTimeMillis() / 1000;
}
}
public static final String REDGIFS_API_HOST = "https://api.redgifs.com";
private static final String GET_TEMPORARY_TOKEN = REDGIFS_API_HOST + "/v2/auth/temporary";
@GuardedBy("itself")
private static final Map<String, RedgifsToken> tokenMap = new HashMap<>();
private static String getToken(String userAgent) throws IOException, JSONException {
HttpURLConnection connection = (HttpURLConnection) new URL(GET_TEMPORARY_TOKEN).openConnection();
connection.setFixedLengthStreamingMode(0);
connection.setRequestMethod(GET.name());
connection.setRequestProperty("User-Agent", userAgent);
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.setUseCaches(false);
JSONObject responseObject = Requester.parseJSONObject(connection);
return responseObject.getString("token");
}
public static RedgifsToken refreshToken(String userAgent) throws IOException, JSONException {
synchronized(tokenMap) {
// Reference: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/pull/67
RedgifsToken token = tokenMap.get(userAgent);
if (token != null && token.isValid()) {
return token;
}
// Copy user agent from original request if present because Redgifs verifies
// that the user agent in subsequent requests matches the one in the OAuth token.
String accessToken = getToken(userAgent);
long refreshTime = System.currentTimeMillis() / 1000;
token = new RedgifsToken(accessToken, refreshTime);
tokenMap.put(userAgent, token);
return token;
}
}
public static String getEmulatedOAuthResponseBody(RedgifsToken token) throws JSONException {
// Reference: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/pull/67
JSONObject responseObject = new JSONObject();
responseObject.put("access_token", token.accessToken);
responseObject.put("expiry_time", token.getExpiryTimeInSeconds() - (System.currentTimeMillis() / 1000));
responseObject.put("scope", "read");
responseObject.put("token_type", "Bearer");
return responseObject.toString();
}
}

View File

@@ -0,0 +1,142 @@
package app.revanced.extension.shared.settings;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceFragment;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toolbar;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
/**
* Base class for hooking activities to inject a custom PreferenceFragment with a toolbar.
* Provides common logic for initializing the activity and setting up the toolbar.
*/
@SuppressWarnings({"deprecation", "NewApi"})
public abstract class BaseActivityHook extends Activity {
/**
* Layout parameters for the toolbar, extracted from the dummy toolbar.
*/
protected static ViewGroup.LayoutParams toolbarLayoutParams;
/**
* Sets the layout parameters for the toolbar.
*/
public static void setToolbarLayoutParams(Toolbar toolbar) {
if (toolbarLayoutParams != null) {
toolbar.setLayoutParams(toolbarLayoutParams);
}
}
/**
* Initializes the activity by setting the theme, content view and injecting a PreferenceFragment.
*/
public static void initialize(BaseActivityHook hook, Activity activity) {
try {
hook.customizeActivityTheme(activity);
activity.setContentView(hook.getContentViewResourceId());
// Sanity check.
String dataString = activity.getIntent().getDataString();
if (!"revanced_settings_intent".equals(dataString)) {
Logger.printException(() -> "Unknown intent: " + dataString);
return;
}
PreferenceFragment fragment = hook.createPreferenceFragment();
hook.createToolbar(activity, fragment);
activity.getFragmentManager()
.beginTransaction()
.replace(Utils.getResourceIdentifier("revanced_settings_fragments", "id"), fragment)
.commit();
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* Creates and configures a toolbar for the activity, replacing a dummy placeholder.
*/
@SuppressLint("UseCompatLoadingForDrawables")
protected void createToolbar(Activity activity, PreferenceFragment fragment) {
// Replace dummy placeholder toolbar.
// This is required to fix submenu title alignment issue with Android ASOP 15+
ViewGroup toolBarParent = activity.findViewById(
Utils.getResourceIdentifier("revanced_toolbar_parent", "id"));
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar");
toolbarLayoutParams = dummyToolbar.getLayoutParams();
toolBarParent.removeView(dummyToolbar);
// Sets appropriate system navigation bar color for the activity.
ToolbarPreferenceFragment.setNavigationBarColor(activity.getWindow());
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
toolbar.setBackgroundColor(getToolbarBackgroundColor());
toolbar.setNavigationIcon(getNavigationIcon());
toolbar.setNavigationOnClickListener(getNavigationClickListener(activity));
toolbar.setTitle(Utils.getResourceIdentifier("revanced_settings_title", "string"));
final int margin = Utils.dipToPixels(16);
toolbar.setTitleMarginStart(margin);
toolbar.setTitleMarginEnd(margin);
TextView toolbarTextView = Utils.getChildView(toolbar, false, view -> view instanceof TextView);
if (toolbarTextView != null) {
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
}
setToolbarLayoutParams(toolbar);
onPostToolbarSetup(activity, toolbar, fragment);
toolBarParent.addView(toolbar, 0);
}
/**
* Customizes the activity's theme.
*/
protected abstract void customizeActivityTheme(Activity activity);
/**
* Returns the resource ID for the content view layout.
*/
protected abstract int getContentViewResourceId();
/**
* Returns the background color for the toolbar.
*/
protected abstract int getToolbarBackgroundColor();
/**
* Returns the navigation icon drawable for the toolbar.
*/
protected abstract Drawable getNavigationIcon();
/**
* Returns the click listener for the toolbar's navigation icon.
*/
protected abstract View.OnClickListener getNavigationClickListener(Activity activity);
/**
* Creates the PreferenceFragment to be injected into the activity.
*/
protected PreferenceFragment createPreferenceFragment() {
return new ToolbarPreferenceFragment();
}
/**
* Performs additional setup after the toolbar is configured.
*
* @param activity The activity hosting the toolbar.
* @param toolbar The configured toolbar.
* @param fragment The PreferenceFragment associated with the activity.
*/
protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) {}
}

View File

@@ -4,8 +4,8 @@ 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.Logger;
import app.revanced.extension.shared.spoof.ClientType;
/**
@@ -31,9 +31,13 @@ public class BaseSettings {
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<AppLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.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_UNPLUGGED, true, parent(SPOOF_VIDEO_STREAMS));
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_61_48, true, parent(SPOOF_VIDEO_STREAMS));
static {
if (SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED) {
Logger.printInfo(() -> "Migrating from iOS Unplugged to iPadOS");
SPOOF_VIDEO_STREAMS_CLIENT_TYPE.save(ClientType.IPADOS);
}
}
}

View File

@@ -1,9 +1,8 @@
package app.revanced.extension.youtube.settings.preference;
package app.revanced.extension.shared.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.preference.Preference;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* A custom preference that clears the ReVanced debug log buffer when clicked.

View File

@@ -1,9 +1,8 @@
package app.revanced.extension.youtube.settings.preference;
package app.revanced.extension.shared.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.preference.Preference;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* A custom preference that triggers exporting ReVanced debug logs to the clipboard when clicked.

View File

@@ -0,0 +1,150 @@
package app.revanced.extension.shared.settings.preference;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.graphics.Insets;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook;
@SuppressWarnings({"deprecation", "NewApi"})
public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
/**
* Sets toolbar for all nested preference screens.
*/
protected void setPreferenceScreenToolbar(PreferenceScreen parentScreen) {
for (int i = 0, count = parentScreen.getPreferenceCount(); i < count; i++) {
Preference childPreference = parentScreen.getPreference(i);
if (childPreference instanceof PreferenceScreen) {
// Recursively set sub preferences.
setPreferenceScreenToolbar((PreferenceScreen) childPreference);
childPreference.setOnPreferenceClickListener(
childScreen -> {
Dialog preferenceScreenDialog = ((PreferenceScreen) childScreen).getDialog();
ViewGroup rootView = (ViewGroup) preferenceScreenDialog
.findViewById(android.R.id.content)
.getParent();
// Allow package-specific background customization.
customizeDialogBackground(rootView);
// Fix the system navigation bar color for submenus.
setNavigationBarColor(preferenceScreenDialog.getWindow());
// Fix edge-to-edge screen with Android 15 and YT 19.45+
// https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars());
Insets cutoutInsets = insets.getInsets(WindowInsets.Type.displayCutout());
// Apply padding for display cutout in landscape.
int leftPadding = cutoutInsets.left;
int rightPadding = cutoutInsets.right;
int topPadding = statusInsets.top;
int bottomPadding = navInsets.bottom;
v.setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
return insets;
});
}
Toolbar toolbar = new Toolbar(childScreen.getContext());
toolbar.setTitle(childScreen.getTitle());
toolbar.setNavigationIcon(getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
final int margin = Utils.dipToPixels(16);
toolbar.setTitleMargin(margin, 0, margin, 0);
TextView toolbarTextView = Utils.getChildView(toolbar,
true, TextView.class::isInstance);
if (toolbarTextView != null) {
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
}
// Allow package-specific toolbar customization.
customizeToolbar(toolbar);
// Allow package-specific post-toolbar setup.
onPostToolbarSetup(toolbar, preferenceScreenDialog);
rootView.addView(toolbar, 0);
return false;
}
);
}
}
}
/**
* Sets the system navigation bar color for the activity.
* Applies the background color obtained from {@link Utils#getAppBackgroundColor()} to the navigation bar.
* For Android 10 (API 29) and above, enforces navigation bar contrast to ensure visibility.
*/
public static void setNavigationBarColor(@Nullable Window window) {
if (window == null) {
Logger.printDebug(() -> "Cannot set navigation bar color, window is null");
return;
}
window.setNavigationBarColor(Utils.getAppBackgroundColor());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(true);
}
}
/**
* Returns the drawable for the back button.
*/
@SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() {
final int backButtonResource = Utils.getResourceIdentifier(
"revanced_settings_toolbar_arrow_left", "drawable");
Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
customizeBackButtonDrawable(drawable);
return drawable;
}
/**
* Customizes the back button drawable.
*/
protected static void customizeBackButtonDrawable(Drawable drawable) {
drawable.setTint(Utils.getAppForegroundColor());
}
/**
* Allows subclasses to customize the dialog's root view background.
*/
protected void customizeDialogBackground(ViewGroup rootView) {
rootView.setBackgroundColor(Utils.getAppBackgroundColor());
}
/**
* Allows subclasses to customize the toolbar.
*/
protected void customizeToolbar(Toolbar toolbar) {
BaseActivityHook.setToolbarLayoutParams(toolbar);
}
/**
* Allows subclasses to perform actions after toolbar setup.
*/
protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) {}
}

View File

@@ -2,17 +2,22 @@ package app.revanced.extension.shared.spoof;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Locale;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;
@SuppressWarnings("ConstantLocale")
public enum ClientType {
/**
* Video not playable: Kids / Paid / Movie / Private / Age-restricted.
* This client can only be used when logged out.
*/
// https://dumps.tadiphone.dev/dumps/oculus/eureka
ANDROID_VR_NO_AUTH(
ANDROID_VR_1_61_48(
28,
"ANDROID_VR",
"com.google.android.apps.youtube.vr.oculus",
@@ -27,29 +32,32 @@ public enum ClientType {
"1.61.48",
false,
false,
"Android VR No auth"
"Android VR 1.61"
),
// Chromecast with Google TV 4K.
// https://dumps.tadiphone.dev/dumps/google/kirkwood
ANDROID_UNPLUGGED(
29,
"ANDROID_UNPLUGGED",
"com.google.android.apps.youtube.unplugged",
"Google",
"Google TV Streamer",
"Android",
"14",
"34",
"UTT3.240625.001.K5",
"132.0.6808.3",
"8.49.0",
true,
true,
"Android TV"
/**
* Uses non adaptive bitrate, which fixes audio stuttering with YT Music.
* Does not use AV1.
*/
ANDROID_VR_1_43_32(
ANDROID_VR_1_61_48.id,
ANDROID_VR_1_61_48.clientName,
Objects.requireNonNull(ANDROID_VR_1_61_48.packageName),
ANDROID_VR_1_61_48.deviceMake,
ANDROID_VR_1_61_48.deviceModel,
ANDROID_VR_1_61_48.osName,
ANDROID_VR_1_61_48.osVersion,
Objects.requireNonNull(ANDROID_VR_1_61_48.androidSdkVersion),
Objects.requireNonNull(ANDROID_VR_1_61_48.buildId),
"107.0.5284.2",
"1.43.32",
ANDROID_VR_1_61_48.requiresAuth,
ANDROID_VR_1_61_48.useAuth,
"Android VR 1.43"
),
// Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
// Google Pixel 9 Pro Fold
// https://dumps.tadiphone.dev/dumps/google/barbet
/**
* Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
* <a href="https://dumps.tadiphone.dev/dumps/google/barbet">Google Pixel 9 Pro Fold</a>
*/
ANDROID_CREATOR(
14,
"ANDROID_CREATOR",
@@ -66,58 +74,64 @@ public enum ClientType {
true,
"Android Creator"
),
/**
* Internal YT client for an unreleased YT client. May stop working at any time.
*/
VISIONOS(101,
"VISIONOS",
"Apple",
"RealityDevice14,1",
"visionOS",
"1.3.21O771",
"0.1",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
false,
false,
"visionOS"
),
/**
* The device machine id for the iPad 6th Gen (iPad7,6).
* AV1 hardware decoding is not supported.
* See [this GitHub Gist](https://gist.github.com/adamawolf/3048717) for more information.
*
* Based on Google's actions to date, PoToken may not be required on devices with very low specs.
* For example, suppose the User-Agent for a PlayStation 3 (with 256MB of RAM) is used.
* Accessing 'Web' (https://www.youtube.com) will redirect to 'TV' (https://www.youtube.com/tv).
* 'TV' target devices with very low specs, such as embedded devices, game consoles, and blu-ray players, so PoToken is not required.
*
* For this reason, the device machine id for the iPad 6th Gen (with 2GB of RAM),
* the lowest spec device capable of running iPadOS 17, was used.
*/
IPADOS(5,
"IOS",
"Apple",
"iPad7,6",
"iPadOS",
"17.7.10.21H450",
"19.22.3",
"com.google.ios.youtube/19.22.3 (iPad7,6; U; CPU iPadOS 17_7_10 like Mac OS X; " + Locale.getDefault() + ")",
false,
false,
"iPadOS"
),
/**
* Obsolete and broken client. Here only to migrate data.
*/
@Deprecated
IOS_UNPLUGGED(
33,
"IOS_UNPLUGGED",
"com.google.ios.youtubeunplugged",
"Apple",
forceAVC()
// 11 Pro Max (last device with iOS 13)
? "iPhone12,5"
// 15 Pro Max
: "iPhone16,2",
"iPhone16,2",
"iOS",
forceAVC()
// iOS 13 and earlier uses only AVC. 14+ adds VP9 and AV1.
? "13.7.17H35"
: "18.2.22C152",
null,
null,
null,
// Version number should be a valid iOS release.
// https://www.ipa4fun.com/history/152043/
forceAVC()
// Some newer versions can also force AVC,
// but 6.45 is the last version that supports iOS 13.
? "6.45"
: "8.49",
"18.2.22C152",
"8.49",
"dummy user-agent",
true,
true,
forceAVC()
? "iOS TV Force AVC"
: "iOS TV"
),
ANDROID_VR_AUTH(
ANDROID_VR_NO_AUTH.id,
ANDROID_VR_NO_AUTH.clientName,
ANDROID_VR_NO_AUTH.packageName,
ANDROID_VR_NO_AUTH.deviceMake,
ANDROID_VR_NO_AUTH.deviceModel,
ANDROID_VR_NO_AUTH.osName,
ANDROID_VR_NO_AUTH.osVersion,
ANDROID_VR_NO_AUTH.androidSdkVersion,
ANDROID_VR_NO_AUTH.buildId,
ANDROID_VR_NO_AUTH.cronetVersion,
ANDROID_VR_NO_AUTH.clientVersion,
ANDROID_VR_NO_AUTH.requiresAuth,
true,
"Android VR Auth"
"iOS TV"
);
private static boolean forceAVC() {
return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
}
/**
* YouTube
* <a href="https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients">client type</a>
@@ -129,6 +143,7 @@ public enum ClientType {
/**
* App package name.
*/
@Nullable
private final String packageName;
/**
@@ -198,17 +213,19 @@ public enum ClientType {
*/
public final String friendlyName;
@SuppressWarnings("ConstantLocale")
/**
* Android constructor.
*/
ClientType(int id,
String clientName,
String packageName,
@NonNull String packageName,
String deviceMake,
String deviceModel,
String osName,
String osVersion,
@Nullable String androidSdkVersion,
@Nullable String buildId,
@Nullable String cronetVersion,
@NonNull String androidSdkVersion,
@NonNull String buildId,
@NonNull String cronetVersion,
String clientVersion,
boolean requiresAuth,
boolean useAuth,
@@ -229,31 +246,44 @@ public enum ClientType {
this.friendlyName = friendlyName;
Locale defaultLocale = Locale.getDefault();
if (androidSdkVersion == null) {
// Convert version from '18.2.22C152' into '18_2_22'
String userAgentOsVersion = osVersion
.replaceAll("(\\d+\\.\\d+\\.\\d+).*", "$1")
.replace(".", "_");
// https://github.com/mitmproxy/mitmproxy/issues/4836
this.userAgent = String.format("%s/%s (%s; U; CPU iOS %s like Mac OS X; %s)",
packageName,
clientVersion,
deviceModel,
userAgentOsVersion,
defaultLocale
);
} else {
this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s; Cronet/%s)",
packageName,
clientVersion,
osVersion,
defaultLocale,
deviceModel,
Objects.requireNonNull(buildId),
Objects.requireNonNull(cronetVersion)
);
}
this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s; Cronet/%s)",
packageName,
clientVersion,
osVersion,
defaultLocale,
deviceModel,
Objects.requireNonNull(buildId),
Objects.requireNonNull(cronetVersion)
);
Logger.printDebug(() -> "userAgent: " + this.userAgent);
}
@SuppressWarnings("ConstantLocale")
ClientType(int id,
String clientName,
String deviceMake,
String deviceModel,
String osName,
String osVersion,
String clientVersion,
String userAgent,
boolean requiresAuth,
boolean useAuth,
String friendlyName) {
this.id = id;
this.clientName = clientName;
this.deviceMake = deviceMake;
this.deviceModel = deviceModel;
this.osName = osName;
this.osVersion = osVersion;
this.clientVersion = clientVersion;
this.userAgent = userAgent;
this.requiresAuth = requiresAuth;
this.useAuth = useAuth;
this.friendlyName = friendlyName;
this.packageName = null;
this.androidSdkVersion = null;
this.buildId = null;
this.cronetVersion = null;
}
}

View File

@@ -10,6 +10,7 @@ import java.util.Map;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
@@ -19,13 +20,25 @@ 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;
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.VISIONOS;
@Nullable
private static volatile AppLanguage languageOverride;
/**
* Any unreachable ip address. Used to intentionally fail requests.
* Domain used for internet connectivity verification.
* It has an empty response body and is only used to check for a 204 response code.
* <p>
* If an unreachable IP address (127.0.0.1) is used, no response code is provided.
* <p>
* YouTube handles unreachable IP addresses without issue.
* YouTube Music has an issue with waiting for the Cronet connect timeout of 30s on mobile networks.
* <p>
* Using a VPN or DNS can temporarily resolve this issue,
* But the ideal workaround is to avoid using an unreachable IP address.
*/
private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0";
private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING);
private static final String INTERNET_CONNECTION_CHECK_URI_STRING = "https://www.google.com/gen_204";
private static final Uri INTERNET_CONNECTION_CHECK_URI = Uri.parse(INTERNET_CONNECTION_CHECK_URI_STRING);
/**
* @return If this patch was included during patching.
@@ -34,10 +47,21 @@ public class SpoofVideoStreamsPatch {
return false; // Modified during patching.
}
public static boolean notSpoofingToAndroid() {
return !isPatchIncluded()
|| !BaseSettings.SPOOF_VIDEO_STREAMS.get()
|| BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
public static boolean spoofingToClientWithNoMultiAudioStreams() {
return isPatchIncluded() && BaseSettings.SPOOF_VIDEO_STREAMS.get();
}
/**
* @param language Language override for non-authenticated requests. If this is null then
* {@link BaseSettings#SPOOF_VIDEO_STREAMS_LANGUAGE} is used.
*/
public static void setLanguageOverride(@Nullable AppLanguage language) {
languageOverride = language;
}
@Nullable
public static AppLanguage getLanguageOverride() {
return languageOverride;
}
/**
@@ -53,9 +77,9 @@ public class SpoofVideoStreamsPatch {
String path = playerRequestUri.getPath();
if (path != null && path.contains("get_watch")) {
Logger.printDebug(() -> "Blocking 'get_watch' by returning unreachable uri");
Logger.printDebug(() -> "Blocking 'get_watch' by returning internet connection check uri");
return UNREACHABLE_HOST_URI;
return INTERNET_CONNECTION_CHECK_URI;
}
} catch (Exception ex) {
Logger.printException(() -> "blockGetWatchRequest failure", ex);
@@ -77,9 +101,9 @@ public class SpoofVideoStreamsPatch {
String path = originalUri.getPath();
if (path != null && path.contains("initplayback")) {
Logger.printDebug(() -> "Blocking 'initplayback' by clearing query");
Logger.printDebug(() -> "Blocking 'initplayback' by returning internet connection check uri");
return originalUri.buildUpon().clearQuery().build().toString();
return INTERNET_CONNECTION_CHECK_URI_STRING;
}
} catch (Exception ex) {
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
@@ -252,16 +276,8 @@ public class SpoofVideoStreamsPatch {
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;
// Since all current clients are un-authenticated, this works for all spoof clients.
return BaseSettings.SPOOF_VIDEO_STREAMS.get();
}
}
}

View File

@@ -1,5 +1,7 @@
package app.revanced.extension.shared.spoof.requests;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
import org.json.JSONException;
import org.json.JSONObject;
@@ -10,8 +12,10 @@ import java.util.Locale;
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.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.spoof.ClientType;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
final class PlayerRoutes {
static final Route.CompiledRoute GET_STREAMING_DATA = new Route(
@@ -37,14 +41,16 @@ 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();
Locale streamLocale = userSelectedClient == ClientType.ANDROID_VR_NO_AUTH
? BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get().getLocale()
: Locale.getDefault();
AppLanguage language = SpoofVideoStreamsPatch.getLanguageOverride();
if (language == null || BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ANDROID_VR_1_43_32) {
// Force original audio has not overrode the language.
// Or if YT has fallen over to the very last client (VR 1.43), then always
// use the app language because forcing an audio stream of specific languages
// can sometimes fail so it's better to try and load something rather than nothing.
language = BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get();
}
//noinspection ExtractMethodRecommender
Locale streamLocale = language.getLocale();
JSONObject client = new JSONObject();
client.put("deviceMake", clientType.deviceMake);

View File

@@ -35,21 +35,27 @@ import app.revanced.extension.shared.spoof.ClientType;
*/
public class StreamingDataRequest {
private static final ClientType[] CLIENT_ORDER_TO_USE;
private static volatile ClientType[] clientOrderToUse = ClientType.values();
static {
ClientType[] allClientTypes = ClientType.values();
ClientType preferredClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
public static void setClientOrderToUse(List<ClientType> availableClients, ClientType preferredClient) {
Objects.requireNonNull(preferredClient);
CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length];
CLIENT_ORDER_TO_USE[0] = preferredClient;
int availableClientSize = availableClients.size();
if (!availableClients.contains(preferredClient)) {
availableClientSize++;
}
clientOrderToUse = new ClientType[availableClientSize];
clientOrderToUse[0] = preferredClient;
int i = 1;
for (ClientType c : allClientTypes) {
for (ClientType c : availableClients) {
if (c != preferredClient) {
CLIENT_ORDER_TO_USE[i++] = c;
clientOrderToUse[i++] = c;
}
}
Logger.printDebug(() -> "Available spoof clients: " + Arrays.toString(clientOrderToUse));
}
private static final String AUTHORIZATION_HEADER = "Authorization";
@@ -193,9 +199,9 @@ public class StreamingDataRequest {
// Retry with different client if empty response body is received.
int i = 0;
for (ClientType clientType : CLIENT_ORDER_TO_USE) {
for (ClientType clientType : clientOrderToUse) {
// 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;
final boolean showErrorToast = (++i == clientOrderToUse.length) || debugEnabled;
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
if (connection != null) {

View File

@@ -2,4 +2,5 @@ dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:syncforreddit:stub"))
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -0,0 +1,22 @@
package app.revanced.extension.syncforreddit;
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
import okhttp3.OkHttpClient;
/**
* @noinspection unused
*/
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
static {
INSTANCE = new FixRedgifsApiPatch();
}
public String getDefaultUserAgent() {
// To be filled in by patch
return "";
}
public static OkHttpClient install(OkHttpClient.Builder builder) {
return builder.addInterceptor(INSTANCE).build();
}
}

View File

@@ -1,7 +1,7 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
import app.revanced.extension.youtube.settings.Settings;
@@ -11,16 +11,21 @@ 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.
* Injection point.
*/
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();
public static void setPreferredLanguage() {
if (Settings.FORCE_ORIGINAL_AUDIO.get()
&& SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams()) {
// If client spoofing does not use authentication and lacks multi-audio streams,
// then can use any language code for the request and if that requested language is
// not available YT uses the original audio language. Authenticated requests ignore
// the language code and always use the account language. Use a language that is
// not auto-dubbed by YouTube: https://support.google.com/youtube/answer/15569972
// but the language is also supported natively by the Meta Quest device that
// Android VR is spoofing.
AppLanguage override = AppLanguage.SV;
Logger.printDebug(() -> "Setting language override: " + override);
SpoofVideoStreamsPatch.setLanguageOverride(override);
}
}

View File

@@ -513,6 +513,9 @@ public final class VideoInformation {
Logger.printDebug(() -> "VideoQualities: " + Arrays.toString(currentQualities));
}
// On extremely slow internet connections the index can initially be -1
originalQualityIndex = Math.max(0, originalQualityIndex);
VideoQuality updatedCurrentQuality = qualities[originalQualityIndex];
if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE
&& (currentQuality == null || currentQuality != updatedCurrentQuality)) {

View File

@@ -194,7 +194,7 @@ public final class LayoutComponentsFilter extends Filter {
// Playable horizontal shelf header.
playablesBuffer = new ByteArrayFilterGroup(
Settings.HIDE_PLAYABLES,
"mini_game"
"FEmini_app_destination"
);
final var quickActions = new StringFilterGroup(

View File

@@ -9,13 +9,13 @@ import app.revanced.extension.youtube.shared.ShortsPlayerState;
public class PlayerFlyoutMenuItemsFilter extends Filter {
public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability {
private static final boolean AVAILABLE_ON_LAUNCH = SpoofVideoStreamsPatch.notSpoofingToAndroid();
private static final boolean AVAILABLE_ON_LAUNCH = !SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams();
@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 AVAILABLE_ON_LAUNCH && SpoofVideoStreamsPatch.notSpoofingToAndroid();
return AVAILABLE_ON_LAUNCH && !SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams();
}
}

View File

@@ -0,0 +1,34 @@
package app.revanced.extension.youtube.patches.spoof;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_CREATOR;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
import static app.revanced.extension.shared.spoof.ClientType.IPADOS;
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
import java.util.List;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.spoof.ClientType;
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
@SuppressWarnings("unused")
public class SpoofVideoStreamsPatch {
/**
* Injection point.
*/
public static void setClientOrderToUse() {
List<ClientType> availableClients = List.of(
ANDROID_VR_1_61_48,
ANDROID_CREATOR,
VISIONOS,
IPADOS,
// VR 1.43 must be last as spoof streaming data handles it slightly differently.
ANDROID_VR_1_43_32
);
StreamingDataRequest.setClientOrderToUse(availableClients,
BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get());
}
}

View File

@@ -1,50 +1,120 @@
package app.revanced.extension.youtube.settings;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceFragment;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.widget.TextView;
import android.view.View;
import android.widget.Toolbar;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseActivityHook;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
/**
* Hooks LicenseActivity.
* <p>
* This class is responsible for injecting our own fragment by replacing the LicenseActivity.
* Hooks LicenseActivity to inject a custom ReVancedPreferenceFragment with a toolbar and search functionality.
*/
@SuppressWarnings("unused")
public class LicenseActivityHook extends Activity {
@SuppressWarnings("deprecation")
public class LicenseActivityHook extends BaseActivityHook {
private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value.
private static ViewGroup.LayoutParams toolbarLayoutParams;
/**
* Controller for managing search view components in the toolbar.
*/
@SuppressLint("StaticFieldLeak")
public static SearchViewController searchViewController;
public static void setToolbarLayoutParams(Toolbar toolbar) {
if (toolbarLayoutParams != null) {
toolbar.setLayoutParams(toolbarLayoutParams);
/**
* Injection point
* <p>
* Creates an instance of LicenseActivityHook for use in static initialization.
*/
@SuppressWarnings("unused")
public static LicenseActivityHook createInstance() {
return new LicenseActivityHook();
}
/**
* Customizes the activity theme based on dark/light mode.
*/
@Override
protected void customizeActivityTheme(Activity activity) {
final var theme = Utils.isDarkModeEnabled()
? "Theme.YouTube.Settings.Dark"
: "Theme.YouTube.Settings";
activity.setTheme(Utils.getResourceIdentifier(theme, "style"));
}
/**
* Returns the resource ID for the YouTube settings layout.
*/
@Override
protected int getContentViewResourceId() {
return Utils.getResourceIdentifier("revanced_settings_with_toolbar", "layout");
}
/**
* Returns the toolbar background color based on dark/light mode.
*/
@Override
protected int getToolbarBackgroundColor() {
final String colorName = Utils.isDarkModeEnabled()
? "yt_black3"
: "yt_white1";
return Utils.getColorFromString(colorName);
}
/**
* Returns the navigation icon drawable for the toolbar.
*/
@Override
protected Drawable getNavigationIcon() {
return ReVancedPreferenceFragment.getBackButtonDrawable();
}
/**
* Returns the click listener for the navigation icon.
*/
@Override
protected View.OnClickListener getNavigationClickListener(Activity activity) {
return null;
}
/**
* Adds search view components to the toolbar for ReVancedPreferenceFragment.
*
* @param activity The activity hosting the toolbar.
* @param toolbar The configured toolbar.
* @param fragment The PreferenceFragment associated with the activity.
*/
@Override
protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) {
if (fragment instanceof ReVancedPreferenceFragment) {
searchViewController = SearchViewController.addSearchViewComponents(
activity, toolbar, (ReVancedPreferenceFragment) fragment);
}
}
/**
* Creates a new ReVancedPreferenceFragment for the activity.
*/
@Override
protected PreferenceFragment createPreferenceFragment() {
return new ReVancedPreferenceFragment();
}
/**
* Injection point.
* Overrides the ReVanced settings language.
*/
@SuppressWarnings("unused")
public static Context getAttachBaseContext(Context original) {
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
if (language == AppLanguage.DEFAULT) {
@@ -57,6 +127,7 @@ public class LicenseActivityHook extends Activity {
/**
* Injection point.
*/
@SuppressWarnings("unused")
public static boolean useCairoSettingsFragment(boolean original) {
// Early targets have layout issues and it's better to always force off.
if (!VersionCheckPatch.IS_19_34_OR_GREATER) {
@@ -80,87 +151,6 @@ public class LicenseActivityHook extends Activity {
/**
* Injection point.
* <p>
* Hooks LicenseActivity#onCreate in order to inject our own fragment.
*/
public static void initialize(Activity licenseActivity) {
try {
setActivityTheme(licenseActivity);
ReVancedPreferenceFragment.setNavigationBarColor(licenseActivity.getWindow());
licenseActivity.setContentView(getResourceIdentifier(
"revanced_settings_with_toolbar", "layout"));
// Sanity check.
String dataString = licenseActivity.getIntent().getDataString();
if (!"revanced_settings_intent".equals(dataString)) {
Logger.printException(() -> "Unknown intent: " + dataString);
return;
}
PreferenceFragment fragment = new ReVancedPreferenceFragment();
createToolbar(licenseActivity, fragment);
//noinspection deprecation
licenseActivity.getFragmentManager()
.beginTransaction()
.replace(getResourceIdentifier("revanced_settings_fragments", "id"), fragment)
.commit();
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
@SuppressLint("UseCompatLoadingForDrawables")
private static void createToolbar(Activity activity, PreferenceFragment fragment) {
// Replace dummy placeholder toolbar.
// This is required to fix submenu title alignment issue with Android ASOP 15+
ViewGroup toolBarParent = activity.findViewById(
getResourceIdentifier("revanced_toolbar_parent", "id"));
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar");
toolbarLayoutParams = dummyToolbar.getLayoutParams();
toolBarParent.removeView(dummyToolbar);
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
toolbar.setBackgroundColor(getToolbarBackgroundColor());
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
final int margin = Utils.dipToPixels(16);
toolbar.setTitleMarginStart(margin);
toolbar.setTitleMarginEnd(margin);
TextView toolbarTextView = Utils.getChildView(toolbar, false,
view -> view instanceof TextView);
if (toolbarTextView != null) {
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
}
setToolbarLayoutParams(toolbar);
// Add Search bar only for ReVancedPreferenceFragment.
if (fragment instanceof ReVancedPreferenceFragment) {
searchViewController = SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment);
}
toolBarParent.addView(toolbar, 0);
}
public static void setActivityTheme(Activity activity) {
final var theme = Utils.isDarkModeEnabled()
? "Theme.YouTube.Settings.Dark"
: "Theme.YouTube.Settings";
activity.setTheme(getResourceIdentifier(theme, "style"));
}
public static int getToolbarBackgroundColor() {
final String colorName = Utils.isDarkModeEnabled()
? "yt_black3"
: "yt_white1";
return Utils.getColorFromString(colorName);
}
/**
* Injection point.
*
* Updates dark/light mode since YT settings can force light/dark mode
* which can differ from the global device settings.
*/
@@ -173,6 +163,10 @@ public class LicenseActivityHook extends Activity {
}
}
/**
* Handles configuration changes, such as orientation, to update the search view.
*/
@SuppressWarnings("unused")
public static void handleConfigurationChanged(Activity activity, Configuration newConfig) {
if (searchViewController != null) {
searchViewController.handleOrientationChange(newConfig.orientation);

View File

@@ -12,7 +12,6 @@ import static app.revanced.extension.youtube.patches.ChangeHeaderPatch.HeaderLog
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.ChangeStartPageTypeAvailability;
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.MiniplayerHorizontalDragAvailability;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MINIMAL;
@@ -75,7 +74,7 @@ public class Settings extends BaseSettings {
"0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true);
// Audio
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, new ForceOriginalAudioAvailability());
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, true);
// Ads
public static final BooleanSetting HIDE_CREATOR_STORE_SHELF = new BooleanSetting("revanced_hide_creator_store_shelf", TRUE);
@@ -436,6 +435,9 @@ public class Settings extends BaseSettings {
public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#FF1684");
public static final FloatSetting SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_HOOK = new StringSetting("sb_hook", IGNORE.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_HOOK_COLOR = new StringSetting("sb_hook_color", "#395699");
public static final FloatSetting SB_CATEGORY_HOOK_OPACITY = new FloatSetting("sb_hook_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", MANUAL_SKIP.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF");
public static final FloatSetting SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f);

View File

@@ -1,36 +0,0 @@
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

@@ -12,8 +12,8 @@ import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
public class HideAudioFlyoutMenuPreference extends SwitchPreference {
{
// Audio menu is not available if spoofing to Android client type.
if (!SpoofVideoStreamsPatch.notSpoofingToAndroid()) {
// Audio menu is not available if spoofing to most client types.
if (SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams()) {
String summary = str("revanced_hide_player_flyout_audio_track_not_available");
setSummary(summary);
setSummaryOn(summary);

View File

@@ -3,11 +3,7 @@ package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.graphics.Insets;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
@@ -17,11 +13,6 @@ import android.preference.SwitchPreference;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.BackgroundColorSpan;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.CallSuper;
@@ -40,16 +31,16 @@ import java.util.regex.Pattern;
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.preference.AbstractPreferenceFragment;
import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory;
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
import app.revanced.extension.youtube.settings.LicenseActivityHook;
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
/**
* Preference fragment for ReVanced settings.
*/
@SuppressWarnings("deprecation")
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
@SuppressWarnings({"deprecation", "NewApi"})
public class ReVancedPreferenceFragment extends ToolbarPreferenceFragment {
/**
* The main PreferenceScreen used to display the current set of preferences.
@@ -70,31 +61,6 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
*/
private final List<AbstractPreferenceSearchData<?>> allPreferences = new ArrayList<>();
@SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() {
final int backButtonResource = getResourceIdentifier("revanced_settings_toolbar_arrow_left", "drawable");
Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
drawable.setTint(Utils.getAppForegroundColor());
return drawable;
}
/**
* Sets the system navigation bar color for the activity.
* Applies the background color obtained from {@link Utils#getAppBackgroundColor()} to the navigation bar.
* For Android 10 (API 29) and above, enforces navigation bar contrast to ensure visibility.
*/
public static void setNavigationBarColor(@Nullable Window window) {
if (window == null) {
Logger.printDebug(() -> "Cannot set navigation bar color, window is null");
return;
}
window.setNavigationBarColor(Utils.getAppBackgroundColor());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(true);
}
}
/**
* Initializes the preference fragment, copying the original screen to allow full restoration.
*/
@@ -139,8 +105,28 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
}
}
/**
* Sets toolbar for all nested preference screens.
*/
@Override
protected void customizeToolbar(Toolbar toolbar) {
LicenseActivityHook.setToolbarLayoutParams(toolbar);
}
/**
* Perform actions after toolbar setup.
*/
@Override
protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) {
if (LicenseActivityHook.searchViewController != null
&& LicenseActivityHook.searchViewController.isSearchActive()) {
toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch());
}
}
/**
* Recursively collects all preferences from the screen or group.
*
* @param includeDepth Menu depth to start including preferences.
* A value of 0 adds all preferences.
*/
@@ -222,75 +208,6 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
preferenceScreen.addPreference(noResultsPreference);
}
}
/**
* Sets toolbar for all nested preference screens.
*/
private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) {
for (int i = 0, count = parentScreen.getPreferenceCount(); i < count; i++) {
Preference childPreference = parentScreen.getPreference(i);
if (childPreference instanceof PreferenceScreen) {
// Recursively set sub preferences.
setPreferenceScreenToolbar((PreferenceScreen) childPreference);
childPreference.setOnPreferenceClickListener(
childScreen -> {
Dialog preferenceScreenDialog = ((PreferenceScreen) childScreen).getDialog();
ViewGroup rootView = (ViewGroup) preferenceScreenDialog
.findViewById(android.R.id.content)
.getParent();
// Fix the system navigation bar color for submenus.
setNavigationBarColor(preferenceScreenDialog.getWindow());
// Fix edge-to-edge screen with Android 15 and YT 19.45+
// https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars());
Insets cutoutInsets = insets.getInsets(WindowInsets.Type.displayCutout());
// Apply padding for display cutout in landscape.
int leftPadding = cutoutInsets.left;
int rightPadding = cutoutInsets.right;
int topPadding = statusInsets.top;
int bottomPadding = navInsets.bottom;
v.setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
return insets;
});
}
Toolbar toolbar = new Toolbar(childScreen.getContext());
toolbar.setTitle(childScreen.getTitle());
toolbar.setNavigationIcon(getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
final int margin = Utils.dipToPixels(16);
toolbar.setTitleMargin(margin, 0, margin, 0);
TextView toolbarTextView = Utils.getChildView(toolbar,
true, TextView.class::isInstance);
if (toolbarTextView != null) {
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
}
LicenseActivityHook.setToolbarLayoutParams(toolbar);
if (LicenseActivityHook.searchViewController != null
&& LicenseActivityHook.searchViewController.isSearchActive()) {
toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch());
}
rootView.addView(toolbar, 0);
return false;
}
);
}
}
}
}
@SuppressWarnings("deprecation")

View File

@@ -78,21 +78,25 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
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");
String title = str(key + "_title");
String summary = str(key + "_summary");
setTitle(str("revanced_spoof_video_streams_about_title"));
// Android VR supports AV1 but all other clients do not.
if (clientType != ClientType.ANDROID_VR_AUTH && clientType != ClientType.ANDROID_VR_NO_AUTH) {
summary += '\n' + str("revanced_spoof_video_streams_about_no_av1");
String summary = str(clientType == ClientType.IPADOS
? "revanced_spoof_video_streams_about_ipados_summary"
// visionOS has same base side effects as Android VR.
: "revanced_spoof_video_streams_about_android_summary");
if (clientType == ClientType.IPADOS) {
summary = str("revanced_spoof_video_streams_about_no_av1")
+ '\n' + summary;
} else if (clientType == ClientType.VISIONOS) {
summary = str("revanced_spoof_video_streams_about_experimental")
+ '\n' + summary
+ '\n' + str("revanced_spoof_video_streams_about_no_av1")
+ '\n' + str("revanced_spoof_video_streams_about_kids_videos");
} else {
summary += '\n' + str("revanced_spoof_video_streams_about_kids_videos");
}
summary += '\n' + str("revanced_spoof_video_streams_about_kids_videos");
setTitle(title);
setSummary(summary);
}
}

View File

@@ -15,6 +15,7 @@ import android.graphics.drawable.shapes.RoundRectShape;
import android.text.TextUtils;
import android.util.Range;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
@@ -167,6 +168,11 @@ public class SegmentPlaybackController {
*/
private static WeakReference<Dialog> toastDialogRef = new WeakReference<>(null);
/**
* Visibility of the ad progress UI component.
*/
private static volatile int adProgressTextVisibility = -1;
static {
// Dismiss toast if app changes to PiP while undo skip is shown.
PlayerType.getOnChange().addObserver((PlayerType type) -> {
@@ -336,6 +342,7 @@ public class SegmentPlaybackController {
*/
static void executeDownloadSegments(String videoId) {
Objects.requireNonNull(videoId);
Utils.verifyOffMainThread();
SponsorSegment[] segments = SBRequester.getSegments(videoId);
@@ -367,6 +374,35 @@ public class SegmentPlaybackController {
});
}
/**
* Injection point.
*/
@SuppressWarnings("unused")
public static void setAdProgressTextVisibility(int visibility) {
if (adProgressTextVisibility != visibility) {
adProgressTextVisibility = visibility;
Logger.printDebug(() -> {
String visibilityMessage = switch (visibility) {
case View.VISIBLE -> "VISIBLE";
case View.GONE -> "GONE";
case View.INVISIBLE -> "INVISIBLE";
default -> "UNKNOWN";
};
return "AdProgressText visibility changed to: " + visibilityMessage;
});
}
}
/**
* When a video ad is playing in a regular video player, segments or the Skip button should be hidden.
* @return Whether the Ad Progress TextView is visible in the regular video player.
*/
public static boolean isAdProgressTextVisible() {
return adProgressTextVisibility == View.VISIBLE;
}
/**
* Injection point.
* Updates SponsorBlock every 1000ms.
@@ -376,7 +412,8 @@ public class SegmentPlaybackController {
try {
if (!Settings.SB_ENABLED.get()
|| PlayerType.getCurrent().isNoneOrHidden() // Shorts playback.
|| segments == null || segments.length == 0) {
|| segments == null || segments.length == 0
|| isAdProgressTextVisible()) {
return;
}
Logger.printDebug(() -> "setVideoTime: " + millis);
@@ -671,7 +708,14 @@ public class SegmentPlaybackController {
// Check for any smaller embedded segments, and count those as auto-skipped.
final boolean showSkipToast = Settings.SB_TOAST_ON_SKIP.get();
for (SponsorSegment otherSegment : Objects.requireNonNull(segments)) {
if (segmentToSkip.end < otherSegment.start) {
if (otherSegment.end <= segmentToSkip.start) {
// Other segment does not overlap, and is before this skipped segment.
// This situation can only happen if a video is opened and adjusted to
// a later time in the video where earlier auto skip segments
// have not been encountered yet.
continue;
}
if (segmentToSkip.end <= otherSegment.start) {
break; // No other segments can be contained.
}
@@ -922,7 +966,8 @@ public class SegmentPlaybackController {
public static String appendTimeWithoutSegments(String totalTime) {
try {
if (Settings.SB_ENABLED.get() && Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get()
&& !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments)) {
&& !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments)
&& !isAdProgressTextVisible()) {
// Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages
return "\u202D" + totalTime + timeWithoutSegments; // u202D = left to right override
}
@@ -983,7 +1028,7 @@ public class SegmentPlaybackController {
@SuppressWarnings("unused")
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
try {
if (segments == null) return;
if (segments == null || isAdProgressTextVisible()) return;
final long videoLength = VideoInformation.getVideoLength();
if (videoLength <= 0) return;

View File

@@ -52,6 +52,8 @@ public enum SegmentCategory {
sf("revanced_sb_skip_button_preview_beginning"), sf("revanced_sb_skip_button_preview_middle"), sf("revanced_sb_skip_button_preview_end"),
sf("revanced_sb_skipped_preview_beginning"), sf("revanced_sb_skipped_preview_middle"), sf("revanced_sb_skipped_preview_end"),
SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR, SB_CATEGORY_PREVIEW_OPACITY),
HOOK("hook", sf("revanced_sb_segments_hook"), sf("revanced_sb_segments_hook_sum"), sf("revanced_sb_skip_button_hook"), sf("revanced_sb_skipped_hook"),
SB_CATEGORY_HOOK, SB_CATEGORY_HOOK_COLOR, SB_CATEGORY_HOOK_OPACITY),
FILLER("filler", sf("revanced_sb_segments_filler"), sf("revanced_sb_segments_filler_sum"), sf("revanced_sb_skip_button_filler"), sf("revanced_sb_skipped_filler"),
SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR, SB_CATEGORY_FILLER_OPACITY),
MUSIC_OFFTOPIC("music_offtopic", sf("revanced_sb_segments_nomusic"), sf("revanced_sb_segments_nomusic_sum"), sf("revanced_sb_skip_button_nomusic"), sf("revanced_sb_skipped_nomusic"),
@@ -69,6 +71,7 @@ public enum SegmentCategory {
INTRO,
OUTRO,
PREVIEW,
HOOK,
FILLER,
MUSIC_OFFTOPIC,
};
@@ -81,6 +84,7 @@ public enum SegmentCategory {
INTRO,
OUTRO,
PREVIEW,
HOOK,
FILLER,
MUSIC_OFFTOPIC,
};

View File

@@ -6,6 +6,7 @@ import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.videoplayer.PlayerControlButton;
@SuppressWarnings("unused")
@@ -26,7 +27,7 @@ public class CreateSegmentButton {
controlsView,
"revanced_sb_create_segment_button",
null,
CreateSegmentButton::shouldBeShown,
CreateSegmentButton::isButtonEnabled,
v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility(),
null
);
@@ -56,7 +57,8 @@ public class CreateSegmentButton {
if (instance != null) instance.setVisibility(visible, animated);
}
private static boolean shouldBeShown() {
return Settings.SB_ENABLED.get() && Settings.SB_CREATE_NEW_SEGMENT.get();
private static boolean isButtonEnabled() {
return Settings.SB_ENABLED.get() && Settings.SB_CREATE_NEW_SEGMENT.get()
&& !SegmentPlaybackController.isAdProgressTextVisible();
}
}

View File

@@ -28,7 +28,7 @@ public class VotingButton {
controlsView,
"revanced_sb_voting_button",
null,
VotingButton::shouldBeShown,
VotingButton::isButtonEnabled,
v -> SponsorBlockUtils.onVotingClicked(v.getContext()),
null
);
@@ -58,8 +58,9 @@ public class VotingButton {
if (instance != null) instance.setVisibility(visible, animated);
}
private static boolean shouldBeShown() {
private static boolean isButtonEnabled() {
return Settings.SB_ENABLED.get() && Settings.SB_VOTING_BUTTON.get()
&& SegmentPlaybackController.videoHasSegments();
&& SegmentPlaybackController.videoHasSegments()
&& !SegmentPlaybackController.isAdProgressTextVisible();
}
}

View File

@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true
android.useAndroidX = true
kotlin.code.style = official
version = 5.35.0-dev.2
version = 5.38.0-dev.3

View File

@@ -264,10 +264,18 @@ public final class app/revanced/patches/instagram/ads/HideAdsPatchKt {
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/instagram/hide/explore/HideExploreFeedKt {
public static final fun getHideExportFeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/instagram/hide/navigation/HideNavigationButtonsKt {
public static final fun getHideNavigationButtonsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/instagram/hide/stories/HideStoriesKt {
public static final fun getHideStoriesPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/instagram/misc/signature/SignatureCheckPatchKt {
public static final fun getSignatureCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -364,8 +372,9 @@ public final class app/revanced/patches/music/layout/premium/HideGetPremiumPatch
public static final fun getHideGetPremiumPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/layout/upgradebutton/RemoveUpgradeButtonPatchKt {
public static final fun getRemoveUpgradeButtonPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
public final class app/revanced/patches/music/layout/upgradebutton/HideUpgradeButtonPatchKt {
public static final fun getHideUpgradeButton ()Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun getRemoveUpgradeButton ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/misc/androidauto/BypassCertificateChecksPatchKt {
@@ -388,14 +397,35 @@ public final class app/revanced/patches/music/misc/gms/GmsCoreSupportPatchKt {
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/misc/spoof/SpoofClientPatchKt {
public static final fun getSpoofClientPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
public final class app/revanced/patches/music/misc/settings/PreferenceScreen : app/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen {
public static final field INSTANCE Lapp/revanced/patches/music/misc/settings/PreferenceScreen;
public fun commit (Lapp/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference;)V
public final fun getADS ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
public final fun getGENERAL ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
public final fun getMISC ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
public final fun getPLAYER ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
}
public final class app/revanced/patches/music/misc/settings/SettingsPatchKt {
public static final fun getSettingsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun newIntent (Ljava/lang/String;)Lapp/revanced/patches/shared/misc/settings/preference/IntentPreference$Intent;
}
public final class app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatchKt {
public static final fun getSpoofVideoStreamsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/misc/spoof/UserAgentClientSpoofPatchKt {
public static final fun getUserAgentClientSpoofPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/playservice/VersionCheckPatchKt {
public static final fun getVersionCheckPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
public static final fun is_7_33_or_greater ()Z
public static final fun is_8_11_or_greater ()Z
public static final fun is_8_15_or_greater ()Z
}
public final class app/revanced/patches/myexpenses/misc/pro/UnlockProPatchKt {
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -512,6 +542,13 @@ public final class app/revanced/patches/reddit/ad/general/HideAdsPatchKt {
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/FixRedgifsApiPatchKt {
public static final field CREATE_NEW_CLIENT_METHOD Ljava/lang/String;
public static final field INSTALL_NEW_CLIENT_METHOD Ljava/lang/String;
public static final fun fixRedgifsApiPatch (Lapp/revanced/patcher/patch/Patch;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
public static synthetic fun fixRedgifsApiPatch$default (Lapp/revanced/patcher/patch/Patch;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/FixSLinksPatchKt {
public static final field RESOLVE_S_LINK_METHOD Ljava/lang/String;
public static final field SET_ACCESS_TOKEN_METHOD Ljava/lang/String;
@@ -528,6 +565,14 @@ public final class app/revanced/patches/reddit/customclients/baconreader/api/Spo
public static final fun getSpoofClientPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/baconreader/fix/redgifs/FixRedgifsApiPatchKt {
public static final fun getFixRedgifsApi ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/baconreader/misc/extension/SharedExtensionPatchKt {
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/boostforreddit/ads/DisableAdsPatchKt {
public static final fun getDisableAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -540,6 +585,10 @@ public final class app/revanced/patches/reddit/customclients/boostforreddit/fix/
public static final fun getFixAudioMissingInDownloadsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/boostforreddit/fix/redgifs/FixRedgifsApiPatchKt {
public static final fun getFixRedgifsApi ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/boostforreddit/fix/slink/FixSLinksPatchKt {
public static final field EXTENSION_CLASS_DESCRIPTOR Ljava/lang/String;
public static final fun getFixSlinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
@@ -610,6 +659,10 @@ public final class app/revanced/patches/reddit/customclients/sync/syncforreddit/
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/sync/syncforreddit/fix/redgifs/FixRedgifsApiPatchKt {
public static final fun getFixRedgifsApi ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/sync/syncforreddit/fix/slink/FixSLinksPatchKt {
public static final field EXTENSION_CLASS_DESCRIPTOR Ljava/lang/String;
public static final fun getFixSLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
@@ -956,10 +1009,6 @@ public final class app/revanced/patches/spotify/lite/ondemand/OnDemandPatchKt {
public static final fun getOnDemandPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/misc/UnlockPremiumPatchKt {
public static final fun getUnlockPremiumPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/misc/extension/ExtensionPatchKt {
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1212,6 +1261,10 @@ public final class app/revanced/patches/twitter/misc/links/SanitizeSharingLinksP
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/viber/ads/HideAdsPatchKt {
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/vsco/misc/pro/UnlockProPatchKt {
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1614,7 +1667,6 @@ public final class app/revanced/patches/youtube/misc/settings/PreferenceScreen :
}
public final class app/revanced/patches/youtube/misc/settings/SettingsPatchKt {
public static final fun addSettingPreference (Lapp/revanced/patches/shared/misc/settings/preference/BasePreference;)V
public static final fun getSettingsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun newIntent (Ljava/lang/String;)Lapp/revanced/patches/shared/misc/settings/preference/IntentPreference$Intent;
}

View File

@@ -19,14 +19,16 @@ val disableAdsPatch = bytecodePatch(
// SharedPreferences has a debug boolean value with key "disable_ads", which maps to "DebugCategory.DISABLE_ADS".
//
// MonetizationDebugSettings seems to be the most general setting to work fine.
initializeMonetizationDebugSettingsFingerprint.method.apply {
val insertIndex = initializeMonetizationDebugSettingsFingerprint.patternMatch!!.startIndex
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
initializeMonetizationDebugSettingsFingerprint
.match(monetizationDebugSettingsToStringFingerprint.classDef)
.method.apply {
val insertIndex = initializeMonetizationDebugSettingsFingerprint.patternMatch!!.startIndex
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
addInstructions(
insertIndex,
"const/4 v$register, 0x1",
)
}
addInstructions(
insertIndex,
"const/4 v$register, 0x1",
)
}
}
}

View File

@@ -7,13 +7,11 @@ import com.android.tools.smali.dexlib2.Opcode
internal val initializeMonetizationDebugSettingsFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
returns("V")
parameters(
"Z", // disableAds
"Z", // useDebugBilling
"Z", // showManageSubscriptions
"Z", // alwaysShowSuperAds
// matches "Lcom/duolingo/debug/FamilyQuestOverride;" or "Lcom/duolingo/data/debug/monetization/FamilyQuestOverride;"
"Lcom/duolingo/",
)
// Parameters have not been reliable for fingerprinting between versions.
opcodes(Opcode.IPUT_BOOLEAN)
}
internal val monetizationDebugSettingsToStringFingerprint = fingerprint {
strings("MonetizationDebugSettings(") // Partial string match.
custom { method, _ -> method.name == "toString" }
}

View File

@@ -0,0 +1,9 @@
package app.revanced.patches.instagram.hide.explore
import app.revanced.patcher.fingerprint
internal val exploreResponseJsonParserFingerprint = fingerprint {
strings("sectional_items", "ExploreTopicalFeedResponse")
custom { method, _ -> method.name == "parseFromJson" }
}

View File

@@ -0,0 +1,33 @@
package app.revanced.patches.instagram.hide.explore
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Suppress("unused")
val hideExportFeedPatch = bytecodePatch(
name = "Hide explore feed",
description = "Hides posts and reels from the explore/search page.",
use = false
) {
compatibleWith("com.instagram.android")
execute {
exploreResponseJsonParserFingerprint.method.apply {
val sectionalItemStringIndex = exploreResponseJsonParserFingerprint.stringMatches!!.first().index
val sectionalItemStringRegister = getInstruction<OneRegisterInstruction>(sectionalItemStringIndex).registerA
/**
* Replacing the JSON key we want to skip with a random string that is not a valid JSON key.
* This way the feeds array will never be populated.
* Received JSON keys that are not handled are simply ignored, so there are no side effects.
*/
replaceInstruction(
sectionalItemStringIndex,
"const-string v$sectionalItemStringRegister, \"BOGUS\""
)
}
}
}

View File

@@ -23,7 +23,7 @@ internal val tabCreateButtonsLoopEndFingerprint = fingerprint {
Opcode.IPUT_OBJECT,
// Injection Jump
Opcode.ADD_INT_LIT8, //Increase Index
Opcode.GOTO_16 // Jump to loopStart
Opcode.GOTO // Jump to loopStart
// LoopEnd
)
}

View File

@@ -15,18 +15,20 @@ val hideNavigationButtonsPatch = bytecodePatch(
description = "Hides navigation bar buttons, such as the Reels and Create button.",
use = false
) {
compatibleWith("com.instagram.android")
compatibleWith("com.instagram.android"("397.1.0.52.81"))
val hideReels by booleanOption(
key = "hideReels",
default = true,
title = "Hide Reels"
title = "Hide Reels",
description = "Permanently hides the Reels button."
)
val hideCreate by booleanOption(
key = "hideCreate",
default = true,
title = "Hide Create"
title = "Hide Create",
description = "Permanently hides the Create button."
)
execute {

View File

@@ -0,0 +1,17 @@
package app.revanced.patches.instagram.hide.stories
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.Opcode
internal val getOrCreateAvatarViewFingerprint = fingerprint {
parameters()
returns("L")
custom { method, classDef ->
classDef.type == "Lcom/instagram/reels/ui/views/reelavatar/RecyclerReelAvatarView;"
}
opcodes(
Opcode.INVOKE_VIRTUAL,
Opcode.IPUT_OBJECT,
Opcode.INVOKE_VIRTUAL // Add View (Story)
)
}

View File

@@ -0,0 +1,20 @@
package app.revanced.patches.instagram.hide.stories
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.bytecodePatch
@Suppress("unused")
val hideStoriesPatch = bytecodePatch(
name = "Hide Stories from Home",
description = "Hides Stories from the main page, by removing the buttons.",
use = false
) {
compatibleWith("com.instagram.android")
execute {
val addStoryMethod = getOrCreateAvatarViewFingerprint.method // Creates Story
val addStoryEndIndex = getOrCreateAvatarViewFingerprint.patternMatch!!.endIndex
// Remove addView of Story.
addStoryMethod.removeInstruction(addStoryEndIndex)
}
}

View File

@@ -1,19 +1,49 @@
package app.revanced.patches.music.ad.video
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
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.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.settings.PreferenceScreen
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideVideoAdsPatch;"
@Suppress("unused")
val hideVideoAdsPatch = bytecodePatch(
name = "Hide music video ads",
description = "Hides ads that appear while listening to or streaming music videos, podcasts, or songs.",
description = "Adds an option to hide ads that appear while listening to or streaming music videos, podcasts, or songs.",
) {
compatibleWith("com.google.android.apps.youtube.music")
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52"
)
)
execute {
addResources("music", "ad.video.hideVideoAdsPatch")
PreferenceScreen.ADS.addPreferences(
SwitchPreference("revanced_music_hide_video_ads"),
)
navigate(showVideoAdsParentFingerprint.originalMethod)
.to(showVideoAdsParentFingerprint.patternMatch!!.startIndex + 1)
.stop()
.addInstruction(0, "const/4 p1, 0x0")
.addInstructions(
0,
"""
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->showVideoAds(Z)Z
move-result p1
"""
)
}
}

View File

@@ -1,6 +1,8 @@
package app.revanced.patches.music.audio.exclusiveaudio
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.util.returnEarly
@Suppress("unused")
@@ -8,7 +10,16 @@ val enableExclusiveAudioPlaybackPatch = bytecodePatch(
name = "Enable exclusive audio playback",
description = "Enables the option to play audio without video.",
) {
compatibleWith("com.google.android.apps.youtube.music")
dependsOn(
sharedExtensionPatch,
settingsPatch,
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52"
)
)
execute {
allowExclusiveAudioPlaybackFingerprint.method.returnEarly(true)

View File

@@ -4,23 +4,55 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
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.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.settings.PreferenceScreen
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.util.findFreeRegister
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/PermanentRepeatPatch;"
@Suppress("unused")
val permanentRepeatPatch = bytecodePatch(
name = "Permanent repeat",
description = "Permanently remember your repeating preference even if the playlist ends or another track is played.",
use = false,
description = "Adds an option to always repeat even if the playlist ends or another track is played."
) {
compatibleWith("com.google.android.apps.youtube.music")
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52"
)
)
execute {
addResources("music", "interaction.permanentrepeat.permanentRepeatPatch")
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_music_play_permanent_repeat"),
)
val startIndex = repeatTrackFingerprint.patternMatch!!.endIndex
val repeatIndex = startIndex + 1
repeatTrackFingerprint.method.apply {
// Start index is at a branch, but the same
// register is clobbered in both branch paths.
val freeRegister = findFreeRegister(startIndex + 1)
addInstructionsWithLabels(
startIndex,
"goto :repeat",
"""
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->permanentRepeat()Z
move-result v$freeRegister
if-nez v$freeRegister, :repeat
""",
ExternalLabel("repeat", instructions[repeatIndex]),
)
}

View File

@@ -9,7 +9,11 @@ val permanentShufflePatch = bytecodePatch(
description = "Permanently remember your shuffle preference " +
"even if the playlist ends or another track is played."
) {
compatibleWith("com.google.android.apps.youtube.music")
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52"
)
)
execute {
disableShuffleFingerprint.method.addInstruction(0, "return-void")

View File

@@ -1,30 +1,60 @@
package app.revanced.patches.music.layout.compactheader
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.settings.PreferenceScreen
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.findFreeRegister
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideCategoryBarPatch;"
@Suppress("unused")
val hideCategoryBar = bytecodePatch(
name = "Hide category bar",
description = "Hides the category bar at the top of the homepage.",
use = false,
description = "Adds an option to hide the category bar at the top of the homepage."
) {
compatibleWith("com.google.android.apps.youtube.music")
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52"
)
)
execute {
addResources("music", "layout.compactheader.hideCategoryBar")
PreferenceScreen.GENERAL.addPreferences(
SwitchPreference("revanced_music_hide_category_bar"),
)
constructCategoryBarFingerprint.method.apply {
val insertIndex = constructCategoryBarFingerprint.patternMatch!!.startIndex
val register = getInstruction<OneRegisterInstruction>(insertIndex - 1).registerA
val freeRegister = findFreeRegister(insertIndex, register)
addInstructions(
addInstructionsWithLabels(
insertIndex,
"""
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->hideCategoryBar()Z
move-result v$freeRegister
if-eqz v$freeRegister, :show
const/16 v$freeRegister, 0x8
invoke-virtual { v$register, v$freeRegister }, Landroid/view/View;->setVisibility(I)V
:show
nop
"""
)
}

View File

@@ -1,19 +1,44 @@
package app.revanced.patches.music.layout.premium
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
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.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.settings.PreferenceScreen
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideGetPremiumPatch;"
@Suppress("unused")
val hideGetPremiumPatch = bytecodePatch(
name = "Hide 'Get Music Premium' label",
description = "Hides the \"Get Music Premium\" label from the account menu and settings.",
name = "Hide 'Get Music Premium'",
description = "Adds an option to hide the \"Get Music Premium\" label in the settings and account menu.",
) {
compatibleWith("com.google.android.apps.youtube.music")
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52"
)
)
execute {
addResources("music", "layout.premium.hideGetPremiumPatch")
PreferenceScreen.ADS.addPreferences(
SwitchPreference("revanced_music_hide_get_premium_label"),
)
hideGetPremiumFingerprint.method.apply {
val insertIndex = hideGetPremiumFingerprint.patternMatch!!.endIndex
@@ -33,12 +58,17 @@ val hideGetPremiumPatch = bytecodePatch(
)
}
membershipSettingsFingerprint.method.addInstructions(
membershipSettingsFingerprint.method.addInstructionsWithLabels(
0,
"""
const/4 v0, 0x0
return-object v0
""",
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->hideGetPremiumLabel()Z
move-result v0
if-eqz v0, :show
const/4 v0, 0x0
return-object v0
:show
nop
"""
)
}
}

View File

@@ -7,20 +7,47 @@ import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.extensions.newLabel
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.toInstructions
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.settings.PreferenceScreen
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction22t
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideUpgradeButtonPatch;"
@Suppress("unused")
val removeUpgradeButtonPatch = bytecodePatch(
name = "Remove upgrade button",
description = "Removes the upgrade tab from the pivot bar.",
val hideUpgradeButton = bytecodePatch(
name = "Hide upgrade button",
description = "Hides the upgrade tab from the pivot bar.",
) {
compatibleWith("com.google.android.apps.youtube.music")
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52"
)
)
execute {
addResources("music", "layout.upgradebutton.hideUpgradeButtonPatch")
// TODO: Add an extension patch to allow this to be enabled/disabled in app.
if (false) {
PreferenceScreen.ADS.addPreferences(
SwitchPreference("revanced_music_hide_upgrade_button")
)
}
pivotBarConstructorFingerprint.method.apply {
val pivotBarElementFieldReference =
getInstruction(pivotBarConstructorFingerprint.patternMatch!!.endIndex - 1)
@@ -73,3 +100,9 @@ val removeUpgradeButtonPatch = bytecodePatch(
}
}
}
@Deprecated("Patch was renamed", ReplaceWith("hideUpgradeButton"))
@Suppress("unused")
val removeUpgradeButton = bytecodePatch{
dependsOn(hideUpgradeButton)
}

View File

@@ -1,6 +1,8 @@
package app.revanced.patches.music.misc.androidauto
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.util.returnEarly
@Suppress("unused")
@@ -8,7 +10,16 @@ val bypassCertificateChecksPatch = bytecodePatch(
name = "Bypass certificate checks",
description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.",
) {
compatibleWith("com.google.android.apps.youtube.music")
dependsOn(
sharedExtensionPatch,
settingsPatch
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52"
)
)
execute {
checkCertificateFingerprint.method.returnEarly(true)

View File

@@ -1,14 +1,25 @@
package app.revanced.patches.music.misc.backgroundplayback
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.util.returnEarly
val backgroundPlaybackPatch = bytecodePatch(
name = "Remove background playback restrictions",
description = "Removes restrictions on background playback, including playing kids videos in the background.",
) {
compatibleWith("com.google.android.apps.youtube.music")
dependsOn(
sharedExtensionPatch,
settingsPatch
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52"
)
)
execute {
kidsBackgroundPlaybackPolicyControllerFingerprint.method.addInstruction(
@@ -16,12 +27,6 @@ val backgroundPlaybackPatch = bytecodePatch(
"return-void",
)
backgroundPlaybackDisableFingerprint.method.addInstructions(
0,
"""
const/4 v0, 0x1
return v0
""",
)
backgroundPlaybackDisableFingerprint.method.returnEarly(true)
}
}

View File

@@ -1,12 +1,17 @@
package app.revanced.patches.music.misc.gms
import app.revanced.patcher.patch.Option
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
import app.revanced.patches.music.misc.spoof.spoofClientPatch
import app.revanced.patches.music.misc.settings.PreferenceScreen
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.music.misc.spoof.spoofVideoStreamsPatch
import app.revanced.patches.shared.castContextFetchFingerprint
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.patches.shared.primeMethodFingerprint
@Suppress("unused")
@@ -21,7 +26,7 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
extensionPatch = sharedExtensionPatch,
gmsCoreSupportResourcePatchFactory = ::gmsCoreSupportResourcePatch,
) {
dependsOn(spoofClientPatch)
dependsOn(spoofVideoStreamsPatch)
compatibleWith(MUSIC_PACKAGE_NAME)
}
@@ -33,4 +38,23 @@ private fun gmsCoreSupportResourcePatch(
toPackageName = REVANCED_MUSIC_PACKAGE_NAME,
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
spoofedPackageSignature = "afb0fed5eeaebdd86f56a97742f4b6b33ef59875",
)
executeBlock = {
addResources("shared", "misc.gms.gmsCoreSupportResourcePatch")
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
PreferenceScreen.MISC.addPreferences(
IntentPreference(
"microg_settings",
intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") {
"$gmsCoreVendorGroupId.android.gms"
}
)
)
}
) {
dependsOn(
addResourcesPatch,
settingsPatch
)
}

View File

@@ -0,0 +1,11 @@
package app.revanced.patches.music.misc.settings
import app.revanced.patcher.fingerprint
internal val googleApiActivityFingerprint = fingerprint {
returns("V")
parameters("Landroid/os/Bundle;")
custom { method, classDef ->
classDef.endsWith("GoogleApiActivity;") && method.name == "onCreate"
}
}

View File

@@ -0,0 +1,176 @@
package app.revanced.patches.music.misc.settings
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.*
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.settingsPatch
import app.revanced.util.*
import com.android.tools.smali.dexlib2.util.MethodUtil
private const val BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/shared/settings/BaseActivityHook;"
private const val GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/music/settings/GoogleApiActivityHook;"
private val preferences = mutableSetOf<BasePreference>()
private val settingsResourcePatch = resourcePatch {
dependsOn(
resourceMappingPatch,
settingsPatch(
IntentPreference(
titleKey = "revanced_settings_title",
summaryKey = null,
intent = newIntent("revanced_settings_intent"),
) to "settings_headers",
preferences
)
)
execute {
// TODO: Remove this when search will be abstract.
copyResources(
"settings",
ResourceGroup(
"layout",
"revanced_music_settings_with_toolbar.xml"
)
)
val targetResource = "values/styles.xml"
inputStreamFromBundledResource(
"settings/music",
targetResource,
)!!.let { inputStream ->
"resources".copyXmlNode(
document(inputStream),
document("res/$targetResource"),
).close()
}
// Remove horizontal divider from the settings Preferences.
val styleFile = get("res/values/styles.xml")
styleFile.writeText(
styleFile.readText()
.replace(
"allowDividerAbove\">true",
"allowDividerAbove\">false"
).replace(
"allowDividerBelow\">true",
"allowDividerBelow\">false"
)
)
}
}
val settingsPatch = bytecodePatch(
description = "Adds settings for ReVanced to YouTube Music.",
) {
dependsOn(
sharedExtensionPatch,
settingsResourcePatch,
addResourcesPatch,
)
execute {
addResources("music", "misc.settings.settingsPatch")
addResources("shared", "misc.debugging.enableDebuggingPatch")
// Should make a separate debugging patch, but for now include it with all installations.
PreferenceScreen.MISC.addPreferences(
PreferenceScreenPreference(
key = "revanced_debug_screen",
sorting = Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_debug"),
NonInteractivePreference(
"revanced_debug_export_logs_to_clipboard",
tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference",
selectable = true
),
NonInteractivePreference(
"revanced_debug_logs_clear_buffer",
tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference",
selectable = true
)
)
)
)
// Add an "About" preference to the top.
preferences += NonInteractivePreference(
key = "revanced_settings_music_screen_0_about",
summaryKey = null,
tag = "app.revanced.extension.shared.settings.preference.ReVancedAboutPreference",
selectable = true,
)
// Modify GoogleApiActivity and remove all existing layout code.
// Must modify an existing activity and cannot add a new activity to the manifest,
// as that fails for root installations.
googleApiActivityFingerprint.method.addInstructions(
1,
"""
invoke-static { }, $GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR->createInstance()Lapp/revanced/extension/music/settings/GoogleApiActivityHook;
move-result-object v0
invoke-static { v0, p0 }, $BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->initialize(Lapp/revanced/extension/shared/settings/BaseActivityHook;Landroid/app/Activity;)V
return-void
"""
)
// Remove other methods as they will break as the onCreate method is modified above.
googleApiActivityFingerprint.classDef.apply {
methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) }
}
}
finalize {
PreferenceScreen.close()
}
}
/**
* Creates an intent to open ReVanced settings.
*/
fun newIntent(settingsName: String) = IntentPreference.Intent(
data = settingsName,
targetClass = "com.google.android.gms.common.api.GoogleApiActivity"
) {
// The package name change has to be reflected in the intent.
setOrGetFallbackPackageName("com.google.android.apps.youtube.music")
}
object PreferenceScreen : BasePreferenceScreen() {
val ADS = Screen(
"revanced_settings_music_screen_1_ads",
summaryKey = null
)
val GENERAL = Screen(
"revanced_settings_music_screen_2_general",
summaryKey = null
)
val PLAYER = Screen(
"revanced_settings_music_screen_3_player",
summaryKey = null
)
val MISC = Screen(
"revanced_settings_music_screen_4_misc",
summaryKey = null
)
override fun commit(screen: PreferenceScreenPreference) {
preferences += screen
}
}

View File

@@ -1,39 +0,0 @@
package app.revanced.patches.music.misc.spoof
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val playerRequestConstructorFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
strings("player")
}
/**
* Matches using the class found in [playerRequestConstructorFingerprint].
*/
internal val createPlayerRequestBodyFingerprint = fingerprint {
parameters("L")
returns("V")
opcodes(
Opcode.CHECK_CAST,
Opcode.IGET,
Opcode.AND_INT_LIT16,
)
strings("ms")
}
/**
* Used to get a reference to other clientInfo fields.
*/
internal val setClientInfoFieldsFingerprint = fingerprint {
returns("L")
strings("Google Inc.")
}
/**
* Used to get a reference to the clientInfo and clientInfo.clientVersion field.
*/
internal val setClientInfoClientVersionFingerprint = fingerprint {
strings("10.29")
}

View File

@@ -1,105 +0,0 @@
package app.revanced.patches.music.misc.spoof
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.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/music/spoof/SpoofClientPatch;"
// TODO: Replace this patch with spoofVideoStreamsPatch once possible.
val spoofClientPatch = bytecodePatch(
name = "Spoof client",
description = "Spoofs the client to fix playback.",
) {
compatibleWith("com.google.android.apps.youtube.music")
dependsOn(
sharedExtensionPatch,
// TODO: Add settingsPatch
userAgentClientSpoofPatch,
)
execute {
val playerRequestClass = playerRequestConstructorFingerprint.classDef
val createPlayerRequestBodyMatch = createPlayerRequestBodyFingerprint.match(playerRequestClass)
val clientInfoContainerClass = createPlayerRequestBodyMatch.method
.getInstruction(createPlayerRequestBodyMatch.patternMatch!!.startIndex)
.getReference<TypeReference>()!!.type
val clientInfoField = setClientInfoClientVersionFingerprint.method.instructions.first {
it.opcode == Opcode.IPUT_OBJECT && it.getReference<FieldReference>()!!.definingClass == clientInfoContainerClass
}.getReference<FieldReference>()!!
val setClientInfoFieldInstructions = setClientInfoFieldsFingerprint.method.instructions.filter {
(it.opcode == Opcode.IPUT_OBJECT || it.opcode == Opcode.IPUT) &&
it.getReference<FieldReference>()!!.definingClass == clientInfoField.type
}.map { it.getReference<FieldReference>()!! }
// Offsets are known for the fields in the clientInfo object.
val clientIdField = setClientInfoFieldInstructions[0]
val clientModelField = setClientInfoFieldInstructions[5]
val osVersionField = setClientInfoFieldInstructions[7]
val clientVersionField = setClientInfoClientVersionFingerprint.method
.getInstruction(setClientInfoClientVersionFingerprint.stringMatches!!.first().index + 1)
.getReference<FieldReference>()
// Helper method to spoof the client info.
val spoofClientInfoMethod = ImmutableMethod(
playerRequestClass.type,
"spoofClientInfo",
listOf(ImmutableMethodParameter(clientInfoContainerClass, null, null)),
"V",
AccessFlags.PRIVATE.value or AccessFlags.STATIC.value,
null,
null,
MutableMethodImplementation(3),
).toMutable().also(playerRequestClass.methods::add).apply {
addInstructions(
"""
iget-object v0, p0, $clientInfoField
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientId()I
move-result v1
iput v1, v0, $clientIdField
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientModel()Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $clientModelField
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientVersion()Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $clientVersionField
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getOsVersion()Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $osVersionField
return-void
""",
)
}
createPlayerRequestBodyMatch.method.apply {
val checkCastIndex = createPlayerRequestBodyMatch.patternMatch!!.startIndex
val clientInfoContainerRegister = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA
addInstruction(checkCastIndex + 1, "invoke-static {v$clientInfoContainerRegister}, $spoofClientInfoMethod")
}
}
}

View File

@@ -0,0 +1,59 @@
package app.revanced.patches.music.misc.spoof
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.gms.musicActivityOnCreateFingerprint
import app.revanced.patches.music.misc.settings.PreferenceScreen
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.music.playservice.is_7_33_or_greater
import app.revanced.patches.music.playservice.is_8_11_or_greater
import app.revanced.patches.music.playservice.is_8_15_or_greater
import app.revanced.patches.music.playservice.versionCheckPatch
import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/spoof/SpoofVideoStreamsPatch;"
val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
fixMediaFetchHotConfigChanges = { true },
fixMediaFetchHotConfigAlternativeChanges = { is_8_11_or_greater && !is_8_15_or_greater },
fixParsePlaybackResponseFeatureFlag = { is_7_33_or_greater },
block = {
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
versionCheckPatch,
userAgentClientSpoofPatch
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52"
)
)
},
executeBlock = {
addResources("shared", "misc.spoof.spoofVideoStreamsPatch")
PreferenceScreen.MISC.addPreferences(
PreferenceScreenPreference(
key = "revanced_spoof_video_streams_screen",
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_spoof_video_streams"),
ListPreference("revanced_spoof_video_streams_client_type"),
)
)
)
musicActivityOnCreateFingerprint.method.addInstruction(
0,
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setClientOrderToUse()V"
)
}
)

View File

@@ -0,0 +1,28 @@
@file:Suppress("ktlint:standard:property-naming")
package app.revanced.patches.music.playservice
import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.findPlayStoreServicesVersion
var is_7_33_or_greater = false
private set
var is_8_11_or_greater = false
private set
var is_8_15_or_greater = false
private set
val versionCheckPatch = resourcePatch(
description = "Uses the Play Store service version to find the major/minor version of the YouTube Music target app.",
) {
execute {
// The app version is missing from the decompiled manifest,
// so instead use the Google Play services version and compare against specific releases.
val playStoreServicesVersion = findPlayStoreServicesVersion()
// All bug fix releases always seem to use the same play store version as the minor version.
is_7_33_or_greater = 245199000 <= playStoreServicesVersion
is_8_11_or_greater = 251199000 <= playStoreServicesVersion
is_8_15_or_greater = 251530000 <= playStoreServicesVersion
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.patches.reddit.customclients
import app.revanced.patcher.patch.BytecodePatchBuilder
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.bytecodePatch
const val INSTALL_NEW_CLIENT_METHOD = "install(Lokhttp3/OkHttpClient${'$'}Builder;)Lokhttp3/OkHttpClient;"
const val CREATE_NEW_CLIENT_METHOD = "createClient()Lokhttp3/OkHttpClient;"
fun fixRedgifsApiPatch(
extensionPatch: Patch<*>,
block: BytecodePatchBuilder.() -> Unit = {},
) = bytecodePatch(name = "Fix Redgifs API") {
dependsOn(extensionPatch)
block()
}

View File

@@ -0,0 +1,13 @@
package app.revanced.patches.reddit.customclients.baconreader.fix.redgifs
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal val getOkHttpClientFingerprint = fingerprint {
returns("Lokhttp3/OkHttpClient;")
parameters()
custom { method, classDef ->
classDef.type == "Lcom/onelouder/baconreader/media/gfycat/RedGifsManager;" && method.name == "getOkhttpClient"
}
}

View File

@@ -0,0 +1,53 @@
package app.revanced.patches.reddit.customclients.baconreader.fix.redgifs
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patches.reddit.customclients.INSTALL_NEW_CLIENT_METHOD
import app.revanced.patches.reddit.customclients.baconreader.misc.extension.sharedExtensionPatch
import app.revanced.patches.reddit.customclients.fixRedgifsApiPatch
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.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/baconreader/FixRedgifsApiPatch;"
@Suppress("unused")
val fixRedgifsApi = fixRedgifsApiPatch(
extensionPatch = sharedExtensionPatch
) {
compatibleWith(
"com.onelouder.baconreader",
"com.onelouder.baconreader.premium",
)
execute {
// region Patch Redgifs OkHttp3 client.
getOkHttpClientFingerprint.method.apply {
// Remove conflicting OkHttp interceptors.
val originalInterceptorInstallIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.NEW_INSTANCE && getReference<TypeReference>()?.type == "Lcom/onelouder/baconreader/media/gfycat/RedGifsManager\$HeaderInterceptor;"
}
removeInstructions(originalInterceptorInstallIndex, 5)
val index = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.name == "build" && reference.definingClass == "Lokhttp3/OkHttpClient\$Builder;"
}
val register = getInstruction<FiveRegisterInstruction>(index).registerC
replaceInstruction(
index,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->$INSTALL_NEW_CLIENT_METHOD
"""
)
}
// endregion
}
}

View File

@@ -0,0 +1,6 @@
package app.revanced.patches.reddit.customclients.baconreader.misc.extension
import app.revanced.patches.reddit.customclients.baconreader.misc.extension.hooks.initHook
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
val sharedExtensionPatch = sharedExtensionPatch("baconreader", initHook)

View File

@@ -0,0 +1,9 @@
package app.revanced.patches.reddit.customclients.baconreader.misc.extension.hooks
import app.revanced.patches.shared.misc.extension.extensionHook
internal val initHook = extensionHook {
custom { method, _ ->
method.definingClass == "Lcom/onelouder/baconreader/BaconReader;" && method.name == "onCreate"
}
}

View File

@@ -0,0 +1,20 @@
package app.revanced.patches.reddit.customclients.boostforreddit.fix.redgifs
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val createOkHttpClientFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE)
opcodes(
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT
)
custom { _, classDef -> classDef.sourceFile == "RedGifsAPIv2.java" }
}

View File

@@ -0,0 +1,38 @@
package app.revanced.patches.reddit.customclients.boostforreddit.fix.redgifs
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patches.reddit.customclients.CREATE_NEW_CLIENT_METHOD
import app.revanced.patches.reddit.customclients.boostforreddit.misc.extension.sharedExtensionPatch
import app.revanced.patches.reddit.customclients.fixRedgifsApiPatch
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/boostforreddit/FixRedgifsApiPatch;"
@Suppress("unused")
val fixRedgifsApi = fixRedgifsApiPatch(
extensionPatch = sharedExtensionPatch
) {
compatibleWith("com.rubenmayayo.reddit")
execute {
// region Patch Redgifs OkHttp3 client.
createOkHttpClientFingerprint.method.apply {
val index = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.name == "build" && reference.definingClass == "Lokhttp3/OkHttpClient\$Builder;"
}
replaceInstruction(
index,
"""
invoke-static { }, ${EXTENSION_CLASS_DESCRIPTOR}->$CREATE_NEW_CLIENT_METHOD
"""
)
}
// endregion
}
}

View File

@@ -0,0 +1,39 @@
package app.revanced.patches.reddit.customclients.sync.syncforreddit.fix.redgifs
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.fingerprint
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.writeRegister
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction11n
internal val createOkHttpClientFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("V")
parameters()
custom { method, classDef ->
// There are four functions (each creating a client) defined in this file with very similar fingerprints.
// We're looking for the one that only creates one object (the builder) and sets client options true
// (thus never reloading the register with a 0).
classDef.sourceFile == "OkHttpHelper.java" &&
method.instructions.count { it.opcode == Opcode.NEW_INSTANCE } == 1 &&
method.indexOfFirstInstruction {
opcode == Opcode.CONST_4 && writeRegister == 1 && (this as Instruction11n).narrowLiteral == 0
} == -1
}
}
internal val getDefaultUserAgentFingerprint = fingerprint {
custom { method, classDef ->
method.name == "getDefaultUserAgent" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
}
}
internal val getOriginalUserAgentFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("Ljava/lang/String;")
parameters()
custom { _, classDef -> classDef.sourceFile == "AccountSingleton.java" }
}

View File

@@ -0,0 +1,56 @@
package app.revanced.patches.reddit.customclients.sync.syncforreddit.fix.redgifs
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patches.reddit.customclients.INSTALL_NEW_CLIENT_METHOD
import app.revanced.patches.reddit.customclients.fixRedgifsApiPatch
import app.revanced.patches.reddit.customclients.sync.syncforreddit.extension.sharedExtensionPatch
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/syncforreddit/FixRedgifsApiPatch;"
@Suppress("unused")
val fixRedgifsApi = fixRedgifsApiPatch(
extensionPatch = sharedExtensionPatch
) {
compatibleWith(
"com.laurencedawson.reddit_sync",
"com.laurencedawson.reddit_sync.pro",
"com.laurencedawson.reddit_sync.dev",
)
execute {
// region Patch Redgifs OkHttp3 client.
createOkHttpClientFingerprint.method.apply {
val index = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.name == "build" && reference.definingClass == "Lokhttp3/OkHttpClient\$Builder;"
}
val register = getInstruction<FiveRegisterInstruction>(index).registerC
replaceInstruction(
index,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->$INSTALL_NEW_CLIENT_METHOD
"""
)
}
getDefaultUserAgentFingerprint.method.apply {
addInstructions(
0,
"""
invoke-static { }, ${getOriginalUserAgentFingerprint.method}
move-result-object v0
return-object v0
"""
)
}
// endregion
}
}

View File

@@ -1,9 +1,12 @@
package app.revanced.patches.shared.misc.spoof
import app.revanced.patcher.fingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val buildInitPlaybackRequestFingerprint = fingerprint {
returns("Lorg/chromium/net/UrlRequest\$Builder;")
@@ -35,8 +38,15 @@ internal val buildPlayerRequestURIFingerprint = fingerprint {
internal val buildRequestFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("Lorg/chromium/net/UrlRequest;")
returns("Lorg/chromium/net/UrlRequest") // UrlRequest; or UrlRequest$Builder;
custom { methodDef, _ ->
if (methodDef.indexOfFirstInstruction {
val reference = getReference<MethodReference>()
reference?.name == "newUrlRequestBuilder"
} < 0) {
return@custom false
}
// Different targets have slightly different parameters
// Earlier targets have parameters:
@@ -58,12 +68,22 @@ internal val buildRequestFingerprint = fingerprint {
// Lorg/chromium/net/UrlRequest\$Callback;
// L
// 20.16+ uses a refactored and extracted method:
// L
// Ljava/util/Map;
// [B
// L
// Lorg/chromium/net/UrlRequest$Callback;
// L
val parameterTypes = methodDef.parameterTypes
(parameterTypes.size == 7 || parameterTypes.size == 8) &&
parameterTypes[1] == "Ljava/util/Map;" // URL headers.
val parameterTypesSize = parameterTypes.size
(parameterTypesSize == 6 || parameterTypesSize == 7 || parameterTypesSize == 8) &&
parameterTypes[1] == "Ljava/util/Map;" // URL headers.
}
}
internal val protobufClassParseByteBufferFingerprint = fingerprint {
accessFlags(AccessFlags.PROTECTED, AccessFlags.STATIC)
returns("L")
@@ -148,7 +168,8 @@ internal val mediaFetchHotConfigFingerprint = fingerprint {
literal { MEDIA_FETCH_HOT_CONFIG_FEATURE_FLAG }
}
// 20.10+
// YT 20.10+, YT Music 8.11 - 8.14.
// Flag is missing in YT Music 8.15+, and it is not known if a replacement flag/feature exists.
internal const val MEDIA_FETCH_HOT_CONFIG_ALTERNATIVE_FEATURE_FLAG = 45683169L
internal val mediaFetchHotConfigAlternativeFingerprint = fingerprint {
@@ -162,7 +183,6 @@ internal val mediaFetchHotConfigAlternativeFingerprint = fingerprint {
internal const val PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG = 45665455L
internal val playbackStartDescriptorFeatureFlagFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters()
returns("Z")
literal { PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG }

View File

@@ -9,6 +9,7 @@ import app.revanced.patcher.patch.BytecodePatchBuilder
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.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.util.findFreeRegister
import app.revanced.util.findInstructionIndicesReversedOrThrow
@@ -45,6 +46,8 @@ fun spoofVideoStreamsPatch(
dependsOn(addResourcesPatch)
execute {
addResources("shared", "misc.fix.playback.spoofVideoStreamsPatch")
// region Enable extension helper method used by other patches
patchIncludedExtensionMethodFingerprint.method.returnEarly(true)
@@ -53,9 +56,8 @@ fun spoofVideoStreamsPatch(
// region Block /initplayback requests to fall back to /get_watch requests.
val moveUriStringIndex = buildInitPlaybackRequestFingerprint.patternMatch!!.startIndex
buildInitPlaybackRequestFingerprint.method.apply {
val moveUriStringIndex = buildInitPlaybackRequestFingerprint.patternMatch!!.startIndex
val targetRegister = getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
addInstructions(
@@ -63,7 +65,7 @@ fun spoofVideoStreamsPatch(
"""
invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$targetRegister
""",
"""
)
}
@@ -71,9 +73,8 @@ fun spoofVideoStreamsPatch(
// region Block /get_watch requests to fall back to /player requests.
val invokeToStringIndex = buildPlayerRequestURIFingerprint.patternMatch!!.startIndex
buildPlayerRequestURIFingerprint.method.apply {
val invokeToStringIndex = buildPlayerRequestURIFingerprint.patternMatch!!.startIndex
val uriRegister = getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
addInstructions(
@@ -81,7 +82,7 @@ fun spoofVideoStreamsPatch(
"""
invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
move-result-object v$uriRegister
""",
"""
)
}
@@ -178,9 +179,9 @@ fun spoofVideoStreamsPatch(
:disabled
return-void
""",
"""
)
},
}
)
}
@@ -199,17 +200,17 @@ fun spoofVideoStreamsPatch(
addInstructions(
targetIndex,
"""
# Field a: Stream uri.
# Field c: Http method.
# Field d: Post data.
move-object v0, p0 # method has over 15 registers and must copy p0 to a lower register.
iget-object v1, v0, $definingClass->a:Landroid/net/Uri;
iget v2, v0, $definingClass->c:I
iget-object v3, v0, $definingClass->d:[B
invoke-static { v1, v2, v3 }, $EXTENSION_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B
move-result-object v1
iput-object v1, v0, $definingClass->d:[B
""",
# Field a: Stream uri.
# Field c: Http method.
# Field d: Post data.
move-object v0, p0 # method has over 15 registers and must copy p0 to a lower register.
iget-object v1, v0, $definingClass->a:Landroid/net/Uri;
iget v2, v0, $definingClass->c:I
iget-object v3, v0, $definingClass->d:[B
invoke-static { v1, v2, v3 }, $EXTENSION_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B
move-result-object v1
iput-object v1, v0, $definingClass->d:[B
"""
)
}

View File

@@ -187,11 +187,16 @@ val customThemePatch = resourcePatch(
}
// Login screen gradient.
document("res/drawable/start_screen_gradient.xml").use { document ->
val gradientNode = document.getElementsByTagName("gradient").item(0) as Element
try {
document("res/drawable/start_screen_gradient.xml").use { document ->
val gradientNode = document.getElementsByTagName("gradient").item(0) as Element
gradientNode.setAttribute("android:startColor", "@color/gray_7")
gradientNode.setAttribute("android:endColor", "@color/gray_7")
gradientNode.setAttribute("android:startColor", "@color/gray_7")
gradientNode.setAttribute("android:endColor", "@color/gray_7")
}
} catch (_: Exception) {
// Fails for 9.0.66+
// printWarn("Failed to locate start_screen_gradient.xml, skipping modification.")
}
}
}

View File

@@ -1,145 +0,0 @@
package app.revanced.patches.spotify.misc
import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
context(BytecodePatchContext)
internal val accountAttributeFingerprint get() = fingerprint {
custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/AccountAttribute;" }
}
context(BytecodePatchContext)
internal val productStateProtoGetMapFingerprint get() = fingerprint {
returns("Ljava/util/Map;")
custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/ProductStateProto;" }
}
internal val buildQueryParametersFingerprint = fingerprint {
strings("trackRows", "device_type:tablet")
}
internal val contextMenuViewModelClassFingerprint = fingerprint {
strings("ContextMenuViewModel(header=")
}
/**
* Used in versions older than "9.0.60.128".
*/
internal val oldContextMenuViewModelAddItemFingerprint = fingerprint {
parameters("L")
returns("V")
custom { method, _ ->
method.indexOfFirstInstruction {
getReference<MethodReference>()?.name == "add"
} >= 0
}
}
internal val contextMenuViewModelConstructorFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
}
/**
* Used to find the interface name of a context menu item.
*/
internal val removeItemFromPlaylistContextMenuItemClassFingerprint = fingerprint {
strings("spotify:playlist:", "REMOVE_TRACK")
}
internal const val CONTEXT_MENU_ITEM_CLASS_DESCRIPTOR_PLACEHOLDER = "Lapp/revanced/ContextMenuItemPlaceholder;"
internal val extensionFilterContextMenuItemsFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("Ljava/util/List;")
parameters("Ljava/util/List;")
custom { method, classDef ->
method.name == "filterContextMenuItems" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
}
}
internal val getViewModelFingerprint = fingerprint {
custom { method, _ -> method.name == "getViewModel" }
}
internal val contextFromJsonFingerprint = fingerprint {
opcodes(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_STATIC
)
custom { method, classDef ->
method.name == "fromJson" &&
classDef.type.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
}
}
internal val readPlayerOptionOverridesFingerprint = fingerprint {
custom { method, classDef ->
method.name == "readPlayerOptionOverrides" &&
classDef.type.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
}
}
internal val protobufListsFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
custom { method, _ -> method.name == "emptyProtobufList" }
}
internal val abstractProtobufListEnsureIsMutableFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters()
returns("V")
custom { method, _ ->
method.indexOfFirstInstruction {
getReference<TypeReference>()?.type == "Ljava/lang/UnsupportedOperationException;"
} >= 0
}
}
internal fun structureGetSectionsFingerprint(className: String) = fingerprint {
custom { method, classDef ->
classDef.type.endsWith(className) && method.indexOfFirstInstruction {
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.name == "sections_"
} >= 0
}
}
internal val homeSectionFingerprint = fingerprint {
custom { _, classDef -> classDef.type.endsWith("homeapi/proto/Section;") }
}
internal val homeStructureGetSectionsFingerprint =
structureGetSectionsFingerprint("homeapi/proto/HomeStructure;")
internal val browseSectionFingerprint = fingerprint {
custom { _, classDef-> classDef.type.endsWith("browsita/v1/resolved/Section;") }
}
internal val browseStructureGetSectionsFingerprint =
structureGetSectionsFingerprint("browsita/v1/resolved/BrowseStructure;")
internal fun reactivexFunctionApplyWithClassInitFingerprint(className: String) = fingerprint {
returns("Ljava/lang/Object;")
parameters("Ljava/lang/Object;")
custom { method, _ ->
method.name == "apply" && method.indexOfFirstInstruction {
opcode == Opcode.NEW_INSTANCE && getReference<TypeReference>()?.type?.endsWith(className) == true
} >= 0
}
}
internal const val PENDRAGON_JSON_FETCH_MESSAGE_REQUEST_CLASS_NAME = "FetchMessageRequest;"
internal val pendragonJsonFetchMessageRequestFingerprint =
reactivexFunctionApplyWithClassInitFingerprint(PENDRAGON_JSON_FETCH_MESSAGE_REQUEST_CLASS_NAME)
internal const val PENDRAGON_PROTO_FETCH_MESSAGE_LIST_REQUEST_CLASS_NAME = "FetchMessageListRequest;"
internal val pendragonProtoFetchMessageListRequestFingerprint =
reactivexFunctionApplyWithClassInitFingerprint(PENDRAGON_PROTO_FETCH_MESSAGE_LIST_REQUEST_CLASS_NAME)

View File

@@ -12,9 +12,9 @@ import app.revanced.util.returnEarly
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/fix/SpoofClientPatch;"
@Deprecated("Patch no longer functions")
@Suppress("unused")
val spoofClientPatch = bytecodePatch(
name = "Spoof client",
description = "Spoofs the client to fix various functions of the app.",
) {
val requestListenerPort by intOption(

View File

@@ -1,12 +1,9 @@
package app.revanced.patches.spotify.navbar
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.spotify.misc.unlockPremiumPatch
@Deprecated("Superseded by unlockPremiumPatch", ReplaceWith("unlockPremiumPatch"))
@Deprecated("Obsolete and will be deleted soon")
@Suppress("unused")
val premiumNavbarTabPatch = bytecodePatch(
description = "Hides the premium tab from the navigation bar.",
) {
dependsOn(unlockPremiumPatch)
}
)

View File

@@ -0,0 +1,13 @@
package app.revanced.patches.viber.ads
import app.revanced.patcher.fingerprint
internal val adsFreeFingerprint = fingerprint {
returns("I")
parameters()
custom { method, classDef ->
classDef.type.contains("com/viber/voip/feature/viberplus") &&
classDef.superclass?.contains("com/viber/voip/core/feature") == true && // Must extend com.viber.voip.core.feature.?
classDef.methods.count() == 1
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.patches.viber.ads
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val hideAdsPatch = bytecodePatch(
name = "Hide Ads",
description = "Hides ad banners between chats.",
) {
compatibleWith("com.viber.voip"("25.9.2.0"))
execute {
// Return 1 (true) indicating ads should be disabled.
adsFreeFingerprint.method.returnEarly(1)
}
}

View File

@@ -89,7 +89,7 @@ val downloadsPatch = bytecodePatch(
// Main activity is used to launch downloader intent.
mainActivityOnCreateFingerprint.method.addInstruction(
1,
0,
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->activityCreated(Landroid/app/Activity;)V"
)

View File

@@ -1,6 +1,7 @@
package app.revanced.patches.youtube.layout.seekbar
import app.revanced.patcher.fingerprint
import app.revanced.patches.youtube.shared.YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
import app.revanced.util.containsLiteralInstruction
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
@@ -103,7 +104,7 @@ internal val launchScreenLayoutTypeFingerprint = fingerprint {
custom { method, _ ->
val firstParameter = method.parameterTypes.firstOrNull()
// 19.25 - 19.45
(firstParameter == "Lcom/google/android/apps/youtube/app/watchwhile/MainActivity;"
(firstParameter == YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
|| firstParameter == "Landroid/app/Activity;") // 19.46+
&& method.containsLiteralInstruction(launchScreenLayoutTypeLotteFeatureFlag)
}

View File

@@ -68,7 +68,7 @@ val shortsAutoplayPatch = bytecodePatch(
// Main activity is used to check if app is in pip mode.
mainActivityOnCreateFingerprint.method.addInstruction(
1,
0,
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->setMainActivity(Landroid/app/Activity;)V",
)

View File

@@ -90,8 +90,8 @@ val openShortsInRegularPlayerPatch = bytecodePatch(
// Activity is used as the context to launch an Intent.
mainActivityOnCreateFingerprint.method.addInstruction(
1,
"invoke-static/range { p0 .. p0 }, ${EXTENSION_CLASS_DESCRIPTOR}->" +
0,
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" +
"setMainActivity(Landroid/app/Activity;)V",
)

View File

@@ -1,8 +1,11 @@
package app.revanced.patches.youtube.layout.sponsorblock
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.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@@ -56,3 +59,20 @@ internal val rectangleFieldInvalidatorFingerprint = fingerprint {
reference?.parameterTypes?.size == 1 && reference.name == "invalidate" // the reference is the invalidate(..) method
}
}
internal val adProgressTextViewVisibilityFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters("Z")
custom { method, _ ->
indexOfAdProgressTextViewVisibilityInstruction(method) >= 0
}
}
internal fun indexOfAdProgressTextViewVisibilityInstruction(method: Method) =
method.indexOfFirstInstructionReversed {
val reference = getReference<MethodReference>()
reference?.definingClass ==
"Lcom/google/android/libraries/youtube/ads/player/ui/AdProgressTextView;"
&& reference.name =="setVisibility"
}

View File

@@ -252,5 +252,16 @@ val sponsorBlockPatch = bytecodePatch(
}
} ?: throw PatchException("Could not find the method which contains the replaceMeWith* strings")
}
adProgressTextViewVisibilityFingerprint.method.apply {
val index = indexOfAdProgressTextViewVisibilityInstruction(this)
val register = getInstruction<FiveRegisterInstruction>(index).registerD
addInstructionsAtControlFlowLabel(
index,
"invoke-static { v$register }, $EXTENSION_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setAdProgressTextVisibility(I)V"
)
}
}
}

View File

@@ -1,6 +1,7 @@
package app.revanced.patches.youtube.layout.theme
import app.revanced.patcher.fingerprint
import app.revanced.patches.youtube.shared.YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@@ -37,6 +38,6 @@ internal val splashScreenStyleFingerprint = fingerprint {
parameters("Landroid/os/Bundle;")
literal { SPLASH_SCREEN_STYLE_FEATURE_FLAG }
custom { method, classDef ->
method.name == "onCreate" && classDef.endsWith("/MainActivity;")
method.name == "onCreate" && classDef.type == YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
}
}

View File

@@ -40,9 +40,7 @@ val announcementsPatch = bytecodePatch(
)
mainActivityOnCreateFingerprint.method.addInstruction(
// Insert index must be greater than the insert index used by GmsCoreSupport,
// as both patch the same method and GmsCore check should be first.
1,
0,
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->showAnnouncement(Landroid/app/Activity;)V",
)
}

View File

@@ -22,6 +22,8 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/EnableDebuggingPatch;"
// TODO: Refactor this into a shared patch that can be used by both YT and YT Music.
// Almost all of the feature flag hooks are the same between both apps.
val enableDebuggingPatch = bytecodePatch(
name = "Enable debugging",
description = "Adds options for debugging and exporting ReVanced logs to the clipboard.",
@@ -45,6 +47,7 @@ val enableDebuggingPatch = bytecodePatch(
)
execute {
addResources("shared", "misc.debugging.enableDebuggingPatch")
addResources("youtube", "misc.debugging.enableDebuggingPatch")
PreferenceScreen.MISC.addPreferences(
@@ -58,13 +61,13 @@ val enableDebuggingPatch = bytecodePatch(
SwitchPreference("revanced_debug_toast_on_error"),
NonInteractivePreference(
"revanced_debug_export_logs_to_clipboard",
tag = "app.revanced.extension.youtube.settings.preference.ExportLogToClipboardPreference",
selectable = true,
tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference",
selectable = true
),
NonInteractivePreference(
"revanced_debug_logs_clear_buffer",
tag = "app.revanced.extension.youtube.settings.preference.ClearLogBufferPreference",
selectable = true,
tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference",
selectable = true
),
),
),

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