Compare commits

..

57 Commits

Author SHA1 Message Date
semantic-release-bot
0acba30245 chore: Release v5.42.0-dev.11 [skip ci]
# [5.42.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.10...v5.42.0-dev.11) (2025-10-03)

### Bug Fixes

* **YouTube:** Resolve UI components not hiding for some users ([#6054](https://github.com/ReVanced/revanced-patches/issues/6054)) ([6b26346](6b26346914))
2025-10-03 06:57:20 +00:00
LisoUseInAIKyrios
6b26346914 fix(YouTube): Resolve UI components not hiding for some users (#6054) 2025-10-03 10:54:44 +04:00
github-actions[bot]
b1511c732d chore: Sync translations (#6055) 2025-10-03 10:53:07 +04:00
semantic-release-bot
26117e744c chore: Release v5.42.0-dev.10 [skip ci]
# [5.42.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.9...v5.42.0-dev.10) (2025-10-02)

### Bug Fixes

* **YouTube - Spoof video streams:** Resolve playback dropping frames ([#6051](https://github.com/ReVanced/revanced-patches/issues/6051)) ([a62ee43](a62ee43441))
2025-10-02 20:09:02 +00:00
LisoUseInAIKyrios
a62ee43441 fix(YouTube - Spoof video streams): Resolve playback dropping frames (#6051) 2025-10-03 00:05:23 +04:00
LisoUseInAIKyrios
6a799110d7 refactor(YouTube - Spoof video streams): Add 'supportsMultiAudioTracks' field 2025-10-01 22:37:10 +04:00
dependabot[bot]
aec17b93f7 chore(deps): Bump com.google.guava:guava from 33.4.0-jre to 33.5.0-jre (#6042) 2025-10-01 22:18:24 +04:00
dependabot[bot]
e7a1706be4 chore(deps): Bump actions/setup-node from 4 to 5 (#6038) 2025-10-01 22:16:40 +04:00
dependabot[bot]
9469604fe0 chore(deps-dev): Bump semantic-release from 24.2.7 to 24.2.9 (#6040) 2025-10-01 22:15:52 +04:00
semantic-release-bot
1a3a12df1a chore: Release v5.42.0-dev.9 [skip ci]
# [5.42.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.8...v5.42.0-dev.9) (2025-10-01)

### Bug Fixes

* **Custom branding:** Update ReVanced logo sizing ([#6029](https://github.com/ReVanced/revanced-patches/issues/6029)) ([ae4b947](ae4b9474d3))
2025-10-01 17:33:14 +00:00
MarcaD
ae4b9474d3 fix(Custom branding): Update ReVanced logo sizing (#6029) 2025-10-01 21:29:05 +04:00
github-actions[bot]
83ccd9d3f1 chore: Sync translations (#6037) 2025-10-01 19:04:51 +04:00
semantic-release-bot
526c7c05e2 chore: Release v5.42.0-dev.8 [skip ci]
# [5.42.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.7...v5.42.0-dev.8) (2025-10-01)

### Bug Fixes

* **YouTube - Force original language:** Resolve some videos using Swedish audio track ([9d67316](9d6731660b))

### Features

* **YouTube Music:** Add `Force original audio` patch ([#6036](https://github.com/ReVanced/revanced-patches/issues/6036)) ([d0d53d1](d0d53d109e))
2025-10-01 15:04:39 +00:00
LisoUseInAIKyrios
d0d53d109e feat(YouTube Music): Add Force original audio patch (#6036) 2025-10-01 18:59:16 +04:00
LisoUseInAIKyrios
9d6731660b fix(YouTube - Force original language): Resolve some videos using Swedish audio track 2025-10-01 18:57:53 +04:00
semantic-release-bot
5a7e199162 chore: Release v5.42.0-dev.7 [skip ci]
# [5.42.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.6...v5.42.0-dev.7) (2025-10-01)

### Features

* **Instagram:** Add `Open links externally` patch ([#6012](https://github.com/ReVanced/revanced-patches/issues/6012)) ([08e8ead](08e8ead04f))
2025-10-01 06:25:01 +00:00
LisoUseInAIKyrios
0c662c8e3b chore(deps): Bump actions/checkout from 4 to 5 2025-10-01 10:19:09 +04:00
Swakshan
08e8ead04f feat(Instagram): Add Open links externally patch (#6012) 2025-10-01 10:17:19 +04:00
semantic-release-bot
d238a42708 chore: Release v5.42.0-dev.6 [skip ci]
# [5.42.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.5...v5.42.0-dev.6) (2025-09-30)

### Bug Fixes

* **X / Twitter:** Remove non functional and obsolete patch `Open links with app chooser` ([#6033](https://github.com/ReVanced/revanced-patches/issues/6033)) ([673609c](673609c2aa))
2025-09-30 20:09:49 +00:00
LisoUseInAIKyrios
673609c2aa fix(X / Twitter): Remove non functional and obsolete patch Open links with app chooser (#6033) 2025-10-01 00:06:40 +04:00
github-actions[bot]
5f1a485e8f chore: Sync translations (#6034) 2025-10-01 00:06:22 +04:00
LisoUseInAIKyrios
6961babee9 refactor(YouTube - Check watch history domain name resolution): Do not show redundant dialog cancel button 2025-09-30 12:11:06 +04:00
semantic-release-bot
328c9b6bbe chore: Release v5.42.0-dev.5 [skip ci]
# [5.42.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.4...v5.42.0-dev.5) (2025-09-28)

### Features

* **YouTube Music:** Add `Custom branding` patch ([#6007](https://github.com/ReVanced/revanced-patches/issues/6007)) ([4c8b56f](4c8b56f546))
2025-09-28 11:30:51 +00:00
MarcaD
4c8b56f546 feat(YouTube Music): Add Custom branding patch (#6007)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-09-28 15:26:12 +04:00
semantic-release-bot
1754023dd6 chore: Release v5.42.0-dev.4 [skip ci]
# [5.42.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.3...v5.42.0-dev.4) (2025-09-28)

### Bug Fixes

* **YouTube Music - GmsCore support:** Handle sharing links to certain apps such as Instagram ([#6026](https://github.com/ReVanced/revanced-patches/issues/6026)) ([328234f](328234f39a))
2025-09-28 10:34:39 +00:00
LisoUseInAIKyrios
328234f39a fix(YouTube Music - GmsCore support): Handle sharing links to certain apps such as Instagram (#6026) 2025-09-28 14:31:40 +04:00
github-actions[bot]
326953cfc3 chore: Sync translations (#6028) 2025-09-28 14:30:01 +04:00
semantic-release-bot
725d5dc974 chore: Release v5.42.0-dev.3 [skip ci]
# [5.42.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.2...v5.42.0-dev.3) (2025-09-28)

### Bug Fixes

* **YouTube - Hide end screen cards:** Hide new type of end screen card ([#6027](https://github.com/ReVanced/revanced-patches/issues/6027)) ([76b0364](76b0364c5b))
2025-09-28 10:25:37 +00:00
LisoUseInAIKyrios
76b0364c5b fix(YouTube - Hide end screen cards): Hide new type of end screen card (#6027) 2025-09-28 14:22:42 +04:00
semantic-release-bot
1cbff799ad chore: Release v5.42.0-dev.2 [skip ci]
# [5.42.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.1...v5.42.0-dev.2) (2025-09-27)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Resolve app startup crash ([080a226](080a226614))
2025-09-27 19:57:01 +00:00
LisoUseInAIKyrios
080a226614 fix(Instagram - Hide navigation buttons): Resolve app startup crash 2025-09-27 23:53:35 +04:00
semantic-release-bot
2b71bd80c2 chore: Release v5.42.0-dev.1 [skip ci]
# [5.42.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.41.1-dev.2...v5.42.0-dev.1) (2025-09-27)

### Features

* **Viber:** Add `Hide navigation buttons` patch ([#5991](https://github.com/ReVanced/revanced-patches/issues/5991)) ([5cb46c4](5cb46c4e91))
2025-09-27 13:02:45 +00:00
Samo Hribar
5cb46c4e91 feat(Viber): Add Hide navigation buttons patch (#5991) 2025-09-27 16:59:51 +04:00
semantic-release-bot
52c369576d chore: Release v5.41.1-dev.2 [skip ci]
## [5.41.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.41.1-dev.1...v5.41.1-dev.2) (2025-09-27)

### Bug Fixes

* **YouTube Music - Hide cast button:** Fix patching error ([28799a5](28799a548a))
2025-09-27 12:35:45 +00:00
LisoUseInAIKyrios
28799a548a fix(YouTube Music - Hide cast button): Fix patching error 2025-09-27 16:31:22 +04:00
semantic-release-bot
1c80774d79 chore: Release v5.41.1-dev.1 [skip ci]
## [5.41.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.41.0...v5.41.1-dev.1) (2025-09-27)

### Bug Fixes

* **YouTube Music - Hide cast button:** Resolve button not hiding ([7817885](7817885cff))
2025-09-27 12:24:18 +00:00
LisoUseInAIKyrios
7817885cff fix(YouTube Music - Hide cast button): Resolve button not hiding 2025-09-27 16:21:32 +04:00
LisoUseInAIKyrios
9afe9afc67 chore(YouTube): Fix patch description 2025-09-27 11:53:21 +04:00
semantic-release-bot
3a8091ae00 chore: Release v5.41.0 [skip ci]
# [5.41.0](https://github.com/ReVanced/revanced-patches/compare/v5.40.0...v5.41.0) (2025-09-27)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Remove button based on name ([#5971](https://github.com/ReVanced/revanced-patches/issues/5971)) ([6fa4043](6fa404331b))
* **Instagram - Limit feed to followed profiles:** Preserve favorites feed ([#5963](https://github.com/ReVanced/revanced-patches/issues/5963)) ([ef51401](ef514017f4))
* **TikTok:** Show correct dialog restart text, use correct font color for non-dark mode ([d1a1293](d1a12930c3))
* **Twitch - Settings:** Fix missing style resources ([#5970](https://github.com/ReVanced/revanced-patches/issues/5970)) ([8c22995](8c229954d7))
* **YouTube - Hide Shorts components:** Fix "Hide preview comment" ([#5990](https://github.com/ReVanced/revanced-patches/issues/5990)) ([dd4e2cd](dd4e2cd085))
* **YouTube - Return YouTube Dislike:** Do not show error toast if API returns 401 status ([#5949](https://github.com/ReVanced/revanced-patches/issues/5949)) ([58d088a](58d088ab30))
* **YouTube - Settings:** Handle on screen back swipe gesture ([#6002](https://github.com/ReVanced/revanced-patches/issues/6002)) ([6f92b6c](6f92b6c50b))
* **YouTube - Settings:** Use an overlay to show search results ([#5806](https://github.com/ReVanced/revanced-patches/issues/5806)) ([ece8076](ece8076f7c))
* **YouTube - SponsorBlock:** Show category color dot in voting dialog menu ([4be00d0](4be00d09b7))
* **YouTube - SponsorBlock:** Show category color in create new segment menu ([#5987](https://github.com/ReVanced/revanced-patches/issues/5987)) ([ffd933c](ffd933c673))
* **YouTube - Spoof video streams:** Update client side effects summary text ([a0a62dd](a0a62ddad2))

### Features

* **Tumblr:** Add `Disable Tumblr TV` patch ([#5959](https://github.com/ReVanced/revanced-patches/issues/5959)) ([212418b](212418b8db))
* **YouTube - Hide layout components:** Add "Hide Emoji and Timestamp buttons" setting ([#5992](https://github.com/ReVanced/revanced-patches/issues/5992)) ([2b555f6](2b555f67f0))
* **YouTube - Hide layout components:** Add "Hide view count" and "Hide upload time" settings ([#5983](https://github.com/ReVanced/revanced-patches/issues/5983)) ([7a37d85](7a37d858fb))
* **YouTube - Loop video:** Add player button to change loop video state ([#5961](https://github.com/ReVanced/revanced-patches/issues/5961)) ([dfb5407](dfb5407e67))
* **YouTube - Spoof app version:** Add spoof target `20.05.46` that fixes transcript functionality ([5823f0e](5823f0e982))
* **YouTube Music:** Add `Check watch history domain name resolution` ([#5979](https://github.com/ReVanced/revanced-patches/issues/5979)) ([8af70fe](8af70fe2d1))
* **YouTube Music:** Add `Sanitize sharing links` patch ([#5952](https://github.com/ReVanced/revanced-patches/issues/5952)) ([45c1ee8](45c1ee8a12))
* **YouTube Music:** Add `Theme` patch ([#5984](https://github.com/ReVanced/revanced-patches/issues/5984)) ([3bd76d6](3bd76d60d6))
* **YouTube:** Add `Disable video codecs` patch ([#5981](https://github.com/ReVanced/revanced-patches/issues/5981)) ([bfbffbd](bfbffbd1f5))
2025-09-27 07:21:33 +00:00
LisoUseInAIKyrios
6192ece114 chore: Merge branch dev to main (#5950) 2025-09-27 11:17:09 +04:00
github-actions[bot]
5d9971444e chore: Sync translations (#6014) 2025-09-27 11:15:04 +04:00
semantic-release-bot
cdfa75dd5a chore: Release v5.41.0-dev.18 [skip ci]
# [5.41.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.17...v5.41.0-dev.18) (2025-09-26)

### Bug Fixes

* **YouTube - Settings:** Handle on screen back swipe gesture ([#6002](https://github.com/ReVanced/revanced-patches/issues/6002)) ([6f92b6c](6f92b6c50b))
2025-09-26 15:05:03 +00:00
LisoUseInAIKyrios
6f92b6c50b fix(YouTube - Settings): Handle on screen back swipe gesture (#6002) 2025-09-26 19:00:12 +04:00
github-actions[bot]
1e023fa1f3 chore: Sync translations (#6010) 2025-09-26 18:59:48 +04:00
semantic-release-bot
00477bfebc chore: Release v5.41.0-dev.17 [skip ci]
# [5.41.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.16...v5.41.0-dev.17) (2025-09-26)

### Bug Fixes

* **YouTube - SponsorBlock:** Show category color dot in voting dialog menu ([4be00d0](4be00d09b7))
2025-09-26 08:28:43 +00:00
LisoUseInAIKyrios
4be00d09b7 fix(YouTube - SponsorBlock): Show category color dot in voting dialog menu 2025-09-26 12:25:17 +04:00
github-actions[bot]
50aca3314f chore: Sync translations (#6005) 2025-09-26 12:24:08 +04:00
LisoUseInAIKyrios
15a7e540de refactor(YouTube - Miniplayer): Change ReVanced settings that are now YouTube default on into "Disable" style settings (#6003) 2025-09-26 12:14:20 +04:00
semantic-release-bot
041f7e0140 chore: Release v5.41.0-dev.16 [skip ci]
# [5.41.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.15...v5.41.0-dev.16) (2025-09-26)

### Features

* **YouTube Music:** Add `Theme` patch ([#5984](https://github.com/ReVanced/revanced-patches/issues/5984)) ([3bd76d6](3bd76d60d6))
2025-09-26 05:33:20 +00:00
MarcaD
3bd76d60d6 feat(YouTube Music): Add Theme patch (#5984)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-09-26 09:29:11 +04:00
github-actions[bot]
1587178ff8 chore: Sync translations (#6001) 2025-09-26 09:28:45 +04:00
semantic-release-bot
8a69240d66 chore: Release v5.41.0-dev.15 [skip ci]
# [5.41.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.14...v5.41.0-dev.15) (2025-09-25)

### Features

* **YouTube - Hide layout components:** Add "Hide view count" and "Hide upload time" settings ([#5983](https://github.com/ReVanced/revanced-patches/issues/5983)) ([7a37d85](7a37d858fb))
2025-09-25 12:47:40 +00:00
viSapio
7a37d858fb feat(YouTube - Hide layout components): Add "Hide view count" and "Hide upload time" settings (#5983) 2025-09-25 16:43:59 +04:00
github-actions[bot]
0ed7067459 chore: Sync translations (#5996) 2025-09-25 16:38:32 +04:00
LisoUseInAIKyrios
6102644194 chore(YouTube): Adjust patch strings 2025-09-25 00:32:15 +04:00
semantic-release-bot
a89556a017 chore: Release v5.41.0-dev.14 [skip ci]
# [5.41.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.13...v5.41.0-dev.14) (2025-09-24)

### Features

* **YouTube - Hide layout components:** Add "Hide Emoji and Timestamp buttons" setting ([#5992](https://github.com/ReVanced/revanced-patches/issues/5992)) ([2b555f6](2b555f67f0))
2025-09-24 20:21:22 +00:00
ILoveOpenSourceApplications
2b555f67f0 feat(YouTube - Hide layout components): Add "Hide Emoji and Timestamp buttons" setting (#5992) 2025-09-25 00:17:08 +04:00
245 changed files with 4436 additions and 2451 deletions

View File

@@ -12,10 +12,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Open pull request
uses: repo-sync/pull-request@v2

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: dev
clean: true

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Preprocess strings
env:

View File

@@ -18,10 +18,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
@@ -35,7 +35,7 @@ jobs:
run: ./gradlew :patches:buildAndroid clean
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: 'lts/*'
cache: 'npm'
@@ -51,14 +51,14 @@ jobs:
fingerprint: ${{ vars.GPG_FINGERPRINT }}
- name: Release
uses: cycjimmy/semantic-release-action@v4
uses: cycjimmy/semantic-release-action@v5
id: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Attest
if: steps.release.outputs.new_release_published == 'true'
uses: actions/attest-build-provenance@v2
uses: actions/attest-build-provenance@v3
with:
subject-name: 'ReVanced Patches ${{ steps.release.outputs.new_release_git_tag }}'
subject-path: patches/build/libs/patches-*.rvp

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Update Gradle Wrapper
uses: gradle-update/update-gradle-wrapper-action@v1

View File

@@ -1,3 +1,164 @@
# [5.42.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.10...v5.42.0-dev.11) (2025-10-03)
### Bug Fixes
* **YouTube:** Resolve UI components not hiding for some users ([#6054](https://github.com/ReVanced/revanced-patches/issues/6054)) ([6b26346](https://github.com/ReVanced/revanced-patches/commit/6b2634691423f5ce25a28b3f2fbc420977b81748))
# [5.42.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.9...v5.42.0-dev.10) (2025-10-02)
### Bug Fixes
* **YouTube - Spoof video streams:** Resolve playback dropping frames ([#6051](https://github.com/ReVanced/revanced-patches/issues/6051)) ([a62ee43](https://github.com/ReVanced/revanced-patches/commit/a62ee43441b197f5c8352ae373bb8919ad66f0bd))
# [5.42.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.8...v5.42.0-dev.9) (2025-10-01)
### Bug Fixes
* **Custom branding:** Update ReVanced logo sizing ([#6029](https://github.com/ReVanced/revanced-patches/issues/6029)) ([ae4b947](https://github.com/ReVanced/revanced-patches/commit/ae4b9474d3fb62528fc21397c19954d31605e9da))
# [5.42.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.7...v5.42.0-dev.8) (2025-10-01)
### Bug Fixes
* **YouTube - Force original language:** Resolve some videos using Swedish audio track ([9d67316](https://github.com/ReVanced/revanced-patches/commit/9d6731660ba0e19b863d05d54aa04f74a879f69b))
### Features
* **YouTube Music:** Add `Force original audio` patch ([#6036](https://github.com/ReVanced/revanced-patches/issues/6036)) ([d0d53d1](https://github.com/ReVanced/revanced-patches/commit/d0d53d109e451759a029326873adfa36fba12b23))
# [5.42.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.6...v5.42.0-dev.7) (2025-10-01)
### Features
* **Instagram:** Add `Open links externally` patch ([#6012](https://github.com/ReVanced/revanced-patches/issues/6012)) ([08e8ead](https://github.com/ReVanced/revanced-patches/commit/08e8ead04ffff47a4608a3db7aadc8d5feccd4ad))
# [5.42.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.5...v5.42.0-dev.6) (2025-09-30)
### Bug Fixes
* **X / Twitter:** Remove non functional and obsolete patch `Open links with app chooser` ([#6033](https://github.com/ReVanced/revanced-patches/issues/6033)) ([673609c](https://github.com/ReVanced/revanced-patches/commit/673609c2aa87988cdc138eab101b9750fe6a7b62))
# [5.42.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.4...v5.42.0-dev.5) (2025-09-28)
### Features
* **YouTube Music:** Add `Custom branding` patch ([#6007](https://github.com/ReVanced/revanced-patches/issues/6007)) ([4c8b56f](https://github.com/ReVanced/revanced-patches/commit/4c8b56f5466b244737f501654eb7c5d34b6b2f88))
# [5.42.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.3...v5.42.0-dev.4) (2025-09-28)
### Bug Fixes
* **YouTube Music - GmsCore support:** Handle sharing links to certain apps such as Instagram ([#6026](https://github.com/ReVanced/revanced-patches/issues/6026)) ([328234f](https://github.com/ReVanced/revanced-patches/commit/328234f39ada81542e596f04e8ce410c787c15c8))
# [5.42.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.2...v5.42.0-dev.3) (2025-09-28)
### Bug Fixes
* **YouTube - Hide end screen cards:** Hide new type of end screen card ([#6027](https://github.com/ReVanced/revanced-patches/issues/6027)) ([76b0364](https://github.com/ReVanced/revanced-patches/commit/76b0364c5b5562c6a0d178d2bbe5b220f48aaca9))
# [5.42.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.1...v5.42.0-dev.2) (2025-09-27)
### Bug Fixes
* **Instagram - Hide navigation buttons:** Resolve app startup crash ([080a226](https://github.com/ReVanced/revanced-patches/commit/080a2266146798be71789c939deef2f289697523))
# [5.42.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.41.1-dev.2...v5.42.0-dev.1) (2025-09-27)
### Features
* **Viber:** Add `Hide navigation buttons` patch ([#5991](https://github.com/ReVanced/revanced-patches/issues/5991)) ([5cb46c4](https://github.com/ReVanced/revanced-patches/commit/5cb46c4e9180ebc16eddb983dad73d137d8ec047))
## [5.41.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.41.1-dev.1...v5.41.1-dev.2) (2025-09-27)
### Bug Fixes
* **YouTube Music - Hide cast button:** Fix patching error ([28799a5](https://github.com/ReVanced/revanced-patches/commit/28799a548a73651134ef304cb6cb542cf8e55abe))
## [5.41.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.41.0...v5.41.1-dev.1) (2025-09-27)
### Bug Fixes
* **YouTube Music - Hide cast button:** Resolve button not hiding ([7817885](https://github.com/ReVanced/revanced-patches/commit/7817885cffed66608039ab45881537cbd3069c9d))
# [5.41.0](https://github.com/ReVanced/revanced-patches/compare/v5.40.0...v5.41.0) (2025-09-27)
### Bug Fixes
* **Instagram - Hide navigation buttons:** Remove button based on name ([#5971](https://github.com/ReVanced/revanced-patches/issues/5971)) ([6fa4043](https://github.com/ReVanced/revanced-patches/commit/6fa404331b5162682d83fba5f38ed570c31495fc))
* **Instagram - Limit feed to followed profiles:** Preserve favorites feed ([#5963](https://github.com/ReVanced/revanced-patches/issues/5963)) ([ef51401](https://github.com/ReVanced/revanced-patches/commit/ef514017f46025d9aef6884424caeb0670514e7a))
* **TikTok:** Show correct dialog restart text, use correct font color for non-dark mode ([d1a1293](https://github.com/ReVanced/revanced-patches/commit/d1a12930c35f630793a0f240d4203c2ff9060158))
* **Twitch - Settings:** Fix missing style resources ([#5970](https://github.com/ReVanced/revanced-patches/issues/5970)) ([8c22995](https://github.com/ReVanced/revanced-patches/commit/8c229954d7f232a7a472ca49f1b5e7cdc475bbcc))
* **YouTube - Hide Shorts components:** Fix "Hide preview comment" ([#5990](https://github.com/ReVanced/revanced-patches/issues/5990)) ([dd4e2cd](https://github.com/ReVanced/revanced-patches/commit/dd4e2cd0855ccc51b94593004fdd8150ac3b41cc))
* **YouTube - Return YouTube Dislike:** Do not show error toast if API returns 401 status ([#5949](https://github.com/ReVanced/revanced-patches/issues/5949)) ([58d088a](https://github.com/ReVanced/revanced-patches/commit/58d088ab307440a6912a867246da799b7dd6499b))
* **YouTube - Settings:** Handle on screen back swipe gesture ([#6002](https://github.com/ReVanced/revanced-patches/issues/6002)) ([6f92b6c](https://github.com/ReVanced/revanced-patches/commit/6f92b6c50beab091f5f7ef7386579eda38cb4c66))
* **YouTube - Settings:** Use an overlay to show search results ([#5806](https://github.com/ReVanced/revanced-patches/issues/5806)) ([ece8076](https://github.com/ReVanced/revanced-patches/commit/ece8076f7cefd752b97515014bc50fe4fd80171e))
* **YouTube - SponsorBlock:** Show category color dot in voting dialog menu ([4be00d0](https://github.com/ReVanced/revanced-patches/commit/4be00d09b7b87dcfac324de8709af06e9f730791))
* **YouTube - SponsorBlock:** Show category color in create new segment menu ([#5987](https://github.com/ReVanced/revanced-patches/issues/5987)) ([ffd933c](https://github.com/ReVanced/revanced-patches/commit/ffd933c6734274cdde5aaec0159b67f173f9228c))
* **YouTube - Spoof video streams:** Update client side effects summary text ([a0a62dd](https://github.com/ReVanced/revanced-patches/commit/a0a62ddad26cfab3e04907fae5532e1ba1fdf710))
### Features
* **Tumblr:** Add `Disable Tumblr TV` patch ([#5959](https://github.com/ReVanced/revanced-patches/issues/5959)) ([212418b](https://github.com/ReVanced/revanced-patches/commit/212418b8db9a730ae9efa89ad2bef24952afbadd))
* **YouTube - Hide layout components:** Add "Hide Emoji and Timestamp buttons" setting ([#5992](https://github.com/ReVanced/revanced-patches/issues/5992)) ([2b555f6](https://github.com/ReVanced/revanced-patches/commit/2b555f67f07e0de5703c630888ce2fbba3145192))
* **YouTube - Hide layout components:** Add "Hide view count" and "Hide upload time" settings ([#5983](https://github.com/ReVanced/revanced-patches/issues/5983)) ([7a37d85](https://github.com/ReVanced/revanced-patches/commit/7a37d858fb937c6bdc2219103dac765b62600e6c))
* **YouTube - Loop video:** Add player button to change loop video state ([#5961](https://github.com/ReVanced/revanced-patches/issues/5961)) ([dfb5407](https://github.com/ReVanced/revanced-patches/commit/dfb5407e67222e80e23c8935e04b6dbf1a43d757))
* **YouTube - Spoof app version:** Add spoof target `20.05.46` that fixes transcript functionality ([5823f0e](https://github.com/ReVanced/revanced-patches/commit/5823f0e982e87b4a35d30feeca8a7e16edfebc5f))
* **YouTube Music:** Add `Check watch history domain name resolution` ([#5979](https://github.com/ReVanced/revanced-patches/issues/5979)) ([8af70fe](https://github.com/ReVanced/revanced-patches/commit/8af70fe2d10c0f4da2d7e53bd00f5b3979775d5d))
* **YouTube Music:** Add `Sanitize sharing links` patch ([#5952](https://github.com/ReVanced/revanced-patches/issues/5952)) ([45c1ee8](https://github.com/ReVanced/revanced-patches/commit/45c1ee8a12dc777a371875d90741a05cf5d8e9dd))
* **YouTube Music:** Add `Theme` patch ([#5984](https://github.com/ReVanced/revanced-patches/issues/5984)) ([3bd76d6](https://github.com/ReVanced/revanced-patches/commit/3bd76d60d664befff29c24c9de56dac1486a6e67))
* **YouTube:** Add `Disable video codecs` patch ([#5981](https://github.com/ReVanced/revanced-patches/issues/5981)) ([bfbffbd](https://github.com/ReVanced/revanced-patches/commit/bfbffbd1f5aa867027053e25b343a51a606216a3))
# [5.41.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.17...v5.41.0-dev.18) (2025-09-26)
### Bug Fixes
* **YouTube - Settings:** Handle on screen back swipe gesture ([#6002](https://github.com/ReVanced/revanced-patches/issues/6002)) ([6f92b6c](https://github.com/ReVanced/revanced-patches/commit/6f92b6c50beab091f5f7ef7386579eda38cb4c66))
# [5.41.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.16...v5.41.0-dev.17) (2025-09-26)
### Bug Fixes
* **YouTube - SponsorBlock:** Show category color dot in voting dialog menu ([4be00d0](https://github.com/ReVanced/revanced-patches/commit/4be00d09b7b87dcfac324de8709af06e9f730791))
# [5.41.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.15...v5.41.0-dev.16) (2025-09-26)
### Features
* **YouTube Music:** Add `Theme` patch ([#5984](https://github.com/ReVanced/revanced-patches/issues/5984)) ([3bd76d6](https://github.com/ReVanced/revanced-patches/commit/3bd76d60d664befff29c24c9de56dac1486a6e67))
# [5.41.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.14...v5.41.0-dev.15) (2025-09-25)
### Features
* **YouTube - Hide layout components:** Add "Hide view count" and "Hide upload time" settings ([#5983](https://github.com/ReVanced/revanced-patches/issues/5983)) ([7a37d85](https://github.com/ReVanced/revanced-patches/commit/7a37d858fb937c6bdc2219103dac765b62600e6c))
# [5.41.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.13...v5.41.0-dev.14) (2025-09-24)
### Features
* **YouTube - Hide layout components:** Add "Hide Emoji and Timestamp buttons" setting ([#5992](https://github.com/ReVanced/revanced-patches/issues/5992)) ([2b555f6](https://github.com/ReVanced/revanced-patches/commit/2b555f67f07e0de5703c630888ce2fbba3145192))
# [5.41.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.12...v5.41.0-dev.13) (2025-09-24)

View File

@@ -0,0 +1,30 @@
package app.revanced.extension.instagram.misc.links;
import android.net.Uri;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
@SuppressWarnings("unused")
public final class OpenLinksExternallyPatch {
/**
* Injection point.
*/
public static boolean openExternally(String url) {
try {
// The "url" parameter to this function will be of the form.
// https://l.instagram.com/?u=<actual url>&e=<tracking id>
String actualUrl = Uri.parse(url).getQueryParameter("u");
if (actualUrl != null) {
Utils.openLink(actualUrl);
return true;
}
} catch (Exception ex) {
Logger.printException(() -> "openExternally failure", ex);
}
return false;
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class ForceOriginalAudioPatch {
/**
* Injection point.
*/
public static void setEnabled() {
app.revanced.extension.shared.patches.ForceOriginalAudioPatch.setEnabled(
Settings.FORCE_ORIGINAL_AUDIO.get(),
Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get()
);
}
}

View File

@@ -0,0 +1,27 @@
package app.revanced.extension.music.patches.theme;
import app.revanced.extension.shared.theme.BaseThemePatch;
@SuppressWarnings("unused")
public class ThemePatch extends BaseThemePatch {
// Color constants used in relation with litho components.
private static final int[] DARK_VALUES = {
0xFF212121, // Comments box background.
0xFF030303, // Button container background in album.
0xFF000000, // Button container background in playlist.
};
/**
* Injection point.
* <p>
* Change the color of Litho components.
* If the color of the component matches one of the values, return the background color.
*
* @param originalValue The original color value.
* @return The new or original color value.
*/
public static int getValue(int originalValue) {
return processColorValue(originalValue, DARK_VALUES, null);
}
}

View File

@@ -32,4 +32,6 @@ public class Settings extends BaseSettings {
// Miscellaneous
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type",
ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", TRUE, true);
}

View File

@@ -15,6 +15,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -696,6 +697,18 @@ public class Utils {
}
}
public static void openLink(String url) {
try {
Intent intent = new Intent("android.intent.action.VIEW", Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Logger.printInfo(() -> "Opening link with external browser: " + intent);
getContext().startActivity(intent);
} catch (Exception ex) {
Logger.printException(() -> "openLink failure", ex);
}
}
public enum NetworkType {
NONE,
MOBILE,

View File

@@ -46,7 +46,7 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
/**
* Injection point.
*
* Checks if s.youtube.com is blacklisted and playback history will fail to work.
* Checks if YouTube watch history endpoint cannot be reached.
*/
public static void checkDnsResolver(Activity context) {
if (!Utils.isNetworkConnected() || !BaseSettings.CHECK_WATCH_HISTORY_DOMAIN_NAME.get()) return;
@@ -67,28 +67,20 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
}
Utils.runOnMainThread(() -> {
try {
// Create the custom dialog.
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
context,
str("revanced_check_watch_history_domain_name_dialog_title"), // Title.
Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message")), // Message (HTML).
null, // No EditText.
null, // OK button text.
() -> {}, // OK button action (just dismiss).
() -> {}, // Cancel button action (just dismiss).
str("revanced_check_watch_history_domain_name_dialog_ignore"), // Neutral button text.
() -> BaseSettings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false), // Neutral button action (Ignore).
true // Dismiss dialog on Neutral button click.
);
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
context,
str("revanced_check_watch_history_domain_name_dialog_title"), // Title.
Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message")), // Message (HTML).
null, // No EditText.
null, // OK button text.
() -> {}, // OK button action (just dismiss).
null, // No cancel button.
str("revanced_check_watch_history_domain_name_dialog_ignore"), // Neutral button text.
() -> BaseSettings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false), // Neutral button action (Ignore).
true // Dismiss dialog on Neutral button click.
);
// Show the dialog.
Dialog dialog = dialogPair.first;
Utils.showDialog(context, dialog, false, null);
} catch (Exception ex) {
Logger.printException(() -> "checkDnsResolver dialog creation failure", ex);
}
Utils.showDialog(context, dialogPair.first, false, null);
});
} catch (Exception ex) {
Logger.printException(() -> "checkDnsResolver failure", ex);

View File

@@ -0,0 +1,71 @@
package app.revanced.extension.shared.patches;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.spoof.ClientType;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
@SuppressWarnings("unused")
public class ForceOriginalAudioPatch {
private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4";
private static volatile boolean enabled;
public static void setEnabled(boolean isEnabled, ClientType client) {
enabled = isEnabled;
if (isEnabled && !client.useAuth && !client.supportsMultiAudioTracks) {
// 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.NB; // Norwegian Bokmal.
Logger.printDebug(() -> "Setting language override: " + override);
SpoofVideoStreamsPatch.setLanguageOverride(override);
}
}
/**
* Injection point.
*/
public static boolean ignoreDefaultAudioStream(boolean original) {
if (enabled) {
return false;
}
return original;
}
/**
* Injection point.
*/
public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) {
try {
if (!enabled) {
return isDefault;
}
if (audioTrackId.isEmpty()) {
// Older app targets can have empty audio tracks and these might be placeholders.
// The real audio tracks are called after these.
return isDefault;
}
Logger.printDebug(() -> "default: " + String.format("%-5s", isDefault) + " id: "
+ String.format("%-8s", audioTrackId) + " name:" + audioTrackDisplayName);
final boolean isOriginal = audioTrackId.endsWith(DEFAULT_AUDIO_TRACKS_SUFFIX);
if (isOriginal) {
Logger.printDebug(() -> "Using audio: " + audioTrackId);
}
return isOriginal;
} catch (Exception ex) {
Logger.printException(() -> "isDefaultAudioStream failure", ex);
return isDefault;
}
}
}

View File

@@ -36,8 +36,8 @@ public enum AppLanguage {
FR,
GL,
GU,
HI,
HE, // App uses obsolete 'IW' and not the modern 'HE' ISO code.
HI,
HR,
HU,
HY,
@@ -60,9 +60,9 @@ public enum AppLanguage {
MR,
MS,
MY,
NB,
NE,
NL,
NB,
OR,
PA,
PL,

View File

@@ -1,4 +1,4 @@
package app.revanced.extension.youtube.settings.preference;
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
@@ -6,17 +6,17 @@ import android.content.Context;
import android.preference.SwitchPreference;
import android.util.AttributeSet;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.spoof.ClientType;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings({"deprecation", "unused"})
public class ForceOriginalAudioSwitchPreference extends SwitchPreference {
// Spoof stream patch is not included, or is not currently spoofing to Android Studio.
private static final boolean available = !SpoofVideoStreamsPatch.isPatchIncluded()
|| !(Settings.SPOOF_VIDEO_STREAMS.get()
&& Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_CREATOR);
|| !(BaseSettings.SPOOF_VIDEO_STREAMS.get()
&& SpoofVideoStreamsPatch.getPreferredClient() == ClientType.ANDROID_CREATOR);
{
if (!available) {

View File

@@ -31,6 +31,7 @@ public enum ClientType {
"132.0.6808.3",
"1.61.48",
false,
false,
"Android VR 1.61"
),
/**
@@ -50,6 +51,7 @@ public enum ClientType {
"107.0.5284.2",
"1.43.32",
ANDROID_VR_1_61_48.useAuth,
ANDROID_VR_1_61_48.supportsMultiAudioTracks,
"Android VR 1.43"
),
/**
@@ -69,6 +71,7 @@ public enum ClientType {
"132.0.6779.0",
"23.47.101",
true,
false,
"Android Studio"
),
/**
@@ -83,6 +86,7 @@ public enum ClientType {
"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"
),
/**
@@ -107,6 +111,7 @@ public enum ClientType {
"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,
true,
"iPadOS"
);
@@ -180,6 +185,11 @@ public enum ClientType {
*/
public final boolean useAuth;
/**
* If the client supports multiple audio tracks.
*/
public final boolean supportsMultiAudioTracks;
/**
* Friendly name displayed in stats for nerds.
*/
@@ -200,6 +210,7 @@ public enum ClientType {
@NonNull String cronetVersion,
String clientVersion,
boolean useAuth,
boolean supportsMultiAudioTracks,
String friendlyName) {
this.id = id;
this.clientName = clientName;
@@ -213,6 +224,7 @@ public enum ClientType {
this.cronetVersion = cronetVersion;
this.clientVersion = clientVersion;
this.useAuth = useAuth;
this.supportsMultiAudioTracks = supportsMultiAudioTracks;
this.friendlyName = friendlyName;
Locale defaultLocale = Locale.getDefault();
@@ -238,6 +250,7 @@ public enum ClientType {
String clientVersion,
String userAgent,
boolean useAuth,
boolean supportsMultiAudioTracks,
String friendlyName) {
this.id = id;
this.clientName = clientName;
@@ -248,6 +261,7 @@ public enum ClientType {
this.clientVersion = clientVersion;
this.userAgent = userAgent;
this.useAuth = useAuth;
this.supportsMultiAudioTracks = supportsMultiAudioTracks;
this.friendlyName = friendlyName;
this.packageName = null;
this.androidSdkVersion = null;

View File

@@ -39,7 +39,7 @@ public class SpoofVideoStreamsPatch {
@Nullable
private static volatile AppLanguage languageOverride;
private static volatile ClientType preferredClient = ClientType.ANDROID_VR_1_61_48;
private static volatile ClientType preferredClient = ClientType.ANDROID_VR_1_43_32;
/**
* @return If this patch was included during patching.
@@ -66,6 +66,10 @@ public class SpoofVideoStreamsPatch {
StreamingDataRequest.setClientOrderToUse(availableClients, client);
}
public static ClientType getPreferredClient() {
return preferredClient;
}
public static boolean spoofingToClientWithNoMultiAudioStreams() {
return isPatchIncluded()
&& SPOOF_STREAMING_DATA

View File

@@ -0,0 +1,48 @@
package app.revanced.extension.shared.theme;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Utils;
@SuppressWarnings("unused")
public abstract class BaseThemePatch {
// Background colors.
protected static final int BLACK_COLOR = Utils.getResourceColor("yt_black1");
protected static final int WHITE_COLOR = Utils.getResourceColor("yt_white1");
/**
* Check if a value matches any of the provided values.
*
* @param value The value to check.
* @param of The array of values to compare against.
* @return True if the value matches any of the provided values.
*/
protected static boolean anyEquals(int value, int... of) {
for (int v : of) {
if (value == v) {
return true;
}
}
return false;
}
/**
* Helper method to process color values for Litho components.
*
* @param originalValue The original color value.
* @param darkValues Array of dark mode color values to match.
* @param lightValues Array of light mode color values to match.
* @return The new or original color value.
*/
protected static int processColorValue(int originalValue, int[] darkValues, @Nullable int[] lightValues) {
if (Utils.isDarkModeEnabled()) {
if (anyEquals(originalValue, darkValues)) {
return BLACK_COLOR;
}
} else if (lightValues != null && anyEquals(originalValue, lightValues)) {
return WHITE_COLOR;
}
return originalValue;
}
}

View File

@@ -1 +1,3 @@
// Do not remove. Necessary for the extension plugin to be applied to the project.
dependencies {
compileOnly(project(":extensions:shared:library"))
}

View File

@@ -1,15 +1,22 @@
package app.revanced.twitter.patches.links;
@SuppressWarnings("unused")
public final class ChangeLinkSharingDomainPatch {
private static final String DOMAIN_NAME = "https://fxtwitter.com";
private static final String LINK_FORMAT = "%s/%s/status/%s";
/**
* Injection point.
*/
public static String formatResourceLink(Object... formatArgs) {
String username = (String) formatArgs[0];
String tweetId = (String) formatArgs[1];
return String.format(LINK_FORMAT, DOMAIN_NAME, username, tweetId);
}
/**
* Injection point.
*/
public static String formatLink(long tweetId, String username) {
return String.format(LINK_FORMAT, DOMAIN_NAME, username, tweetId);
}

View File

@@ -2,11 +2,18 @@ package app.revanced.twitter.patches.links;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
@Deprecated(forRemoval = true)
public final class OpenLinksWithAppChooserPatch {
/**
* Injection point.
*/
public static void openWithChooser(final Context context, final Intent intent) {
Log.d("ReVanced", "Opening intent with chooser: " + intent);
Logger.printInfo(() -> "Opening intent with chooser: " + intent);
intent.setAction("android.intent.action.VIEW");

View File

@@ -1,72 +1,17 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class ForceOriginalAudioPatch {
private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4";
/**
* Injection point.
*/
public static void setPreferredLanguage() {
if (Settings.FORCE_ORIGINAL_AUDIO.get()
&& SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams()
&& !Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get().useAuth) {
// 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);
}
}
/**
* Injection point.
*/
public static boolean ignoreDefaultAudioStream(boolean original) {
if (Settings.FORCE_ORIGINAL_AUDIO.get()) {
return false;
}
return original;
}
/**
* Injection point.
*/
public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) {
try {
if (!Settings.FORCE_ORIGINAL_AUDIO.get()) {
return isDefault;
}
if (audioTrackId.isEmpty()) {
// Older app targets can have empty audio tracks and these might be placeholders.
// The real audio tracks are called after these.
return isDefault;
}
Logger.printDebug(() -> "default: " + String.format("%-5s", isDefault) + " id: "
+ String.format("%-8s", audioTrackId) + " name:" + audioTrackDisplayName);
final boolean isOriginal = audioTrackId.endsWith(DEFAULT_AUDIO_TRACKS_SUFFIX);
if (isOriginal) {
Logger.printDebug(() -> "Using audio: " + audioTrackId);
}
return isOriginal;
} catch (Exception ex) {
Logger.printException(() -> "isDefaultAudioStream failure", ex);
return isDefault;
}
public static void setEnabled() {
app.revanced.extension.shared.patches.ForceOriginalAudioPatch.setEnabled(
Settings.FORCE_ORIGINAL_AUDIO.get(),
Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get()
);
}
}

View File

@@ -0,0 +1,24 @@
package app.revanced.extension.youtube.patches;
import android.view.View;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class HideEndScreenCardsPatch {
/**
* Injection point.
*/
public static void hideEndScreenCardView(View view) {
Utils.hideViewUnderCondition(Settings.HIDE_ENDSCREEN_CARDS, view);
}
/**
* Injection point.
*/
public static boolean hideEndScreenCards() {
return Settings.HIDE_ENDSCREEN_CARDS.get();
}
}

View File

@@ -1,14 +0,0 @@
package app.revanced.extension.youtube.patches;
import android.view.View;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class HideEndscreenCardsPatch {
//Used by app.revanced.patches.youtube.layout.hideendscreencards.bytecode.patch.HideEndscreenCardsPatch
public static void hideEndscreen(View view) {
if (!Settings.HIDE_ENDSCREEN_CARDS.get()) return;
view.setVisibility(View.GONE);
}
}

View File

@@ -19,7 +19,7 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("SpellCheckingInspection")
@SuppressWarnings({"unused", "SpellCheckingInspection"})
public final class MiniplayerPatch {
/**
@@ -129,7 +129,7 @@ public final class MiniplayerPatch {
(CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get());
private static final boolean DRAG_AND_DROP_ENABLED =
CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get();
CURRENT_TYPE.isModern() && !Settings.MINIPLAYER_DISABLE_DRAG_AND_DROP.get();
private static final boolean HIDE_OVERLAY_BUTTONS_ENABLED =
Settings.MINIPLAYER_HIDE_OVERLAY_BUTTONS.get()
@@ -145,10 +145,10 @@ public final class MiniplayerPatch {
&& (VersionCheckPatch.IS_19_34_OR_GREATER || Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get());
private static final boolean MINIPLAYER_ROUNDED_CORNERS_ENABLED =
CURRENT_TYPE.isModern() && Settings.MINIPLAYER_ROUNDED_CORNERS.get();
CURRENT_TYPE.isModern() && !Settings.MINIPLAYER_DISABLE_ROUNDED_CORNERS.get();
private static final boolean MINIPLAYER_HORIZONTAL_DRAG_ENABLED =
DRAG_AND_DROP_ENABLED && Settings.MINIPLAYER_HORIZONTAL_DRAG.get();
DRAG_AND_DROP_ENABLED && !Settings.MINIPLAYER_DISABLE_HORIZONTAL_DRAG.get();
/**
* Remove a broken and always present subtitle text that is only
@@ -173,14 +173,14 @@ public final class MiniplayerPatch {
public static final class MiniplayerHorizontalDragAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
return Settings.MINIPLAYER_TYPE.get().isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get();
return Settings.MINIPLAYER_TYPE.get().isModern() && !Settings.MINIPLAYER_DISABLE_DRAG_AND_DROP.get();
}
@Override
public List<Setting<?>> getParentSettings() {
return List.of(
Settings.MINIPLAYER_TYPE,
Settings.MINIPLAYER_DRAG_AND_DROP
Settings.MINIPLAYER_DISABLE_DRAG_AND_DROP
);
}
}
@@ -192,7 +192,7 @@ public final class MiniplayerPatch {
return type == MODERN_4
|| (!IS_19_20_OR_GREATER && (type == MODERN_1 || type == MODERN_3))
|| (!IS_19_26_OR_GREATER && type == MODERN_1
&& !Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() && !Settings.MINIPLAYER_DRAG_AND_DROP.get())
&& !Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() && Settings.MINIPLAYER_DISABLE_DRAG_AND_DROP.get())
|| (IS_19_29_OR_GREATER && type == MODERN_3);
}
@@ -201,7 +201,7 @@ public final class MiniplayerPatch {
return List.of(
Settings.MINIPLAYER_TYPE,
Settings.MINIPLAYER_DOUBLE_TAP_ACTION,
Settings.MINIPLAYER_DRAG_AND_DROP
Settings.MINIPLAYER_DISABLE_DRAG_AND_DROP
);
}
}

View File

@@ -131,11 +131,11 @@ public class ReturnYouTubeDislikePatch {
String conversionContextString = conversionContext.toString();
if (isRollingNumber && !conversionContextString.contains("video_action_bar.eml")) {
if (isRollingNumber && !conversionContextString.contains("video_action_bar.e")) {
return original;
}
if (conversionContextString.contains("segmented_like_dislike_button.eml")) {
if (conversionContextString.contains("segmented_like_dislike_button.e")) {
// Regular video.
ReturnYouTubeDislike videoData = currentVideoData;
if (videoData == null) {
@@ -153,12 +153,12 @@ public class ReturnYouTubeDislikePatch {
}
if (Utils.containsAny(conversionContextString,
"|shorts_dislike_button.eml", "|reel_dislike_button.eml")) {
"|shorts_dislike_button.e", "|reel_dislike_button.e")) {
return getShortsSpan(original, true);
}
if (Utils.containsAny(conversionContextString,
"|shorts_like_button.eml", "|reel_like_button.eml")) {
"|shorts_like_button.e", "|reel_like_button.e")) {
if (!Utils.containsNumber(original)) {
Logger.printDebug(() -> "Replacing hidden likes count");
return getShortsSpan(original, false);

View File

@@ -105,17 +105,17 @@ public final class AdsFilter extends Filter {
Settings.HIDE_VIEW_PRODUCTS_BANNER,
"product_item",
"products_in_video",
"shopping_overlay.eml" // Video player overlay shopping links.
"shopping_overlay.e" // Video player overlay shopping links.
);
final var shoppingLinks = new StringFilterGroup(
Settings.HIDE_SHOPPING_LINKS,
"shopping_description_shelf.eml"
"shopping_description_shelf.e"
);
playerShoppingShelf = new StringFilterGroup(
Settings.HIDE_CREATOR_STORE_SHELF,
"horizontal_shelf.eml"
"horizontal_shelf.e"
);
playerShoppingShelfBuffer = new ByteArrayFilterGroup(
@@ -131,7 +131,7 @@ public final class AdsFilter extends Filter {
final var merchandise = new StringFilterGroup(
Settings.HIDE_MERCHANDISE_BANNERS,
"product_carousel",
"shopping_carousel.eml" // Channel profile shopping shelf.
"shopping_carousel.e" // Channel profile shopping shelf.
);
final var selfSponsor = new StringFilterGroup(

View File

@@ -14,7 +14,7 @@ public final class AdvancedVideoQualityMenuFilter extends Filter {
public AdvancedVideoQualityMenuFilter() {
addPathCallbacks(new StringFilterGroup(
Settings.ADVANCED_VIDEO_QUALITY_MENU,
"quick_quality_sheet_content.eml-js"
"quick_quality_sheet_content.e"
));
}

View File

@@ -4,13 +4,13 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
final class ButtonsFilter extends Filter {
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.eml";
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.e";
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.e";
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.e";
/**
* Video bar path when the video information is collapsed. Seems to shown only with 20.14+
*/
private static final String COMPACTIFY_VIDEO_ACTION_BAR_PATH = "compactify_video_action_bar.eml";
private static final String COMPACTIFY_VIDEO_ACTION_BAR_PATH = "compactify_video_action_bar.e";
private static final String ANIMATED_VECTOR_TYPE_PATH = "AnimatedVectorType";
private final StringFilterGroup likeSubscribeGlow;
@@ -28,12 +28,12 @@ final class ButtonsFilter extends Filter {
likeSubscribeGlow = new StringFilterGroup(
Settings.DISABLE_LIKE_SUBSCRIBE_GLOW,
"animated_button_border.eml"
"animated_button_border.e"
);
bufferFilterPathGroup = new StringFilterGroup(
null,
"|ContainerType|button.eml"
"|ContainerType|button.e"
);
addPathCallbacks(
@@ -45,7 +45,7 @@ final class ButtonsFilter extends Filter {
),
new StringFilterGroup(
Settings.HIDE_DOWNLOAD_BUTTON,
"|download_button.eml"
"|download_button.e"
),
new StringFilterGroup(
Settings.HIDE_SAVE_BUTTON,
@@ -53,7 +53,7 @@ final class ButtonsFilter extends Filter {
),
new StringFilterGroup(
Settings.HIDE_CLIP_BUTTON,
"|clip_button.eml"
"|clip_button.e"
)
);

View File

@@ -6,18 +6,21 @@ import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
final class CommentsFilter extends Filter {
private static final String COMMENT_COMPOSER_PATH = "comment_composer.e";
private final StringFilterGroup chipBar;
private final ByteArrayFilterGroup aiCommentsSummary;
private final StringFilterGroup emojiAndTimestampButtons;
public CommentsFilter() {
var chatSummary = new StringFilterGroup(
Settings.HIDE_COMMENTS_AI_CHAT_SUMMARY,
"live_chat_summary_banner.eml"
"live_chat_summary_banner.e"
);
chipBar = new StringFilterGroup(
Settings.HIDE_COMMENTS_AI_SUMMARY,
"chip_bar.eml"
"chip_bar.e"
);
aiCommentsSummary = new ByteArrayFilterGroup(
@@ -32,8 +35,8 @@ final class CommentsFilter extends Filter {
var commentsByMembers = new StringFilterGroup(
Settings.HIDE_COMMENTS_BY_MEMBERS_HEADER,
"sponsorships_comments_header.eml",
"sponsorships_comments_footer.eml"
"sponsorships_comments_header.e",
"sponsorships_comments_footer.e"
);
var comments = new StringFilterGroup(
@@ -49,7 +52,12 @@ final class CommentsFilter extends Filter {
var createAShort = new StringFilterGroup(
Settings.HIDE_COMMENTS_CREATE_A_SHORT_BUTTON,
"composer_short_creation_button.eml"
"composer_short_creation_button.e"
);
emojiAndTimestampButtons = new StringFilterGroup(
Settings.HIDE_COMMENTS_EMOJI_AND_TIMESTAMP_BUTTONS,
"|CellType|ContainerType|ContainerType|ContainerType|ContainerType|ContainerType|"
);
var previewComment = new StringFilterGroup(
@@ -61,12 +69,7 @@ final class CommentsFilter extends Filter {
var thanksButton = new StringFilterGroup(
Settings.HIDE_COMMENTS_THANKS_BUTTON,
"super_thanks_button.eml"
);
StringFilterGroup timestampButton = new StringFilterGroup(
Settings.HIDE_COMMENTS_TIMESTAMP_BUTTON,
"composer_timestamp_button.eml"
"super_thanks_button.e"
);
addPathCallbacks(
@@ -77,9 +80,9 @@ final class CommentsFilter extends Filter {
comments,
communityGuidelines,
createAShort,
emojiAndTimestampButtons,
previewComment,
thanksButton,
timestampButton
thanksButton
);
}
@@ -93,6 +96,10 @@ final class CommentsFilter extends Filter {
&& aiCommentsSummary.check(buffer).isFiltered();
}
if (matchedGroup == emojiAndTimestampButtons) {
return path.startsWith(COMMENT_COMPOSER_PATH);
}
return true;
}
}

View File

@@ -29,12 +29,12 @@ final class DescriptionComponentsFilter extends Filter {
aiGeneratedVideoSummarySection = new StringFilterGroup(
Settings.HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION,
"cell_expandable_metadata.eml"
"cell_expandable_metadata.e"
);
final StringFilterGroup askSection = new StringFilterGroup(
Settings.HIDE_ASK_SECTION,
"youchat_entrypoint.eml"
"youchat_entrypoint.e"
);
final StringFilterGroup attributesSection = new StringFilterGroup(
@@ -65,7 +65,7 @@ final class DescriptionComponentsFilter extends Filter {
macroMarkersCarousel = new StringFilterGroup(
null,
"macro_markers_carousel.eml"
"macro_markers_carousel.e"
);
macroMarkersCarouselGroupList.addAll(
@@ -81,7 +81,7 @@ final class DescriptionComponentsFilter extends Filter {
horizontalShelf = new StringFilterGroup(
Settings.HIDE_ATTRIBUTES_SECTION,
"horizontal_shelf.eml"
"horizontal_shelf.e"
);
cellVideoAttribute = new ByteArrayFilterGroup(

View File

@@ -9,7 +9,7 @@ public final class HideInfoCardsFilter extends Filter {
addIdentifierCallbacks(
new StringFilterGroup(
Settings.HIDE_INFO_CARDS,
"info_card_teaser_overlay.eml"
"info_card_teaser_overlay.e"
)
);
}

View File

@@ -79,10 +79,10 @@ final class KeywordContentFilter extends Filter {
"search_vwc_description_transition_key",
"g-high-recZ",
// Text and litho components found in the buffer that belong to path filters.
"expandable_metadata.eml",
"thumbnail.eml",
"avatar.eml",
"overflow_button.eml",
"expandable_metadata.e",
"thumbnail.e",
"avatar.e",
"overflow_button.e",
"shorts-lockup-image",
"shorts-lockup.overlay-metadata.secondary-text",
"YouTubeSans-SemiBold",
@@ -94,16 +94,16 @@ final class KeywordContentFilter extends Filter {
*/
private final StringFilterGroup startsWithFilter = new StringFilterGroup(
null, // Multiple settings are used and must be individually checked if active.
"home_video_with_context.eml",
"search_video_with_context.eml",
"video_with_context.eml", // Subscription tab videos.
"related_video_with_context.eml",
"home_video_with_context.e",
"search_video_with_context.e",
"video_with_context.e", // Subscription tab videos.
"related_video_with_context.e",
// A/B test for subscribed video, and sometimes when tablet layout is enabled.
"video_lockup_with_attachment.eml",
"compact_video.eml",
"video_lockup_with_attachment.e",
"compact_video.e",
"inline_shorts",
"shorts_video_cell",
"shorts_pivot_item.eml"
"shorts_pivot_item.e"
);
/**
@@ -112,9 +112,9 @@ final class KeywordContentFilter extends Filter {
@SuppressWarnings("FieldCanBeLocal")
private final StringFilterGroup containsFilter = new StringFilterGroup(
null,
"modern_type_shelf_header_content.eml",
"shorts_lockup_cell.eml", // Part of 'shorts_shelf_carousel.eml'
"video_card.eml" // Shorts that appear in a horizontal shelf.
"modern_type_shelf_header_content.e",
"shorts_lockup_cell.e", // Part of 'shorts_shelf_carousel.e'
"video_card.e" // Shorts that appear in a horizontal shelf.
);
/**
@@ -125,10 +125,10 @@ final class KeywordContentFilter extends Filter {
* the buffer of the parent component was already searched and passed.
*/
private final StringTrieSearch exceptions = new StringTrieSearch(
"metadata.eml",
"thumbnail.eml",
"avatar.eml",
"overflow_button.eml"
"metadata.e",
"thumbnail.e",
"avatar.e",
"overflow_button.e"
);
/**

View File

@@ -3,6 +3,9 @@ package app.revanced.extension.youtube.patches.components;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
import android.graphics.drawable.Drawable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.view.View;
import android.widget.ImageView;
@@ -73,18 +76,18 @@ public final class LayoutComponentsFilter extends Filter {
communityPosts = new StringFilterGroup(
Settings.HIDE_COMMUNITY_POSTS,
"post_base_wrapper", // may be obsolete and no longer needed.
"text_post_root.eml",
"images_post_root.eml",
"images_post_slim.eml", // may be obsolete and no longer needed.
"images_post_root_slim.eml",
"text_post_root_slim.eml",
"post_base_wrapper_slim.eml",
"poll_post_root.eml",
"videos_post_root.eml",
"post_shelf_slim.eml",
"videos_post_responsive_root.eml",
"text_post_responsive_root.eml",
"poll_post_responsive_root.eml"
"text_post_root.e",
"images_post_root.e",
"images_post_slim.e", // may be obsolete and no longer needed.
"images_post_root_slim.e",
"text_post_root_slim.e",
"post_base_wrapper_slim.e",
"poll_post_root.e",
"videos_post_root.e",
"post_shelf_slim.e",
"videos_post_responsive_root.e",
"text_post_responsive_root.e",
"poll_post_responsive_root.e"
);
final var subscribersCommunityGuidelines = new StringFilterGroup(
@@ -146,7 +149,7 @@ public final class LayoutComponentsFilter extends Filter {
final var channelLinksPreview = new StringFilterGroup(
Settings.HIDE_LINKS_PREVIEW,
"attribution.eml"
"attribution.e"
);
final var emergencyBox = new StringFilterGroup(
@@ -187,8 +190,8 @@ public final class LayoutComponentsFilter extends Filter {
final var playables = new StringFilterGroup(
Settings.HIDE_PLAYABLES,
"horizontal_gaming_shelf.eml",
"mini_game_card.eml"
"horizontal_gaming_shelf.e",
"mini_game_card.e"
);
// Playable horizontal shelf header.
@@ -225,7 +228,7 @@ public final class LayoutComponentsFilter extends Filter {
compactChannelBarInnerButton = new StringFilterGroup(
null,
"|button.eml"
"|button.e"
);
joinMembershipButton = new ByteArrayFilterGroup(
@@ -245,13 +248,13 @@ public final class LayoutComponentsFilter extends Filter {
final var videoRecommendationLabels = new StringFilterGroup(
Settings.HIDE_VIDEO_RECOMMENDATION_LABELS,
"endorsement_header_footer.eml"
"endorsement_header_footer.e"
);
channelProfile = new StringFilterGroup(
null,
"channel_profile.eml",
"page_header.eml"
"channel_profile.e",
"page_header.e"
);
channelProfileBuffer = new ByteArrayFilterGroupList();
channelProfileBuffer.addAll(new ByteArrayFilterGroup(
@@ -266,15 +269,15 @@ public final class LayoutComponentsFilter extends Filter {
horizontalShelves = new StringFilterGroup(
Settings.HIDE_HORIZONTAL_SHELVES,
"horizontal_video_shelf.eml",
"horizontal_shelf.eml",
"horizontal_shelf_inline.eml",
"horizontal_tile_shelf.eml"
"horizontal_video_shelf.e",
"horizontal_shelf.e",
"horizontal_shelf_inline.e",
"horizontal_tile_shelf.e"
);
ticketShelf = new ByteArrayFilterGroup(
Settings.HIDE_TICKET_SHELF,
"ticket_item.eml"
"ticket_item.e"
);
addPathCallbacks(
@@ -500,4 +503,62 @@ public final class LayoutComponentsFilter extends Filter {
// This check is important as the shelf layout is used for the library tab playlists.
return NavigationButton.getSelectedNavigationButton() != NavigationButton.LIBRARY;
}
/**
* Injection point.
*/
public static SpannableString modifyFeedSubtitleSpan(SpannableString original, float truncationDimension) {
try {
final boolean hideViewCount = Settings.HIDE_VIEW_COUNT.get();
final boolean hideUploadTime = Settings.HIDE_UPLOAD_TIME.get();
if (!hideViewCount && !hideUploadTime) {
return original;
}
// Applies only for these specific dimensions.
if (truncationDimension == 16f || truncationDimension == 42f) {
String delimiter = " · ";
final int delimiterLength = delimiter.length();
// Index includes the starting delimiter.
final int viewCountStartIndex = TextUtils.indexOf(original, delimiter);
if (viewCountStartIndex < 0) {
return original;
}
final int uploadTimeStartIndex = TextUtils.indexOf(original, delimiter,
viewCountStartIndex + delimiterLength);
if (uploadTimeStartIndex < 0) {
return original;
}
// Ensure there is exactly 2 delimiters.
if (TextUtils.indexOf(original, delimiter,
uploadTimeStartIndex + delimiterLength) >= 0) {
return original;
}
// Make a mutable copy that keeps existing span styling.
SpannableStringBuilder builder = new SpannableStringBuilder(original);
// Remove the sections.
if (hideUploadTime) {
builder.delete(uploadTimeStartIndex, original.length());
}
if (hideViewCount) {
builder.delete(viewCountStartIndex, uploadTimeStartIndex);
}
SpannableString replacement = new SpannableString(builder);
Logger.printDebug(() -> "Replacing feed subtitle span: " + original + " with: " + replacement);
return replacement;
}
} catch (Exception ex) {
Logger.printException(() -> "modifyFeedSubtitleSpan failure", ex);
}
return original;
}
}

View File

@@ -24,13 +24,13 @@ public final class PlaybackSpeedMenuFilter extends Filter {
// 0.05x litho speed menu.
var playbackRateSelectorGroup = new StringFilterGroup(
Settings.CUSTOM_SPEED_MENU,
"playback_rate_selector_menu_sheet.eml-js"
"playback_rate_selector_menu_sheet.e"
);
// Old litho based speed menu.
oldPlaybackMenuGroup = new StringFilterGroup(
Settings.CUSTOM_SPEED_MENU,
"playback_speed_sheet_content.eml-js");
"playback_speed_sheet_content.e");
addPathCallbacks(playbackRateSelectorGroup, oldPlaybackMenuGroup);
}

View File

@@ -38,7 +38,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
addPathCallbacks(
videoQualityMenuFooter,
new StringFilterGroup(null, "overflow_menu_item.eml")
new StringFilterGroup(null, "overflow_menu_item.e")
);
flyoutFilterGroupList.addAll(

View File

@@ -72,8 +72,8 @@ public final class ReturnYouTubeDislikeFilter extends Filter {
// But if swiping back to a previous video and liking/disliking, then only that single button reloads.
// So must check for both buttons.
addPathCallbacks(
new StringFilterGroup(null, "|shorts_like_button.eml"),
new StringFilterGroup(null, "|shorts_dislike_button.eml")
new StringFilterGroup(null, "|shorts_like_button.e"),
new StringFilterGroup(null, "|shorts_dislike_button.e")
);
// After the button identifiers is binary data and then the video id for that specific short.

View File

@@ -18,12 +18,12 @@ import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public final class ShortsFilter extends Filter {
private static final boolean HIDE_SHORTS_NAVIGATION_BAR = Settings.HIDE_SHORTS_NAVIGATION_BAR.get();
private static final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.eml";
private static final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.e";
/**
* For paid promotion label and subscribe button that appears in the channel bar.
*/
private static final String REEL_METAPANEL_PATH = "reel_metapanel.eml";
private static final String REEL_METAPANEL_PATH = "reel_metapanel.e";
/**
* Tags that appears when opening the Shorts player.
@@ -74,7 +74,7 @@ public final class ShortsFilter extends Filter {
// Use a different filter group for this pattern, as it requires an additional check after matching.
shelfHeader = new StringFilterGroup(
null,
"shelf_header.eml"
"shelf_header.e"
);
addIdentifierCallbacks(shortsIdentifiers, shelfHeader);
@@ -85,11 +85,11 @@ public final class ShortsFilter extends Filter {
shortsCompactFeedVideo = new StringFilterGroup(null,
// Shorts that appear in the feed/search when the device is using tablet layout.
"compact_video.eml",
// 'video_lockup_with_attachment.eml' is shown instead of 'compact_video.eml' for some users
"video_lockup_with_attachment.eml",
"compact_video.e",
// 'video_lockup_with_attachment.e' is shown instead of 'compact_video.e' for some users
"video_lockup_with_attachment.e",
// Search results that appear in a horizontal shelf.
"video_card.eml");
"video_card.e");
// Filter out items that use the 'frame0' thumbnail.
// This is a valid thumbnail for both regular videos and Shorts,
@@ -134,31 +134,31 @@ public final class ShortsFilter extends Filter {
StringFilterGroup stickers = new StringFilterGroup(
Settings.HIDE_SHORTS_STICKERS,
"stickers_layer.eml"
"stickers_layer.e"
);
StringFilterGroup likeFountain = new StringFilterGroup(
Settings.HIDE_SHORTS_LIKE_FOUNTAIN,
"like_fountain.eml"
"like_fountain.e"
);
StringFilterGroup likeButton = new StringFilterGroup(
Settings.HIDE_SHORTS_LIKE_BUTTON,
"shorts_like_button.eml",
"reel_like_button.eml"
"shorts_like_button.e",
"reel_like_button.e"
);
StringFilterGroup dislikeButton = new StringFilterGroup(
Settings.HIDE_SHORTS_DISLIKE_BUTTON,
"shorts_dislike_button.eml",
"reel_dislike_button.eml"
"shorts_dislike_button.e",
"reel_dislike_button.e"
);
StringFilterGroup previewComment = new StringFilterGroup(
Settings.HIDE_SHORTS_PREVIEW_COMMENT,
// Preview comment that can popup while a Short is playing.
// Uses no bundled icons, and instead the users profile photo is shown.
"participation_bar.eml"
"participation_bar.e"
);
joinButton = new StringFilterGroup(
@@ -173,20 +173,20 @@ public final class ShortsFilter extends Filter {
paidPromotionButton = new StringFilterGroup(
Settings.HIDE_PAID_PROMOTION_LABEL,
"reel_player_disclosure.eml"
"reel_player_disclosure.e"
);
shortsActionBar = new StringFilterGroup(
null,
"shorts_action_bar.eml",
"reel_action_bar.eml"
"shorts_action_bar.e",
"reel_action_bar.e"
);
useSoundButton = new StringFilterGroup(
Settings.HIDE_SHORTS_USE_SOUND_BUTTON,
// First filter needed for "Use this sound" that can appear when viewing Shorts
// through the "Short remixing this video" section.
"floating_action_button.eml",
"floating_action_button.e",
// Second filter needed for "Use this sound" that can appear below the video title.
REEL_METAPANEL_PATH
);
@@ -209,13 +209,13 @@ public final class ShortsFilter extends Filter {
videoActionButton = new StringFilterGroup(
null,
// Can be simply 'button.eml', 'shorts_video_action_button.eml' or 'reel_action_button.eml'
"button.eml"
// Can be simply 'button.e', 'shorts_video_action_button.e' or 'reel_action_button.e'
"button.e"
);
suggestedAction = new StringFilterGroup(
null,
"suggested_action.eml"
"suggested_action.e"
);
addPathCallbacks(

View File

@@ -18,28 +18,20 @@ public class SpoofVideoStreamsPatch {
* Injection point.
*/
public static void setClientOrderToUse() {
final boolean forceAVC = Settings.FORCE_AVC_CODEC.get();
// VR 1.61 uses VP9/AV1, and cannot force AVC.
ClientType client = Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
if (forceAVC && client == ANDROID_VR_1_61_48) {
client = ANDROID_VR_1_43_32; // Use VR 1.43 instead.
if (Settings.FORCE_AVC_CODEC.get() && client == ANDROID_VR_1_61_48) {
// VR 1.61 uses VP9/AV1, and cannot force AVC. Use 1.43 instead.
client = ANDROID_VR_1_43_32;
}
List<ClientType> availableClients = forceAVC
? List.of(
List<ClientType> availableClients = List.of(
ANDROID_VR_1_43_32,
VISIONOS,
ANDROID_CREATOR,
ANDROID_VR_1_61_48,
IPADOS)
: List.of(
ANDROID_VR_1_61_48,
VISIONOS,
ANDROID_CREATOR,
ANDROID_VR_1_43_32,
IPADOS
);
IPADOS);
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(
availableClients, client);

View File

@@ -1,16 +1,13 @@
package app.revanced.extension.youtube.patches.theme;
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle.styleFromOrdinal;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.theme.BaseThemePatch;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class ThemePatch {
public class ThemePatch extends BaseThemePatch {
public enum SplashScreenAnimationStyle {
DEFAULT(0),
FPS_60_ONE_SECOND(1),
@@ -43,57 +40,39 @@ public class ThemePatch {
}
}
// color constants used in relation with litho components
// Color constants used in relation with litho components.
private static final int[] WHITE_VALUES = {
-1, // comments chip background
-394759, // music related results panel background
-83886081, // video chapters list background
0xFFFFFFFF, // Comments chip background.
0xFFF9F9F9, // Music related results panel background.
0xFAFFFFFF, // Video chapters list background.
};
private static final int[] DARK_VALUES = {
-14145496, // explore drawer background
-14606047, // comments chip background
-15198184, // music related results panel background
-15790321, // comments chip background (new layout)
-98492127 // video chapters list background
0xFF282828, // Explore drawer background.
0xFF212121, // Comments chip background.
0xFF181818, // Music related results panel background.
0xFF0F0F0F, // Comments chip background (new layout).
0xFA212121, // Video chapters list background.
};
// Background colors.
private static final int WHITE_COLOR = Utils.getResourceColor("yt_white1");
private static final int BLACK_COLOR = Utils.getResourceColor("yt_black1");
private static final boolean GRADIENT_LOADING_SCREEN_ENABLED = Settings.GRADIENT_LOADING_SCREEN.get();
/**
* Injection point.
*
* <p>
* Change the color of Litho components.
* If the color of the component matches one of the values, return the background color .
* If the color of the component matches one of the values, return the background color.
*
* @param originalValue The original color value.
* @return The new or original color value
* @return The new or original color value.
*/
public static int getValue(int originalValue) {
if (Utils.isDarkModeEnabled()) {
if (anyEquals(originalValue, DARK_VALUES)) return BLACK_COLOR;
} else {
if (anyEquals(originalValue, WHITE_VALUES)) return WHITE_COLOR;
}
return originalValue;
}
private static boolean anyEquals(int value, int... of) {
for (int v : of) if (value == v) return true;
return false;
return processColorValue(originalValue, DARK_VALUES, WHITE_VALUES);
}
/**
* Injection point.
*/
public static boolean gradientLoadingScreenEnabled(boolean original) {
return GRADIENT_LOADING_SCREEN_ENABLED;
return Settings.GRADIENT_LOADING_SCREEN.get();
}
/**
@@ -108,7 +87,7 @@ public class ThemePatch {
final int replacement = style.style;
if (original != replacement) {
Logger.printDebug(() -> "Overriding splash screen style from: "
+ styleFromOrdinal(original) + " to: " + style);
+ SplashScreenAnimationStyle.styleFromOrdinal(original) + " to: " + style);
}
return replacement;

View File

@@ -11,6 +11,10 @@ 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.MiniplayerPatch.MiniplayerAnyModernAvailability;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideOverlayButtonsAvailability;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideRewindOrOverlayOpacityAvailability;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideSubtextsAvailability;
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;
@@ -51,6 +55,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE);
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
public static final BooleanSetting FORCE_AVC_CODEC = new BooleanSetting("revanced_force_avc_codec", FALSE, true, "revanced_force_avc_codec_user_dialog_message");
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, true);
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2);
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2);
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE);
@@ -71,9 +76,6 @@ public class Settings extends BaseSettings {
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
"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, true);
// Ads
public static final BooleanSetting HIDE_CREATOR_STORE_SHELF = new BooleanSetting("revanced_hide_creator_store_shelf", TRUE);
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true);
@@ -112,7 +114,9 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
public static final BooleanSetting HIDE_SURVEYS = new BooleanSetting("revanced_hide_surveys", TRUE);
public static final BooleanSetting HIDE_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", FALSE);
public static final BooleanSetting HIDE_UPLOAD_TIME = new BooleanSetting("revanced_hide_upload_time", FALSE, "revanced_hide_upload_time_user_dialog_message");
public static final BooleanSetting HIDE_VIDEO_RECOMMENDATION_LABELS = new BooleanSetting("revanced_hide_video_recommendation_labels", TRUE);
public static final BooleanSetting HIDE_VIEW_COUNT = new BooleanSetting("revanced_hide_view_count", FALSE, "revanced_hide_view_count_user_dialog_message");
// Alternative thumbnails
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
@@ -175,15 +179,15 @@ public class Settings extends BaseSettings {
// Miniplayer
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.DEFAULT, true);
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, new MiniplayerPatch.MiniplayerAnyModernAvailability());
public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_drag_and_drop", TRUE, true, new MiniplayerPatch.MiniplayerAnyModernAvailability());
public static final BooleanSetting MINIPLAYER_HORIZONTAL_DRAG = new BooleanSetting("revanced_miniplayer_horizontal_drag", FALSE, true, new MiniplayerHorizontalDragAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_OVERLAY_BUTTONS = new BooleanSetting("revanced_miniplayer_hide_overlay_buttons", FALSE, true, new MiniplayerPatch.MiniplayerHideOverlayButtonsAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, new MiniplayerPatch.MiniplayerHideSubtextsAvailability());
public static final BooleanSetting MINIPLAYER_DISABLE_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_disable_drag_and_drop", FALSE, true, new MiniplayerAnyModernAvailability());
public static final BooleanSetting MINIPLAYER_DISABLE_HORIZONTAL_DRAG = new BooleanSetting("revanced_miniplayer_disable_horizontal_drag", FALSE, true, new MiniplayerHorizontalDragAvailability());
public static final BooleanSetting MINIPLAYER_DISABLE_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_disable_rounded_corners", FALSE, true, new MiniplayerAnyModernAvailability());
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, new MiniplayerAnyModernAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_OVERLAY_BUTTONS = new BooleanSetting("revanced_miniplayer_hide_overlay_buttons", FALSE, true, new MiniplayerHideOverlayButtonsAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, new MiniplayerHideSubtextsAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, new MiniplayerPatch.MiniplayerHideRewindOrOverlayOpacityAvailability());
public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, new MiniplayerPatch.MiniplayerAnyModernAvailability());
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, new MiniplayerPatch.MiniplayerAnyModernAvailability());
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, new MiniplayerPatch.MiniplayerHideRewindOrOverlayOpacityAvailability());
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, new MiniplayerAnyModernAvailability());
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, new MiniplayerHideRewindOrOverlayOpacityAvailability());
// External downloader
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
@@ -199,9 +203,9 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_COMMENTS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_comments_community_guidelines", TRUE);
public static final BooleanSetting HIDE_COMMENTS_CREATE_A_SHORT_BUTTON = new BooleanSetting("revanced_hide_comments_create_a_short_button", TRUE);
public static final BooleanSetting HIDE_COMMENTS_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_comments_preview_comment", FALSE);
public static final BooleanSetting HIDE_COMMENTS_EMOJI_AND_TIMESTAMP_BUTTONS = new BooleanSetting("revanced_hide_comments_emoji_and_timestamp_buttons", FALSE);
public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE);
public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE);
public static final BooleanSetting HIDE_COMMENTS_TIMESTAMP_BUTTON = new BooleanSetting("revanced_hide_comments_timestamp_button", FALSE);
// Description
public static final BooleanSetting HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION = new BooleanSetting("revanced_hide_ai_generated_video_summary_section", FALSE);
@@ -352,7 +356,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting EXTERNAL_BROWSER = new BooleanSetting("revanced_external_browser", TRUE, true);
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true,
"revanced_spoof_device_dimensions_user_dialog_message");
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));
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
"revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));

View File

@@ -152,7 +152,7 @@ public class YouTubeActivityHook extends BaseActivityHook {
* @return if the original activity finish method should be allowed to run.
*/
@SuppressWarnings("unused")
public static boolean handleFinish() {
public static boolean handleBackPress() {
return YouTubeSearchViewController.handleFinish(searchViewController);
}
}

View File

@@ -83,12 +83,17 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
String summary = str("revanced_spoof_video_streams_about_no_audio_tracks");
switch (clientType) {
case ANDROID_VR_1_61_48 ->
summary += '\n' + str("revanced_spoof_video_streams_about_no_stable_volume");
case ANDROID_CREATOR ->
summary += '\n' + str("revanced_spoof_video_streams_about_no_av1")
+ '\n' + str("revanced_spoof_video_streams_about_no_stable_volume")
+ '\n' + str("revanced_spoof_video_streams_about_no_force_original_audio");
summary += '\n' + str("revanced_spoof_video_streams_about_no_stable_volume")
+ '\n' + str("revanced_spoof_video_streams_about_no_av1")
+ '\n' + str("revanced_spoof_video_streams_about_no_force_original_audio");
case ANDROID_VR_1_43_32 ->
summary += '\n' + str("revanced_spoof_video_streams_about_no_stable_volume")
+ '\n' + str("revanced_spoof_video_streams_about_no_av1");
case ANDROID_VR_1_61_48 ->
summary = str("revanced_spoof_video_streams_about_dropped_frames")
+ '\n' + summary
+ '\n' + str("revanced_spoof_video_streams_about_no_stable_volume");
case IPADOS ->
summary = str("revanced_spoof_video_streams_about_playback_failure")
+ '\n' + str("revanced_spoof_video_streams_about_no_av1");

View File

@@ -302,7 +302,7 @@ public class SponsorBlockUtils {
SpannableStringBuilder spannableBuilder = new SpannableStringBuilder();
spannableBuilder.append(segment.category.getTitle().toString());
spannableBuilder.append(segment.category.getTitleWithColorDot());
spannableBuilder.append('\n');
String startTime = formatSegmentTime(segment.start);

View File

@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true
android.useAndroidX = true
kotlin.code.style = official
version = 5.41.0-dev.13
version = 5.42.0-dev.11

View File

@@ -10,7 +10,7 @@ annotation = "1.9.1"
appcompat = "1.7.0"
okhttp = "5.0.0-alpha.14"
retrofit = "2.11.0"
guava = "33.4.0-jre"
guava = "33.5.0-jre"
protobuf-javalite = "4.32.0"
protoc = "4.32.0"
protobuf = "0.9.5"

42
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.10.1",
"semantic-release": "^24.2.7"
"semantic-release": "^24.2.9"
}
},
"node_modules/@babel/code-frame": {
@@ -6889,9 +6889,9 @@
"license": "MIT"
},
"node_modules/semantic-release": {
"version": "24.2.7",
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.7.tgz",
"integrity": "sha512-g7RssbTAbir1k/S7uSwSVZFfFXwpomUB9Oas0+xi9KStSCmeDXcA7rNhiskjLqvUe/Evhx8fVCT16OSa34eM5g==",
"version": "24.2.9",
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.9.tgz",
"integrity": "sha512-phCkJ6pjDi9ANdhuF5ElS10GGdAKY6R1Pvt9lT3SFhOwM4T7QZE7MLpBDbNruUx/Q3gFD92/UOFringGipRqZA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6909,7 +6909,7 @@
"find-versions": "^6.0.0",
"get-stream": "^6.0.0",
"git-log-parser": "^1.2.0",
"hook-std": "^3.0.0",
"hook-std": "^4.0.0",
"hosted-git-info": "^8.0.0",
"import-from-esm": "^2.0.0",
"lodash-es": "^4.17.21",
@@ -6921,7 +6921,7 @@
"read-package-up": "^11.0.0",
"resolve-from": "^5.0.0",
"semver": "^7.3.2",
"semver-diff": "^4.0.0",
"semver-diff": "^5.0.0",
"signale": "^1.2.1",
"yargs": "^17.5.1"
},
@@ -7045,6 +7045,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/semantic-release/node_modules/hook-std": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-4.0.0.tgz",
"integrity": "sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/semantic-release/node_modules/human-signals": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz",
@@ -7138,6 +7151,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/semantic-release/node_modules/semver-diff": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-5.0.0.tgz",
"integrity": "sha512-0HbGtOm+S7T6NGQ/pxJSJipJvc4DK3FcRVMRkhsIwJDJ4Jcz5DQC1cPPzB5GhzyHjwttW878HaWQq46CkL3cqg==",
"deprecated": "Deprecated as the semver package now supports this built-in.",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/semantic-release/node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",

View File

@@ -4,6 +4,6 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.10.1",
"semantic-release": "^24.2.7"
"semantic-release": "^24.2.9"
}
}

View File

@@ -284,6 +284,10 @@ public final class app/revanced/patches/instagram/misc/extension/SharedExtension
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/instagram/misc/links/OpenLinksExternallyPatchKt {
public static final fun getOpenLinksExternallyPatch ()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;
}
@@ -372,6 +376,10 @@ public final class app/revanced/patches/music/interaction/permanentshuffle/Perma
public static final fun getPermanentShufflePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/layout/branding/CustomBrandingPatchKt {
public static final fun getCustomBrandingPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/music/layout/castbutton/HideCastButtonKt {
public static final fun getHideCastButton ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -388,6 +396,10 @@ 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/theme/ThemePatchKt {
public static final fun getThemePatch ()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;
@@ -447,9 +459,14 @@ public final class app/revanced/patches/music/misc/spoof/UserAgentClientSpoofPat
public static final fun getUserAgentClientSpoofPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/misc/tracks/ForceOriginalAudioPatchKt {
public static final fun getForceOriginalAudioPatch ()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_10_or_greater ()Z
public static final fun is_8_11_or_greater ()Z
public static final fun is_8_15_or_greater ()Z
}
@@ -733,6 +750,11 @@ public final class app/revanced/patches/serviceportalbund/detection/root/RootDet
public static final fun getRootDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/shared/layout/theme/LithoColorHookPatchKt {
public static final fun getLithoColorHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun getLithoColorOverrideHook ()Lkotlin/jvm/functions/Function2;
}
public final class app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatchKt {
public static final fun checkEnvironmentPatch (Lapp/revanced/patcher/Fingerprint;Lapp/revanced/patcher/patch/Patch;[Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1297,6 +1319,10 @@ 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/viber/misc/navbar/HideNavigationButtonsKt {
public static final fun getHideNavigationButtonsPatch ()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;
}
@@ -1398,8 +1424,8 @@ public final class app/revanced/patches/youtube/layout/formfactor/ChangeFormFact
public static final fun getChangeFormFactorPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/layout/hide/endscreencards/HideEndscreenCardsPatchKt {
public static final fun getHideEndscreenCardsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
public final class app/revanced/patches/youtube/layout/hide/endscreencards/HideEndScreenCardsPatchKt {
public static final fun getHideEndScreenCardsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/layout/hide/endscreensuggestion/HideEndScreenSuggestedVideoPatchKt {

View File

@@ -3,6 +3,7 @@ package app.revanced.patches.instagram.hide.navigation
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.instagram.misc.extension.sharedExtensionPatch
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.findFreeRegister
import app.revanced.util.getReference
@@ -24,6 +25,8 @@ val hideNavigationButtonsPatch = bytecodePatch(
) {
compatibleWith("com.instagram.android")
dependsOn(sharedExtensionPatch)
val hideReels by booleanOption(
key = "hideReels",
default = true,
@@ -69,20 +72,22 @@ val hideNavigationButtonsPatch = bytecodePatch(
const-string v$freeRegister2, "$enumNameField"
invoke-static { v$buttonsListRegister, v$freeRegister, v$freeRegister2 }, $EXTENSION_CLASS_DESCRIPTOR->removeNavigationButtonByName(Ljava/util/List;Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;
move-result-object v$buttonsListRegister
"""
"""
}
if (hideReels!!)
if (hideReels!!) {
addInstructionsAtControlFlowLabel(
returnIndex,
instructionsRemoveButtonByName("fragment_clips")
)
}
if (hideCreate!!)
if (hideCreate!!) {
addInstructionsAtControlFlowLabel(
returnIndex,
instructionsRemoveButtonByName("fragment_share")
)
}
}
}
}

View File

@@ -0,0 +1,9 @@
package app.revanced.patches.instagram.misc.links
import app.revanced.patcher.fingerprint
internal const val TARGET_STRING = "Tracking.ARG_CLICK_SOURCE"
internal val inAppBrowserFunctionFingerprint = fingerprint {
returns("Z")
strings("TrackingInfo.ARG_MODULE_NAME", TARGET_STRING)
}

View File

@@ -0,0 +1,47 @@
package app.revanced.patches.instagram.misc.links
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.instagram.misc.extension.sharedExtensionPatch
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/instagram/misc/links/OpenLinksExternallyPatch;"
@Suppress("unused")
val openLinksExternallyPatch = bytecodePatch(
name = "Open links externally",
description = "Changes links to always open in your external browser, instead of the in-app browser.",
use = false,
) {
dependsOn(sharedExtensionPatch)
compatibleWith("com.instagram.android")
execute {
inAppBrowserFunctionFingerprint.let {
val stringMatchIndex = it.stringMatches?.first { match -> match.string == TARGET_STRING }!!.index
it.method.apply {
val urlResultObjIndex = indexOfFirstInstructionOrThrow(
stringMatchIndex, Opcode.MOVE_OBJECT_FROM16
)
// Register that contains the url after moving from a higher register.
val urlRegister = getInstruction<TwoRegisterInstruction>(urlResultObjIndex).registerA
addInstructions(
urlResultObjIndex + 1,
"""
invoke-static { v$urlRegister }, $EXTENSION_CLASS_DESCRIPTOR->openExternally(Ljava/lang/String;)Z
move-result v$urlRegister
return v$urlRegister
"""
)
}
}
}
}

View File

@@ -0,0 +1,82 @@
package app.revanced.patches.music.layout.branding
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.layout.branding.baseCustomBrandingPatch
import app.revanced.patches.shared.misc.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversed
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private val disableSplashAnimationPatch = bytecodePatch {
dependsOn(resourceMappingPatch)
execute {
// The existing YT animation usually only shows for a fraction of a second,
// and the existing animation does not match the new splash screen
// causing the original YT Music logo to momentarily flash on screen as the animation starts.
//
// Could replace the lottie animation file with our own custom animation (app_launch.json),
// but the animation is not always the same size as the launch screen and it's still
// barely shown. Instead turn off the animation entirely (app will also launch a little faster).
cairoSplashAnimationConfigFingerprint.method.apply {
val mainActivityLaunchAnimation = resourceMappings["layout", "main_activity_launch_animation"]
val literalIndex = indexOfFirstLiteralInstructionOrThrow(
mainActivityLaunchAnimation
)
val insertIndex = indexOfFirstInstructionReversed(literalIndex) {
this.opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setContentView"
} + 1
val jumpIndex = indexOfFirstInstructionOrThrow(insertIndex) {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.parameterTypes?.firstOrNull() == "Ljava/lang/Runnable;"
} + 1
addInstructionsWithLabels(
insertIndex,
"goto :skip_animation",
ExternalLabel("skip_animation", getInstruction(jumpIndex))
)
}
}
}
private const val APP_NAME = "YT Music ReVanced"
@Suppress("unused")
val customBrandingPatch = baseCustomBrandingPatch(
defaultAppName = APP_NAME,
appNameValues = mapOf(
"YT Music ReVanced" to APP_NAME,
"Music ReVanced" to "Music ReVanced",
"Music" to "Music",
"YT Music" to "YT Music",
),
resourceFolder = "custom-branding/music",
iconResourceFileNames = arrayOf(
"adaptiveproduct_youtube_music_2024_q4_background_color_108",
"adaptiveproduct_youtube_music_2024_q4_foreground_color_108",
"ic_launcher_release",
),
monochromeIconFileNames = arrayOf("ic_app_icons_themed_youtube_music.xml"),
block = {
dependsOn(disableSplashAnimationPatch)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52"
)
)
}
)

View File

@@ -0,0 +1,12 @@
package app.revanced.patches.music.layout.branding
import app.revanced.patcher.fingerprint
import app.revanced.patches.music.shared.YOUTUBE_MUSIC_MAIN_ACTIVITY_CLASS_TYPE
internal val cairoSplashAnimationConfigFingerprint = fingerprint {
returns("V")
parameters("Landroid/os/Bundle;")
custom { method, classDef ->
method.name == "onCreate" && method.definingClass == YOUTUBE_MUSIC_MAIN_ACTIVITY_CLASS_TYPE
}
}

View File

@@ -9,6 +9,9 @@ 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.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
@@ -29,6 +32,7 @@ val hideCastButton = bytecodePatch(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
resourceMappingPatch
)
compatibleWith(
@@ -39,6 +43,8 @@ val hideCastButton = bytecodePatch(
)
execute {
playerOverlayChip = resourceMappings["id", "player_overlay_chip"]
addResources("music", "layout.castbutton.hideCastButton")
PreferenceScreen.GENERAL.addPreferences(
@@ -59,7 +65,7 @@ val hideCastButton = bytecodePatch(
playerOverlayChipFingerprint.method.apply {
val resourceIndex = indexOfFirstLiteralInstructionOrThrow(playerOverlayChip)
val targetIndex = indexOfFirstInstructionOrThrow(resourceIndex, Opcode.MOVE_RESULT)
val targetIndex = indexOfFirstInstructionOrThrow(resourceIndex, Opcode.MOVE_RESULT_OBJECT)
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstruction(

View File

@@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
@@ -35,7 +36,23 @@ val navigationBarPatch = bytecodePatch(
resourceMappingPatch,
sharedExtensionPatch,
settingsPatch,
addResourcesPatch
addResourcesPatch,
resourcePatch {
execute {
// Ensure the first ImageView has 'layout_weight' to stay properly sized
// when the TextView is hidden.
document("res/layout/image_with_text_tab.xml").use { document ->
val imageView = document.getElementsByTagName("ImageView").item(0)
imageView?.let {
if (it.attributes.getNamedItem("android:layout_weight") == null) {
val attr = document.createAttribute("android:layout_weight")
attr.value = "0.5"
it.attributes.setNamedItem(attr)
}
}
}
}
}
)
compatibleWith(
@@ -46,10 +63,7 @@ val navigationBarPatch = bytecodePatch(
)
execute {
text1 = resourceMappings[
"id",
"text1",
]
text1 = resourceMappings["id", "text1"]
addResources("music", "layout.navigationbar.navigationBarPatch")
@@ -71,9 +85,7 @@ val navigationBarPatch = bytecodePatch(
)
tabLayoutTextFingerprint.method.apply {
/**
* Hide navigation labels.
*/
// Hide navigation labels.
val constIndex = indexOfFirstLiteralInstructionOrThrow(text1)
val targetIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST)
val targetParameter = getInstruction<ReferenceInstruction>(targetIndex).reference
@@ -87,9 +99,7 @@ val navigationBarPatch = bytecodePatch(
"invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->hideNavigationLabel(Landroid/widget/TextView;)V"
)
/**
* Set navigation enum and hide navigation buttons.
*/
// Set navigation enum and hide navigation buttons.
val enumIndex = tabLayoutTextFingerprint.patternMatch!!.startIndex + 3
val enumRegister = getInstruction<OneRegisterInstruction>(enumIndex).registerA
val insertEnumIndex = indexOfFirstInstructionOrThrow(Opcode.AND_INT_LIT8) - 2

View File

@@ -0,0 +1,43 @@
package app.revanced.patches.music.layout.theme
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.shared.layout.theme.THEME_DEFAULT_DARK_COLOR_NAMES
import app.revanced.patches.shared.layout.theme.baseThemePatch
import app.revanced.patches.shared.layout.theme.baseThemeResourcePatch
import app.revanced.patches.shared.layout.theme.darkThemeBackgroundColorOption
import app.revanced.patches.shared.misc.settings.overrideThemeColors
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/theme/ThemePatch;"
@Suppress("unused")
val themePatch = baseThemePatch(
extensionClassDescriptor = EXTENSION_CLASS_DESCRIPTOR,
block = {
dependsOn(
sharedExtensionPatch,
baseThemeResourcePatch(
darkColorNames = THEME_DEFAULT_DARK_COLOR_NAMES + setOf(
"yt_black_pure",
"yt_black_pure_opacity80",
"ytm_color_grey_12",
"material_grey_800"
)
)
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52"
)
)
},
executeBlock = {
overrideThemeColors(
null,
darkThemeBackgroundColorOption.value!!
)
}
)

View File

@@ -0,0 +1,44 @@
package app.revanced.patches.music.misc.fileprovider
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
import app.revanced.patches.music.utils.fix.fileprovider.fileProviderResolverFingerprint
internal fun fileProviderPatch(
youtubePackageName: String,
musicPackageName: String
) = bytecodePatch(
description = "Fixes broken YouTube Music file provider that prevents sharing with specific apps such as Instagram."
) {
finalize {
// Must do modification last, so change package name value is correctly set.
val musicChangedPackageName = setOrGetFallbackPackageName(musicPackageName)
// For some reason, if the app gets "android.support.FILE_PROVIDER_PATHS",
// the package name of YouTube is used, not the package name of the YT Music.
//
// There is no issue in the stock YT Music, but this is an issue in the GmsCore Build.
// https://github.com/ReVanced/revanced-patches/issues/55
//
// To solve this issue, replace the package name of YouTube with YT Music's package name.
fileProviderResolverFingerprint.method.addInstructionsWithLabels(
0,
"""
const-string v0, "com.google.android.youtube.fileprovider"
invoke-static { p1, v0 }, Ljava/util/Objects;->equals(Ljava/lang/Object;Ljava/lang/Object;)Z
move-result v0
if-nez v0, :fix
const-string v0, "$youtubePackageName.fileprovider"
invoke-static { p1, v0 }, Ljava/util/Objects;->equals(Ljava/lang/Object;Ljava/lang/Object;)Z
move-result v0
if-nez v0, :fix
goto :ignore
:fix
const-string p1, "$musicChangedPackageName.fileprovider"
:ignore
nop
"""
)
}
}

View File

@@ -0,0 +1,11 @@
package app.revanced.patches.music.utils.fix.fileprovider
import app.revanced.patcher.fingerprint
internal val fileProviderResolverFingerprint = fingerprint {
returns("L")
strings(
"android.support.FILE_PROVIDER_PATHS",
"Name must not be empty"
)
}

View File

@@ -9,6 +9,7 @@ import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
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.music.misc.fileprovider.fileProviderPatch
import app.revanced.patches.shared.castContextFetchFingerprint
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
@@ -60,6 +61,10 @@ private fun gmsCoreSupportResourcePatch(
) {
dependsOn(
addResourcesPatch,
settingsPatch
settingsPatch,
fileProviderPatch(
MUSIC_PACKAGE_NAME,
REVANCED_MUSIC_PACKAGE_NAME
)
)
}

View File

@@ -6,6 +6,7 @@ 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.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.BasePreference
import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen
@@ -108,7 +109,8 @@ val settingsPatch = bytecodePatch(
modifyActivityForSettingsInjection(
googleApiActivityFingerprint.classDef,
googleApiActivityFingerprint.method,
GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR
GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR,
true
)
}
@@ -125,7 +127,7 @@ fun newIntent(settingsName: String) = IntentPreference.Intent(
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")
setOrGetFallbackPackageName(MUSIC_PACKAGE_NAME)
}
object PreferenceScreen : BasePreferenceScreen() {

View File

@@ -0,0 +1,34 @@
package app.revanced.patches.music.misc.tracks
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.music.playservice.is_8_10_or_greater
import app.revanced.patches.music.playservice.versionCheckPatch
import app.revanced.patches.music.shared.mainActivityOnCreateFingerprint
import app.revanced.patches.shared.misc.audio.forceOriginalAudioPatch
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/music/patches/ForceOriginalAudioPatch;"
@Suppress("unused")
val forceOriginalAudioPatch = forceOriginalAudioPatch(
block = {
dependsOn(
sharedExtensionPatch,
settingsPatch,
versionCheckPatch
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52"
)
)
},
fixUseLocalizedAudioTrackFlag = is_8_10_or_greater,
mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint,
subclassExtensionClassDescriptor = EXTENSION_CLASS_DESCRIPTOR,
preferenceScreen = PreferenceScreen.MISC,
)

View File

@@ -7,6 +7,8 @@ import app.revanced.util.findPlayStoreServicesVersion
var is_7_33_or_greater = false
private set
var is_8_10_or_greater = false
private set
var is_8_11_or_greater = false
private set
var is_8_15_or_greater = false
@@ -22,6 +24,7 @@ val versionCheckPatch = resourcePatch(
// 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_10_or_greater = 244799000 <= playStoreServicesVersion
is_8_11_or_greater = 251199000 <= playStoreServicesVersion
is_8_15_or_greater = 251530000 <= playStoreServicesVersion
}

View File

@@ -1,6 +1,5 @@
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
@@ -27,9 +26,7 @@ val fixRedgifsApi = fixRedgifsApiPatch(
}
replaceInstruction(
index,
"""
invoke-static { }, ${EXTENSION_CLASS_DESCRIPTOR}->$CREATE_NEW_CLIENT_METHOD
"""
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->$CREATE_NEW_CLIENT_METHOD"
)
}

View File

@@ -0,0 +1,173 @@
package app.revanced.patches.shared.layout.branding
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.ResourcePatchBuilder
import app.revanced.patcher.patch.ResourcePatchContext
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.util.ResourceGroup
import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.copyResources
import java.io.File
import java.nio.file.Files
import java.util.logging.Logger
private const val REVANCED_ICON = "ReVanced*Logo" // Can never be a valid path.
internal val mipmapDirectories = arrayOf(
// Target app does not have ldpi icons.
"mdpi",
"hdpi",
"xhdpi",
"xxhdpi",
"xxxhdpi",
).map { "mipmap-$it" }.toTypedArray()
private fun formatResourceFileList(resourceNames: Array<String>) = resourceNames.joinToString("\n") { "- $it" }
/**
* Attempts to fix unescaped and invalid characters not allowed for an Android app name.
*/
private fun escapeAppName(name: String): String? {
// Remove ASCII control characters.
val cleanedName = name.filter { it.code >= 32 }
// Replace invalid XML characters with escaped equivalents.
val escapedName = cleanedName
.replace("&", "&amp;") // Must be first to avoid double-escaping.
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace(Regex("(?<!&)\""), "&quot;")
// Trim empty spacing.
val trimmed = escapedName.trim()
return trimmed.ifBlank { null }
}
/**
* Shared custom branding patch for YouTube and YT Music.
*/
internal fun baseCustomBrandingPatch(
defaultAppName: String,
appNameValues: Map<String, String>,
resourceFolder: String,
iconResourceFileNames: Array<String>,
monochromeIconFileNames: Array<String>,
block: ResourcePatchBuilder.() -> Unit = {},
executeBlock: ResourcePatchContext.() -> Unit = {}
): ResourcePatch = resourcePatch(
name = "Custom branding",
description = "Applies a custom app name and icon. Defaults to \"$defaultAppName\" and the ReVanced logo.",
use = false,
) {
val iconResourceFileNamesPng = iconResourceFileNames.map { "$it.png" }.toTypedArray<String>()
val appName by stringOption(
key = "appName",
default = defaultAppName,
values = appNameValues,
title = "App name",
description = "The name of the app.",
)
val iconPath by stringOption(
key = "iconPath",
default = REVANCED_ICON,
values = mapOf("ReVanced Logo" to REVANCED_ICON),
title = "App icon",
description = """
The icon to apply to the app.
If a path to a folder is provided, the folder must contain the following folders:
${formatResourceFileList(mipmapDirectories)}
Each of these folders must contain the following files:
${formatResourceFileList(iconResourceFileNamesPng)}
Optionally, a 'drawable' folder with the monochrome icon files:
${formatResourceFileList(monochromeIconFileNames)}
""".trimIndentMultiline(),
)
block()
execute {
val mipmapIconResourceGroups = mipmapDirectories.map { directory ->
ResourceGroup(
directory,
*iconResourceFileNamesPng,
)
}
val iconPathTrimmed = iconPath!!.trim()
if (iconPathTrimmed == REVANCED_ICON) {
// Replace mipmap icons with preset patch icons.
mipmapIconResourceGroups.forEach { groupResources ->
copyResources(resourceFolder, groupResources)
}
// Replace monochrome icons.
monochromeIconFileNames.forEach { fileName ->
copyResources(
resourceFolder,
ResourceGroup("drawable", fileName)
)
}
} else {
val filePath = File(iconPathTrimmed)
val resourceDirectory = get("res")
// Replace
mipmapIconResourceGroups.forEach { groupResources ->
val groupResourceDirectoryName = groupResources.resourceDirectoryName
val fromDirectory = filePath.resolve(groupResourceDirectoryName)
val toDirectory = resourceDirectory.resolve(groupResourceDirectoryName)
groupResources.resources.forEach { iconFileName ->
Files.write(
toDirectory.resolve(iconFileName).toPath(),
fromDirectory.resolve(iconFileName).readBytes(),
)
}
}
// Copy all monochrome icons if provided.
monochromeIconFileNames.forEach { fileName ->
val replacementMonochrome = filePath.resolve("drawable").resolve(fileName)
if (replacementMonochrome.exists()) {
Files.write(
resourceDirectory.resolve("drawable").resolve(fileName).toPath(),
replacementMonochrome.readBytes(),
)
}
}
}
// Change the app name.
escapeAppName(appName!!)?.let { escapedAppName ->
val newValue = "android:label=\"$escapedAppName\""
val manifest = get("AndroidManifest.xml")
val original = manifest.readText()
val replacement = original
// YouTube
.replace("android:label=\"@string/application_name\"", newValue)
// YT Music
.replace("android:label=\"@string/app_launcher_name\"", newValue)
if (original == replacement) {
Logger.getLogger(this::class.java.name).warning(
"Could not replace manifest app name"
)
}
manifest.writeText(replacement)
}
executeBlock() // Must be after the main code to rename the new icons for YouTube 19.34+.
}
}

View File

@@ -0,0 +1,133 @@
package app.revanced.patches.shared.layout.theme
import app.revanced.patcher.patch.BytecodePatchBuilder
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.util.childElementsSequence
import java.util.Locale
internal const val THEME_COLOR_OPTION_DESCRIPTION = "Can be a hex color (#RRGGBB) or a color resource reference."
internal val THEME_DEFAULT_DARK_COLOR_NAMES = setOf(
"yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98",
"yt_black2", "yt_black3", "yt_black4", "yt_status_bar_background_dark",
"material_grey_850"
)
internal val THEME_DEFAULT_LIGHT_COLOR_NAMES = setOf(
"yt_white1", "yt_white1_opacity95", "yt_white1_opacity98",
"yt_white2", "yt_white3", "yt_white4"
)
/**
* @param colorString #AARRGGBB #RRGGBB, or an Android color resource name.
*/
internal fun validateColorName(colorString: String): Boolean {
if (colorString.startsWith("#")) {
// #RRGGBB or #AARRGGBB
val hex = colorString.substring(1).uppercase(Locale.US)
if (hex.length == 8) {
// Transparent colors will crash the app.
if (hex[0] != 'F' || hex[1] != 'F') {
return false
}
} else if (hex.length != 6) {
return false
}
return hex.all { it.isDigit() || it in 'A'..'F' }
}
if (colorString.startsWith("@android:color/")) {
// Cannot easily validate Android built-in colors, so assume it's a correct color.
return true
}
// Allow any color name, because if it's invalid it will
// throw an exception during resource compilation.
return colorString.startsWith("@color/")
}
/**
* Dark theme color option for YouTube and YT Music Theme patches.
*/
internal val darkThemeBackgroundColorOption = stringOption(
key = "darkThemeBackgroundColor",
default = "@android:color/black",
values = mapOf(
"Pure black" to "@android:color/black",
"Material You" to "@android:color/system_neutral1_900",
"Classic (old YouTube)" to "#212121",
"Catppuccin (Mocha)" to "#181825",
"Dark pink" to "#290025",
"Dark blue" to "#001029",
"Dark green" to "#002905",
"Dark yellow" to "#282900",
"Dark orange" to "#291800",
"Dark red" to "#290000",
),
title = "Dark theme background color",
description = THEME_COLOR_OPTION_DESCRIPTION
)
/**
* Shared theme patch for YouTube and YT Music.
*/
internal fun baseThemePatch(
extensionClassDescriptor: String,
block: BytecodePatchBuilder.() -> Unit = {},
executeBlock: BytecodePatchContext.() -> Unit = {}
) = bytecodePatch(
name = "Theme",
description = "Adds options for theming and applies a custom background theme " +
"(dark background theme defaults to pure black).",
) {
darkThemeBackgroundColorOption()
block()
dependsOn(lithoColorHookPatch)
execute {
executeBlock()
lithoColorOverrideHook(extensionClassDescriptor, "getValue")
}
}
internal fun baseThemeResourcePatch(
darkColorNames: Set<String> = THEME_DEFAULT_DARK_COLOR_NAMES,
lightColorNames: Set<String> = THEME_DEFAULT_LIGHT_COLOR_NAMES,
lightColorReplacement: (() -> String)? = null
) = resourcePatch {
execute {
// After patch option validators are fixed https://github.com/ReVanced/revanced-patcher/issues/372
// This should changed to a patch option validator.
val darkColor by darkThemeBackgroundColorOption
if (!validateColorName(darkColor!!)) {
throw PatchException("Invalid dark theme color: $darkColor")
}
val lightColor = lightColorReplacement?.invoke()
if (lightColor != null && !validateColorName(lightColor)) {
throw PatchException("Invalid light theme color: $lightColor")
}
document("res/values/colors.xml").use { document ->
val resourcesNode = document.getElementsByTagName("resources").item(0)
resourcesNode.childElementsSequence().forEach { node ->
val name = node.getAttribute("name")
when {
name in darkColorNames -> node.textContent = darkColor
lightColor != null && name in lightColorNames -> node.textContent = lightColor
}
}
}
}
}

View File

@@ -0,0 +1,24 @@
package app.revanced.patches.shared.layout.theme
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val lithoOnBoundsChangeFingerprint = fingerprint {
accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL)
returns("V")
parameters("Landroid/graphics/Rect;")
opcodes(
Opcode.IGET,
Opcode.IF_EQZ,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_NEZ,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID,
)
custom { method, _ ->
method.name == "onBoundsChange"
}
}

View File

@@ -0,0 +1,27 @@
package app.revanced.patches.shared.layout.theme
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
lateinit var lithoColorOverrideHook: (targetMethodClass: String, targetMethodName: String) -> Unit
private set
val lithoColorHookPatch = bytecodePatch(
description = "Adds a hook to set color of Litho components.",
) {
execute {
var insertionIndex = lithoOnBoundsChangeFingerprint.patternMatch!!.endIndex - 1
lithoColorOverrideHook = { targetMethodClass, targetMethodName ->
lithoOnBoundsChangeFingerprint.method.addInstructions(
insertionIndex,
"""
invoke-static { p1 }, $targetMethodClass->$targetMethodName(I)I
move-result p1
"""
)
insertionIndex += 2
}
}
}

View File

@@ -1,4 +1,4 @@
package app.revanced.patches.youtube.video.audio
package app.revanced.patches.shared.misc.audio
import app.revanced.patcher.fingerprint
import app.revanced.util.containsLiteralInstruction
@@ -7,10 +7,14 @@ import com.android.tools.smali.dexlib2.AccessFlags
internal val formatStreamModelToStringFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("Ljava/lang/String;")
custom { method, classDef ->
method.name == "toString" && classDef.type ==
"Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
custom { method, _ ->
method.name == "toString"
}
strings(
// Strings are partial matches.
"isDefaultAudioTrack=",
"audioTrackId="
)
}
internal const val AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG = 45666189L
@@ -20,7 +24,6 @@ internal val selectAudioStreamFingerprint = fingerprint {
returns("L")
custom { method, _ ->
method.parameters.size > 2 // Method has a large number of parameters and may change.
&& method.parameters[1].type == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;"
&& method.containsLiteralInstruction(AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG)
}
}

View File

@@ -0,0 +1,157 @@
package app.revanced.patches.shared.misc.audio
import app.revanced.patcher.Fingerprint
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.patch.BytecodePatchBuilder
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
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.patches.shared.misc.settings.preference.BasePreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.util.findMethodFromToString
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.insertLiteralOverride
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.immutable.ImmutableField
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/shared/patches/ForceOriginalAudioPatch;"
/**
* Patch shared with YouTube and YT Music.
*/
internal fun forceOriginalAudioPatch(
block: BytecodePatchBuilder.() -> Unit = {},
executeBlock: BytecodePatchContext.() -> Unit = {},
fixUseLocalizedAudioTrackFlag: Boolean,
mainActivityOnCreateFingerprint: Fingerprint,
subclassExtensionClassDescriptor: String,
preferenceScreen: BasePreferenceScreen.Screen
) = bytecodePatch(
name = "Force original audio",
description = "Adds an option to always use the original audio track.",
) {
block()
dependsOn(addResourcesPatch)
execute {
addResources("shared", "misc.audio.forceOriginalAudioPatch")
preferenceScreen.addPreferences(
SwitchPreference(
key = "revanced_force_original_audio",
tag = "app.revanced.extension.shared.settings.preference.ForceOriginalAudioSwitchPreference"
)
)
mainActivityOnCreateFingerprint.method.addInstruction(
0,
"invoke-static { }, $subclassExtensionClassDescriptor->setEnabled()V"
)
// Disable feature flag that ignores the default track flag
// and instead overrides to the user region language.
if (fixUseLocalizedAudioTrackFlag) {
selectAudioStreamFingerprint.method.insertLiteralOverride(
AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->ignoreDefaultAudioStream(Z)Z"
)
}
formatStreamModelToStringFingerprint.let {
val isDefaultAudioTrackMethod = it.originalMethod.findMethodFromToString("isDefaultAudioTrack=")
val audioTrackDisplayNameMethod = it.originalMethod.findMethodFromToString("audioTrackDisplayName=")
val audioTrackIdMethod = it.originalMethod.findMethodFromToString("audioTrackId=")
it.classDef.apply {
// Add a new field to store the override.
val helperFieldName = "patch_isDefaultAudioTrackOverride"
fields.add(
ImmutableField(
type,
helperFieldName,
"Ljava/lang/Boolean;",
// Boolean is a 100% immutable class (all fields are final)
// and safe to write to a shared field without volatile/synchronization,
// but without volatile the field can show stale data
// and the same field is calculated more than once by different threads.
AccessFlags.PRIVATE.value or AccessFlags.VOLATILE.value,
null,
null,
null
).toMutable()
)
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
val helperMethodClass = type
val helperMethodName = "patch_isDefaultAudioTrack"
val helperMethod = ImmutableMethod(
helperMethodClass,
helperMethodName,
listOf(ImmutableMethodParameter("Z", null, null)),
"Z",
AccessFlags.PRIVATE.value,
null,
null,
MutableMethodImplementation(6),
).toMutable().apply {
addInstructionsWithLabels(
0,
"""
iget-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
if-eqz v0, :call_extension
invoke-virtual { v0 }, Ljava/lang/Boolean;->booleanValue()Z
move-result v3
return v3
:call_extension
invoke-virtual { p0 }, $audioTrackIdMethod
move-result-object v1
invoke-virtual { p0 }, $audioTrackDisplayNameMethod
move-result-object v2
invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->isDefaultAudioStream(ZLjava/lang/String;Ljava/lang/String;)Z
move-result v3
invoke-static { v3 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;
move-result-object v0
iput-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
return v3
"""
)
}
methods.add(helperMethod)
// Modify isDefaultAudioTrack() to call extension helper method.
isDefaultAudioTrackMethod.apply {
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index,
"""
invoke-direct { p0, v$register }, $helperMethodClass->$helperMethodName(Z)Z
move-result v$register
"""
)
}
}
}
executeBlock()
}
}

View File

@@ -27,7 +27,7 @@ internal fun sanitizeSharingLinksPatch(
replaceMusicLinksWithYouTube: Boolean = false
) = bytecodePatch(
name = "Sanitize sharing links",
description = "Adds an option to remove the tracking parameter from links you share.",
description = "Adds an option to remove the tracking query parameter from shared links.",
) {
block()

View File

@@ -26,26 +26,26 @@ fun settingsPatch (
preferences: Set<BasePreference>,
) = settingsPatch(listOf(rootPreference), preferences)
private var themeForegroundColor : String? = null
private var themeBackgroundColor : String? = null
private var lightThemeColor : String? = null
private var darkThemeColor : String? = null
/**
* Sets the default theme colors used in various ReVanced specific settings menus.
* By default these colors are white and black, but instead can be set to the
* same color the target app uses for it's own settings.
*/
fun overrideThemeColors(foregroundColor: String, backgroundColor: String) {
themeForegroundColor = foregroundColor
themeBackgroundColor = backgroundColor
fun overrideThemeColors(lightThemeColorString: String?, darkThemeColorString: String) {
lightThemeColor = lightThemeColorString
darkThemeColor = darkThemeColorString
}
private val settingsColorPatch = bytecodePatch {
finalize {
if (themeForegroundColor != null) {
themeLightColorResourceNameFingerprint.method.returnEarly(themeForegroundColor!!)
if (lightThemeColor != null) {
themeLightColorResourceNameFingerprint.method.returnEarly(lightThemeColor!!)
}
if (themeBackgroundColor != null) {
themeDarkColorResourceNameFingerprint.method.returnEarly(themeBackgroundColor!!)
if (darkThemeColor != null) {
themeDarkColorResourceNameFingerprint.method.returnEarly(darkThemeColor!!)
}
}
}

View File

@@ -12,9 +12,8 @@ val dynamicColorPatch = resourcePatch(
) {
compatibleWith(
"com.twitter.android"(
"10.86.0-release.0",
"10.60.0-release.0",
"10.48.0-release.0"
"10.86.0-release.0",
)
)

View File

@@ -1,5 +1,6 @@
package app.revanced.patches.twitter.misc.extension
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
import app.revanced.patches.twitter.misc.extension.hooks.applicationInitHook
val sharedExtensionPatch = sharedExtensionPatch("twitter")
val sharedExtensionPatch = sharedExtensionPatch("twitter", applicationInitHook)

View File

@@ -0,0 +1,10 @@
package app.revanced.patches.twitter.misc.extension.hooks
import app.revanced.patches.shared.misc.extension.extensionHook
internal val applicationInitHook =
extensionHook {
custom { method, classDef ->
classDef.type == "Lcom/twitter/app/TwitterApplication;" && method.name == "onCreate"
}
}

View File

@@ -13,11 +13,8 @@ fun hookPatch(
compatibleWith(
"com.twitter.android"(
// Only v10.85 uses Pairip and requires additional changes to work.
"10.86.0-release.0",
// Confirmed to not show reply ads. Slightly newer versions may also work.
"10.60.0-release.0",
"10.48.0-release.0"
"10.86.0-release.0",
)
)

View File

@@ -39,9 +39,8 @@ val changeLinkSharingDomainPatch = bytecodePatch(
compatibleWith(
"com.twitter.android"(
"10.86.0-release.0",
"10.60.0-release.0",
"10.48.0-release.0"
"10.86.0-release.0",
)
)
@@ -54,28 +53,28 @@ val changeLinkSharingDomainPatch = bytecodePatch(
)
execute {
val replacementIndex =
linkSharingDomainFingerprint.stringMatches!!.first().index
val domainRegister =
linkSharingDomainFingerprint.method.getInstruction<OneRegisterInstruction>(replacementIndex).registerA
linkSharingDomainFingerprint.let {
val replacementIndex = it.stringMatches!!.first().index
val domainRegister = it.method.getInstruction<OneRegisterInstruction>(
replacementIndex
).registerA
linkSharingDomainFingerprint.method.replaceInstruction(
replacementIndex,
"const-string v$domainRegister, \"https://$domainName\"",
)
// Replace the domain name when copying a link with "Copy link" button.
linkBuilderFingerprint.method.apply {
addInstructions(
0,
"""
invoke-static { p0, p1, p2 }, $EXTENSION_CLASS_DESCRIPTOR->formatLink(JLjava/lang/String;)Ljava/lang/String;
move-result-object p0
return-object p0
""",
it.method.replaceInstruction(
replacementIndex,
"const-string v$domainRegister, \"https://$domainName\"",
)
}
// Replace the domain name when copying a link with "Copy link" button.
linkBuilderFingerprint.method.addInstructions(
0,
"""
invoke-static { p0, p1, p2 }, $EXTENSION_CLASS_DESCRIPTOR->formatLink(JLjava/lang/String;)Ljava/lang/String;
move-result-object p0
return-object p0
"""
)
// Used in the Share via... dialog.
linkResourceGetterFingerprint.method.apply {
val templateIdConstIndex = indexOfFirstLiteralInstructionOrThrow(tweetShareLinkTemplateId)

View File

@@ -4,12 +4,12 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.twitter.misc.extension.sharedExtensionPatch
@Deprecated("Patch is obsolete and no longer needed with the highest supported app target. " +
"This patch will soon be deleted.")
@Suppress("unused")
val openLinksWithAppChooserPatch = bytecodePatch(
name = "Open links with app chooser",
description = "Instead of opening links directly, open them with an app chooser. " +
"As a result you can select a browser to open the link with.",
use = false,
"As a result you can select a browser to open the link with.",
) {
dependsOn(sharedExtensionPatch)
@@ -18,7 +18,7 @@ val openLinksWithAppChooserPatch = bytecodePatch(
execute {
val methodReference =
"Lapp/revanced/extension/twitter/patches/links/OpenLinksWithAppChooserPatch;->" +
"openWithChooser(Landroid/content/Context;Landroid/content/Intent;)V"
"openWithChooser(Landroid/content/Context;Landroid/content/Intent;)V"
openLinkFingerprint.method.addInstructions(
0,

View File

@@ -10,9 +10,8 @@ val sanitizeSharingLinksPatch = bytecodePatch(
) {
compatibleWith(
"com.twitter.android"(
"10.86.0-release.0",
"10.60.0-release.0",
"10.48.0-release.0"
"10.86.0-release.0",
)
)

View File

@@ -0,0 +1,16 @@
package app.revanced.patches.viber.misc.navbar
import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.BytecodePatchContext
internal val tabIdClassFingerprint = fingerprint {
strings("shouldShowTabId")
}
context(BytecodePatchContext)
internal val shouldShowTabIdMethodFingerprint get() = fingerprint {
parameters("I", "I")
returns("Z")
custom { methodDef, classDef ->
classDef == tabIdClassFingerprint.classDef
}
}

View File

@@ -0,0 +1,85 @@
package app.revanced.patches.viber.misc.navbar
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.bytecodePatch
import java.util.logging.Logger
import kotlin.collections.joinToString
private const val instructionsFooter = """
# If we reach this, it means that this tab has been disabled by user
const/4 v0, 0
return v0 # return false as "This tab is not enabled"
# Proceed with default execution
:continue
nop
"""
@Suppress("unused")
val hideNavigationButtonsPatch = bytecodePatch(
name = "Hide navigation buttons",
description = "Permanently hides navigation bar buttons, such as Explore and Marketplace.",
use = false
) {
compatibleWith("com.viber.voip")
val hideOptions = AllowedNavigationItems.entries.associateWith {
booleanOption(
key = it.key,
default = it.defaultHideOption,
title = it.title,
description = it.description,
)
}
execute {
// Items that won't be forcefully hidden.
val allowedItems = hideOptions.filter { (option, enabled) -> enabled.value != true }
if (allowedItems.size == AllowedNavigationItems.entries.size) {
return@execute Logger.getLogger(this::class.java.name).warning(
"No hide navigation buttons options are enabled. No changes made."
)
}
val injectionInstructions = allowedItems
.map { it.key.buildAllowInstruction() }
.joinToString("\n") + instructionsFooter
shouldShowTabIdMethodFingerprint
.method
.addInstructionsWithLabels(0, injectionInstructions)
}
}
/**
* Navigation items taken from source code.
* They appear in code like new NavigationItem(0, R.string.bottom_tab_chats, R.drawable.ic_tab_chats).
*/
private enum class AllowedNavigationItems(
val defaultHideOption: Boolean,
private val itemName: String,
private vararg val ids: Int
) {
CHATS(false, "Chats", 0),
CALLS(false, "Calls", 1, 7),
EXPLORE(true, "Explore", 2),
MORE(false, "More", 3),
PAY(true, "Pay", 5),
CAMERA(true, "Camera", 6),
MARKETPLACE(true, "Marketplace", 8);
val key = "hide$itemName"
val title = "Hide $itemName"
val description = "Permanently hides the $itemName button."
fun buildAllowInstruction(): String =
ids.joinToString("\n") { id ->
"""
const/4 v0, $id # If tabId == $id ($itemName), don't hide it
if-eq p1, v0, :continue
"""
}
}

View File

@@ -1,141 +1,60 @@
package app.revanced.patches.youtube.layout.branding
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.copyResources
import java.io.File
import app.revanced.patches.shared.layout.branding.baseCustomBrandingPatch
import app.revanced.patches.shared.layout.branding.mipmapDirectories
import java.nio.file.Files
private const val REVANCED_ICON = "ReVanced*Logo" // Can never be a valid path.
private const val APP_NAME = "YouTube ReVanced"
private val iconResourceFileNames = arrayOf(
"adaptiveproduct_youtube_background_color_108",
"adaptiveproduct_youtube_foreground_color_108",
"ic_launcher",
"ic_launcher_round",
).map { "$it.png" }.toTypedArray()
private val iconResourceFileNamesNew = mapOf(
private val youtubeIconResourceFileNames_19_34 = mapOf(
"adaptiveproduct_youtube_foreground_color_108" to "adaptiveproduct_youtube_2024_q4_foreground_color_108",
"adaptiveproduct_youtube_background_color_108" to "adaptiveproduct_youtube_2024_q4_background_color_108",
)
private val mipmapDirectories = arrayOf(
"xxxhdpi",
"xxhdpi",
"xhdpi",
"hdpi",
"mdpi",
).map { "mipmap-$it" }
@Suppress("unused")
val customBrandingPatch = resourcePatch(
name = "Custom branding",
description = "Applies a custom app name and icon. Defaults to \"YouTube ReVanced\" and the ReVanced logo.",
use = false,
) {
dependsOn(versionCheckPatch)
val customBrandingPatch = baseCustomBrandingPatch(
defaultAppName = APP_NAME,
appNameValues = mapOf(
"YouTube ReVanced" to APP_NAME,
"YT ReVanced" to "YT ReVanced",
"YT" to "YT",
"YouTube" to "YouTube",
),
resourceFolder = "custom-branding/youtube",
iconResourceFileNames = arrayOf(
"adaptiveproduct_youtube_background_color_108",
"adaptiveproduct_youtube_foreground_color_108",
"ic_launcher",
"ic_launcher_round",
),
monochromeIconFileNames = arrayOf(
"adaptive_monochrome_ic_youtube_launcher.xml",
"ringo2_adaptive_monochrome_ic_youtube_launcher.xml"
),
compatibleWith(
"com.google.android.youtube"(
"19.34.42",
"20.07.39",
"20.13.41",
"20.14.43",
)
)
val appName by stringOption(
key = "appName",
default = APP_NAME,
values = mapOf(
"YouTube ReVanced" to APP_NAME,
"YT ReVanced" to "YT ReVanced",
"YT" to "YT",
"YouTube" to "YouTube",
),
title = "App name",
description = "The name of the app.",
)
val icon by stringOption(
key = "iconPath",
default = REVANCED_ICON,
values = mapOf("ReVanced Logo" to REVANCED_ICON),
title = "App icon",
description = """
The icon to apply to the app.
If a path to a folder is provided, the folder must contain the following folders:
${mipmapDirectories.joinToString("\n") { "- $it" }}
Each of these folders must contain the following files:
${iconResourceFileNames.joinToString("\n") { "- $it" }}
""".trimIndentMultiline(),
)
execute {
icon?.let { icon ->
// Change the app icon.
mipmapDirectories.map { directory ->
ResourceGroup(
directory,
*iconResourceFileNames,
)
}.let { resourceGroups ->
if (icon != REVANCED_ICON) {
val path = File(icon)
val resourceDirectory = get("res")
resourceGroups.forEach { group ->
val fromDirectory = path.resolve(group.resourceDirectoryName)
val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
group.resources.forEach { iconFileName ->
Files.write(
toDirectory.resolve(iconFileName).toPath(),
fromDirectory.resolve(iconFileName).readBytes(),
)
}
}
} else {
resourceGroups.forEach { copyResources("custom-branding", it) }
}
}
if (is_19_34_or_greater) {
val resourceDirectory = get("res")
mipmapDirectories.forEach { directory ->
val targetDirectory = resourceDirectory.resolve(directory)
iconResourceFileNamesNew.forEach { (old, new) ->
val oldFile = targetDirectory.resolve("$old.png")
val newFile = targetDirectory.resolve("$new.png")
Files.write(newFile.toPath(), oldFile.readBytes())
}
}
}
}
appName?.let { name ->
// Change the app name.
val manifest = get("AndroidManifest.xml")
manifest.writeText(
manifest.readText()
.replace(
"android:label=\"@string/application_name",
"android:label=\"$name",
),
block = {
compatibleWith(
"com.google.android.youtube"(
"19.34.42",
"20.07.39",
"20.13.41",
"20.14.43",
)
)
},
executeBlock = {
val resourceDirectory = get("res")
mipmapDirectories.forEach { directory ->
val targetDirectory = resourceDirectory.resolve(directory)
youtubeIconResourceFileNames_19_34.forEach { (old, new) ->
val oldFile = targetDirectory.resolve("$old.png")
val newFile = targetDirectory.resolve("$new.png")
Files.write(newFile.toPath(), oldFile.readBytes())
}
}
}
}
)

View File

@@ -1,8 +1,13 @@
package app.revanced.patches.youtube.layout.hide.endscreencards
import app.revanced.patcher.fingerprint
import app.revanced.util.containsLiteralInstruction
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.FieldReference
internal val layoutCircleFingerprint = fingerprint {
returns("Landroid/view/View;")
@@ -38,3 +43,19 @@ internal val layoutVideoFingerprint = fingerprint {
)
literal { layoutVideo }
}
internal val showEndscreenCardsFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters("L")
custom { method, classDef ->
classDef.methods.count() == 5
&& method.containsLiteralInstruction(0)
&& method.containsLiteralInstruction(5)
&& method.containsLiteralInstruction(8)
&& method.indexOfFirstInstruction {
val reference = getReference<FieldReference>()
reference?.type == "Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"
} >= 0
}
}

View File

@@ -1,6 +1,7 @@
package app.revanced.patches.youtube.layout.hide.endscreencards
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
@@ -11,6 +12,8 @@ import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_43_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@@ -22,7 +25,7 @@ internal var layoutIcon = -1L
internal var layoutVideo = -1L
private set
private val hideEndscreenCardsResourcePatch = resourcePatch {
private val hideEndScreenCardsResourcePatch = resourcePatch {
dependsOn(
settingsPatch,
resourceMappingPatch,
@@ -30,7 +33,7 @@ private val hideEndscreenCardsResourcePatch = resourcePatch {
)
execute {
addResources("youtube", "layout.hide.endscreencards.hideEndscreenCardsResourcePatch")
addResources("youtube", "layout.hide.endscreencards.hideEndScreenCardsResourcePatch")
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_hide_endscreen_cards"),
@@ -45,16 +48,17 @@ private val hideEndscreenCardsResourcePatch = resourcePatch {
}
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/HideEndscreenCardsPatch;"
"Lapp/revanced/extension/youtube/patches/HideEndScreenCardsPatch;"
@Suppress("unused")
val hideEndscreenCardsPatch = bytecodePatch(
val hideEndScreenCardsPatch = bytecodePatch(
name = "Hide end screen cards",
description = "Adds an option to hide suggested video cards at the end of videos.",
) {
dependsOn(
sharedExtensionPatch,
hideEndscreenCardsResourcePatch,
hideEndScreenCardsResourcePatch,
versionCheckPatch
)
compatibleWith(
@@ -78,9 +82,24 @@ val hideEndscreenCardsPatch = bytecodePatch(
addInstruction(
insertIndex,
"invoke-static { v$viewRegister }, $EXTENSION_CLASS_DESCRIPTOR->hideEndscreen(Landroid/view/View;)V",
"invoke-static { v$viewRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->hideEndScreenCardView(Landroid/view/View;)V",
)
}
}
if (is_19_43_or_greater) {
showEndscreenCardsFingerprint.method.addInstructionsWithLabels(
0,
"""
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hideEndScreenCards()Z
move-result v0
if-eqz v0, :show
return-void
:show
nop
"""
)
}
}
}

View File

@@ -147,3 +147,17 @@ internal val showFloatingMicrophoneButtonFingerprint = fingerprint {
)
literal { fabButtonId }
}
internal val hideViewCountFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("Ljava/lang/CharSequence;")
opcodes(
Opcode.RETURN_OBJECT,
Opcode.CONST_STRING,
Opcode.RETURN_OBJECT,
)
strings(
"Has attachmentRuns but drawableRequester is missing.",
)
}

View File

@@ -36,6 +36,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
var expandButtonDownId = -1L
private set
@@ -161,9 +162,9 @@ val hideLayoutComponentsPatch = bytecodePatch(
SwitchPreference("revanced_hide_comments_section"),
SwitchPreference("revanced_hide_comments_community_guidelines"),
SwitchPreference("revanced_hide_comments_create_a_short_button"),
SwitchPreference("revanced_hide_comments_emoji_and_timestamp_buttons"),
SwitchPreference("revanced_hide_comments_preview_comment"),
SwitchPreference("revanced_hide_comments_thanks_button"),
SwitchPreference("revanced_hide_comments_timestamp_button"),
),
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
),
@@ -239,6 +240,8 @@ val hideLayoutComponentsPatch = bytecodePatch(
SwitchPreference("revanced_hide_surveys"),
SwitchPreference("revanced_hide_ticket_shelf"),
SwitchPreference("revanced_hide_video_recommendation_labels"),
SwitchPreference("revanced_hide_view_count"),
SwitchPreference("revanced_hide_upload_time"),
SwitchPreference("revanced_hide_doodles"),
)
@@ -397,6 +400,39 @@ val hideLayoutComponentsPatch = bytecodePatch(
// endregion
// region hide view count
hideViewCountFingerprint.method.apply {
val startIndex = hideViewCountFingerprint.patternMatch!!.startIndex
var returnStringRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA
// Find the instruction where the text dimension is retrieved.
val applyDimensionIndex = indexOfFirstInstructionReversedOrThrow {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_STATIC &&
reference?.definingClass == "Landroid/util/TypedValue;" &&
reference.returnType == "F" &&
reference.name == "applyDimension" &&
reference.parameterTypes == listOf("I", "F", "Landroid/util/DisplayMetrics;")
}
// A float value is passed which is used to determine subtitle text size.
val floatDimensionRegister = getInstruction<OneRegisterInstruction>(
applyDimensionIndex + 1
).registerA
addInstructions(
applyDimensionIndex - 1,
"""
invoke-static { v$returnStringRegister, v$floatDimensionRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->modifyFeedSubtitleSpan(Landroid/text/SpannableString;F)Landroid/text/SpannableString;
move-result-object v$returnStringRegister
"""
)
}
// endregion
// region hide filter bar
/**

View File

@@ -186,15 +186,15 @@ val miniplayerPatch = bytecodePatch(
if (!is_19_29_or_greater) {
preferences += SwitchPreference("revanced_miniplayer_double_tap_action")
}
preferences += SwitchPreference("revanced_miniplayer_drag_and_drop")
preferences += SwitchPreference("revanced_miniplayer_disable_drag_and_drop")
}
if (is_19_43_or_greater) {
preferences += SwitchPreference("revanced_miniplayer_horizontal_drag")
preferences += SwitchPreference("revanced_miniplayer_disable_horizontal_drag")
}
if (is_19_36_or_greater) {
preferences += SwitchPreference("revanced_miniplayer_rounded_corners")
preferences += SwitchPreference("revanced_miniplayer_disable_rounded_corners")
}
preferences += SwitchPreference("revanced_miniplayer_hide_subtext")

View File

@@ -10,13 +10,12 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.shared.layout.theme.lithoColorHookPatch
import app.revanced.patches.shared.layout.theme.lithoColorOverrideHook
import app.revanced.patches.shared.misc.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.youtube.layout.theme.lithoColorHookPatch
import app.revanced.patches.youtube.layout.theme.lithoColorOverrideHook
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_46_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_49_or_greater
@@ -108,11 +107,6 @@ private val seekbarColorResourcePatch = resourcePatch {
scaleNode.replaceChild(replacementNode, shapeNode)
}
if (!is_19_25_or_greater) {
return@execute
}
ytYoutubeMagentaColorId = resourceMappings[
"color",
"yt_youtube_magenta",
@@ -260,10 +254,6 @@ val seekbarColorPatch = bytecodePatch(
lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getLithoColor")
if (!is_19_25_or_greater) {
return@execute
}
// 19.25+ changes
arrayOf(

View File

@@ -161,7 +161,7 @@ val openShortsInRegularPlayerPatch = bytecodePatch(
addInstructions(
index + 1,
"""
invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->overrideBackPressToExit(Z)Z
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->overrideBackPressToExit(Z)Z
move-result v$register
"""
)

View File

@@ -3,27 +3,6 @@ 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
internal val lithoThemeFingerprint = fingerprint {
accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL)
returns("V")
parameters("Landroid/graphics/Rect;")
opcodes(
Opcode.IGET,
Opcode.IF_EQZ,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_NEZ,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID,
)
custom { method, _ ->
method.name == "onBoundsChange"
}
}
internal const val GRADIENT_LOADING_SCREEN_AB_CONSTANT = 45412406L

View File

@@ -1,28 +1,19 @@
package app.revanced.patches.youtube.layout.theme
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
@Deprecated("Function was moved", ReplaceWith("app.revanced.patches.shared.layout.theme.lithoColorOverrideHook"))
@Suppress("unused")
lateinit var lithoColorOverrideHook: (targetMethodClass: String, targetMethodName: String) -> Unit
private set
val lithoColorHookPatch = bytecodePatch(
description = "Adds a hook to set color of Litho components.",
) {
@Deprecated("Patch was moved", ReplaceWith("app.revanced.patches.shared.layout.theme.lithoColorHookPatch"))
@Suppress("unused")
val lithoColorHookPatch = bytecodePatch{
dependsOn(app.revanced.patches.shared.layout.theme.lithoColorHookPatch)
execute {
var insertionIndex = lithoThemeFingerprint.patternMatch!!.endIndex - 1
lithoColorOverrideHook = { targetMethodClass, targetMethodName ->
lithoThemeFingerprint.method.addInstructions(
insertionIndex,
"""
invoke-static { p1 }, $targetMethodClass->$targetMethodName(I)I
move-result p1
""",
)
insertionIndex += 2
}
lithoColorOverrideHook = app.revanced.patches.shared.layout.theme.lithoColorOverrideHook
}
}

View File

@@ -1,14 +1,16 @@
package app.revanced.patches.youtube.layout.theme
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.layout.theme.THEME_COLOR_OPTION_DESCRIPTION
import app.revanced.patches.shared.layout.theme.baseThemePatch
import app.revanced.patches.shared.layout.theme.baseThemeResourcePatch
import app.revanced.patches.shared.layout.theme.darkThemeBackgroundColorOption
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.overrideThemeColors
import app.revanced.patches.shared.misc.settings.preference.BasePreference
import app.revanced.patches.shared.misc.settings.preference.InputType
import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
@@ -17,126 +19,54 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.layout.seekbar.seekbarColorPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_47_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.childElementsSequence
import app.revanced.util.forEachChildElement
import app.revanced.util.insertLiteralOverride
import org.w3c.dom.Element
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/theme/ThemePatch;"
val themePatch = bytecodePatch(
name = "Theme",
description = "Adds options for theming and applies a custom background theme " +
"(dark background theme defaults to amoled black).",
) {
val amoledBlackColor = "@android:color/black"
val whiteColor = "@android:color/white"
val themePatch = baseThemePatch(
extensionClassDescriptor = EXTENSION_CLASS_DESCRIPTOR,
val darkThemeBackgroundColor by stringOption(
key = "darkThemeBackgroundColor",
default = amoledBlackColor,
values = mapOf(
"Amoled black" to amoledBlackColor,
"Material You" to "@android:color/system_neutral1_900",
"Classic (old YouTube)" to "#FF212121",
"Catppuccin (Mocha)" to "#FF181825",
"Dark pink" to "#FF290025",
"Dark blue" to "#FF001029",
"Dark green" to "#FF002905",
"Dark yellow" to "#FF282900",
"Dark orange" to "#FF291800",
"Dark red" to "#FF290000",
),
title = "Dark theme background color",
description = "Can be a hex color (#AARRGGBB) or a color resource reference.",
)
block = {
val lightThemeBackgroundColor by stringOption(
key = "lightThemeBackgroundColor",
default = "@android:color/white",
values = mapOf(
"White" to "@android:color/white",
"Material You" to "@android:color/system_neutral1_50",
"Catppuccin (Latte)" to "#E6E9EF",
"Light pink" to "#FCCFF3",
"Light blue" to "#D1E0FF",
"Light green" to "#CCFFCC",
"Light yellow" to "#FDFFCC",
"Light orange" to "#FFE6CC",
"Light red" to "#FFD6D6",
),
title = "Light theme background color",
description = THEME_COLOR_OPTION_DESCRIPTION
)
val lightThemeBackgroundColor by stringOption(
key = "lightThemeBackgroundColor",
default = whiteColor,
values = mapOf(
"White" to whiteColor,
"Material You" to "@android:color/system_neutral1_50",
"Catppuccin (Latte)" to "#FFE6E9EF",
"Light pink" to "#FFFCCFF3",
"Light blue" to "#FFD1E0FF",
"Light green" to "#FFCCFFCC",
"Light yellow" to "#FFFDFFCC",
"Light orange" to "#FFFFE6CC",
"Light red" to "#FFFFD6D6",
),
title = "Light theme background color",
description = "Can be a hex color (#AARRGGBB) or a color resource reference.",
)
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
lithoColorHookPatch,
seekbarColorPatch,
versionCheckPatch,
resourcePatch {
dependsOn(
settingsPatch,
resourceMappingPatch,
)
val themeResourcePatch = resourcePatch {
dependsOn(resourceMappingPatch)
execute {
val preferences = mutableSetOf<BasePreference>(
SwitchPreference("revanced_seekbar_custom_color"),
TextPreference("revanced_seekbar_custom_color_primary",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
inputType = InputType.TEXT_CAP_CHARACTERS),
overrideThemeColors(
lightThemeBackgroundColor!!,
darkThemeBackgroundColorOption.value!!
)
if (is_19_25_or_greater) {
preferences += TextPreference("revanced_seekbar_custom_color_accent",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
inputType = InputType.TEXT_CAP_CHARACTERS)
}
PreferenceScreen.SEEKBAR.addPreferences(
PreferenceCategory(
titleKey = null,
sorting = Sorting.UNSORTED,
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
preferences = preferences
)
)
overrideThemeColors(lightThemeBackgroundColor!!, darkThemeBackgroundColor!!)
// Edit theme colors via resources.
document("res/values/colors.xml").use { document ->
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
resourcesNode.childElementsSequence().forEach { node ->
when (node.getAttribute("name")) {
"yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98",
"yt_black2", "yt_black3", "yt_black4", "yt_status_bar_background_dark",
"material_grey_850",
-> node.textContent = darkThemeBackgroundColor
"yt_white1", "yt_white1_opacity95", "yt_white1_opacity98",
"yt_white2", "yt_white3", "yt_white4",
-> node.textContent = lightThemeBackgroundColor
}
}
}
fun addColorResource(
resourceFile: String,
colorName: String,
colorValue: String,
) {
document(resourceFile).use { document ->
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
val resourcesNode =
document.getElementsByTagName("resources").item(0) as Element
resourcesNode.appendChild(
document.createElement("color").apply {
@@ -150,18 +80,31 @@ val themePatch = bytecodePatch(
// Add a dynamic background color to the colors.xml file.
val splashBackgroundColorKey = "revanced_splash_background_color"
addColorResource("res/values/colors.xml", splashBackgroundColorKey, lightThemeBackgroundColor!!)
addColorResource("res/values-night/colors.xml", splashBackgroundColorKey, darkThemeBackgroundColor!!)
addColorResource(
"res/values/colors.xml",
splashBackgroundColorKey,
lightThemeBackgroundColor!!
)
addColorResource(
"res/values-night/colors.xml",
splashBackgroundColorKey,
darkThemeBackgroundColorOption.value!!
)
// Edit splash screen files and change the background color,
// Edit splash screen files and change the background color.
arrayOf(
"res/drawable/quantum_launchscreen_youtube.xml",
"res/drawable-sw600dp/quantum_launchscreen_youtube.xml",
).forEach editSplashScreen@{ resourceFileName ->
document(resourceFileName).use { document ->
document.getElementsByTagName("layer-list").item(0).forEachChildElement { node ->
document.getElementsByTagName(
"layer-list"
).item(0).forEachChildElement { node ->
if (node.hasAttribute("android:drawable")) {
node.setAttribute("android:drawable", "@color/$splashBackgroundColorKey")
node.setAttribute(
"android:drawable",
"@color/$splashBackgroundColorKey"
)
return@editSplashScreen
}
}
@@ -172,7 +115,6 @@ val themePatch = bytecodePatch(
// Fix the splash screen dark mode background color.
// In 19.32+ the dark mode splash screen is white and fades to black.
// Maybe it's a bug in YT, or maybe it intentionally. Who knows.
document("res/values-night-v27/styles.xml").use { document ->
// Create a night mode specific override for the splash screen background.
val style = document.createElement("style")
@@ -195,29 +137,63 @@ val themePatch = bytecodePatch(
style.appendChild(styleItem)
}
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
val resourcesNode =
document.getElementsByTagName("resources").item(0) as Element
resourcesNode.appendChild(style)
}
}
}
)
compatibleWith(
"com.google.android.youtube"(
"19.34.42",
"20.07.39",
"20.13.41",
"20.14.43",
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
seekbarColorPatch,
baseThemeResourcePatch(
lightColorReplacement = { lightThemeBackgroundColor!! }
),
themeResourcePatch
)
)
execute {
compatibleWith(
"com.google.android.youtube"(
"19.34.42",
"20.07.39",
"20.13.41",
"20.14.43",
)
)
},
executeBlock = {
addResources("youtube", "layout.theme.themePatch")
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
SwitchPreference("revanced_gradient_loading_screen")
)
val preferences = mutableSetOf(
SwitchPreference("revanced_seekbar_custom_color"),
TextPreference(
"revanced_seekbar_custom_color_primary",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
inputType = InputType.TEXT_CAP_CHARACTERS
),
TextPreference(
"revanced_seekbar_custom_color_accent",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
inputType = InputType.TEXT_CAP_CHARACTERS
)
)
PreferenceScreen.SEEKBAR.addPreferences(
PreferenceCategory(
titleKey = null,
sorting = Sorting.UNSORTED,
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
preferences = preferences
)
)
if (is_19_47_or_greater) {
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
ListPreference("revanced_splash_screen_animation_style")
@@ -236,7 +212,5 @@ val themePatch = bytecodePatch(
"$EXTENSION_CLASS_DESCRIPTOR->getLoadingScreenType(I)I"
)
}
lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getValue")
}
}
)

View File

@@ -164,7 +164,7 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
addInstruction(
index + 1,
"invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->setToolbar(Landroid/widget/FrameLayout;)V"
"invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->setToolbar(Landroid/widget/FrameLayout;)V"
)
}

View File

@@ -38,6 +38,7 @@ var is_19_32_or_greater = false
@Deprecated("19.34.42 is the lowest supported version")
var is_19_33_or_greater = false
private set
@Deprecated("19.34.42 is the lowest supported version")
var is_19_34_or_greater = false
private set
var is_19_35_or_greater = false

View File

@@ -218,7 +218,8 @@ val settingsPatch = bytecodePatch(
modifyActivityForSettingsInjection(
licenseActivityOnCreateFingerprint.classDef,
licenseActivityOnCreateFingerprint.method,
YOUTUBE_ACTIVITY_HOOK_CLASS_DESCRIPTOR
YOUTUBE_ACTIVITY_HOOK_CLASS_DESCRIPTOR,
false
)
}
@@ -233,7 +234,8 @@ val settingsPatch = bytecodePatch(
internal fun modifyActivityForSettingsInjection(
activityOnCreateClass: MutableClass,
activityOnCreateMethod: MutableMethod,
extensionClassType: String
extensionClassType: String,
isYouTubeMusic: Boolean
) {
// Modify Activity and remove all existing layout code.
// Must modify an existing activity and cannot add a new activity to the manifest,
@@ -275,7 +277,7 @@ internal fun modifyActivityForSettingsInjection(
// Override finish() to intercept back gesture.
ImmutableMethod(
activityOnCreateClass.type,
"finish",
if (isYouTubeMusic) "finish" else "onBackPressed",
emptyList(),
"V",
AccessFlags.PUBLIC.value,
@@ -283,13 +285,16 @@ internal fun modifyActivityForSettingsInjection(
null,
MutableMethodImplementation(3),
).toMutable().apply {
// Slightly different hooks are needed, otherwise the back button can behave wrong.
val extensionMethodName = if (isYouTubeMusic) "handleFinish" else "handleBackPress"
val invokeFinishOpcode = if (isYouTubeMusic) "invoke-super" else "invoke-virtual"
addInstructions(
"""
invoke-static {}, $extensionClassType->handleFinish()Z
invoke-static {}, $extensionClassType->$extensionMethodName()Z
move-result v0
if-nez v0, :search_handled
invoke-super { p0 }, Landroid/app/Activity;->finish()V
return-void
$invokeFinishOpcode { p0 }, Landroid/app/Activity;->finish()V
:search_handled
return-void
"""

View File

@@ -1,159 +1,36 @@
package app.revanced.patches.youtube.video.audio
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.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
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.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.audio.forceOriginalAudioPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
import app.revanced.util.findMethodFromToString
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.insertLiteralOverride
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.immutable.ImmutableField
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/youtube/patches/ForceOriginalAudioPatch;"
@Suppress("unused")
val forceOriginalAudioPatch = bytecodePatch(
name = "Force original audio",
description = "Adds an option to always use the original audio track.",
) {
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
versionCheckPatch
)
compatibleWith(
"com.google.android.youtube"(
"19.34.42",
"20.07.39",
"20.13.41",
"20.14.43",
val forceOriginalAudioPatch = forceOriginalAudioPatch(
block = {
dependsOn(
sharedExtensionPatch,
settingsPatch,
versionCheckPatch
)
)
execute {
addResources("youtube", "video.audio.forceOriginalAudioPatch")
PreferenceScreen.VIDEO.addPreferences(
SwitchPreference(
key = "revanced_force_original_audio",
tag = "app.revanced.extension.youtube.settings.preference.ForceOriginalAudioSwitchPreference"
compatibleWith(
"com.google.android.youtube"(
"19.34.42",
"20.07.39",
"20.13.41",
"20.14.43",
)
)
mainActivityOnCreateFingerprint.method.addInstruction(
0,
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setPreferredLanguage()V"
)
// Disable feature flag that ignores the default track flag
// and instead overrides to the user region language.
if (is_20_07_or_greater) {
selectAudioStreamFingerprint.method.insertLiteralOverride(
AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->ignoreDefaultAudioStream(Z)Z"
)
}
formatStreamModelToStringFingerprint.let {
val isDefaultAudioTrackMethod = it.originalMethod.findMethodFromToString("isDefaultAudioTrack=")
val audioTrackDisplayNameMethod = it.originalMethod.findMethodFromToString("audioTrackDisplayName=")
val audioTrackIdMethod = it.originalMethod.findMethodFromToString("audioTrackId=")
it.classDef.apply {
// Add a new field to store the override.
val helperFieldName = "patch_isDefaultAudioTrackOverride"
fields.add(
ImmutableField(
type,
helperFieldName,
"Ljava/lang/Boolean;",
// Boolean is a 100% immutable class (all fields are final)
// and safe to write to a shared field without volatile/synchronization,
// but without volatile the field can show stale data
// and the same field is calculated more than once by different threads.
AccessFlags.PRIVATE.value or AccessFlags.VOLATILE.value,
null,
null,
null
).toMutable()
)
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
val helperMethodClass = type
val helperMethodName = "patch_isDefaultAudioTrack"
val helperMethod = ImmutableMethod(
helperMethodClass,
helperMethodName,
listOf(ImmutableMethodParameter("Z", null, null)),
"Z",
AccessFlags.PRIVATE.value,
null,
null,
MutableMethodImplementation(6),
).toMutable().apply {
addInstructionsWithLabels(
0,
"""
iget-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
if-eqz v0, :call_extension
invoke-virtual { v0 }, Ljava/lang/Boolean;->booleanValue()Z
move-result v3
return v3
:call_extension
invoke-virtual { p0 }, $audioTrackIdMethod
move-result-object v1
invoke-virtual { p0 }, $audioTrackDisplayNameMethod
move-result-object v2
invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->isDefaultAudioStream(ZLjava/lang/String;Ljava/lang/String;)Z
move-result v3
invoke-static { v3 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;
move-result-object v0
iput-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
return v3
"""
)
}
methods.add(helperMethod)
// Modify isDefaultAudioTrack() to call extension helper method.
isDefaultAudioTrackMethod.apply {
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index,
"""
invoke-direct { p0, v$register }, $helperMethodClass->$helperMethodName(Z)Z
move-result v$register
"""
)
}
}
}
}
}
},
fixUseLocalizedAudioTrackFlag = is_20_07_or_greater,
mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint,
subclassExtensionClassDescriptor = EXTENSION_CLASS_DESCRIPTOR,
preferenceScreen = PreferenceScreen.VIDEO,
)

View File

@@ -14,8 +14,7 @@ internal val settingsMenuVideoQualityGroup = mutableSetOf<BasePreference>()
@Suppress("unused")
val videoQualityPatch = bytecodePatch(
name = "Video quality",
description = "Adds options to use the advanced video quality menu, set default video qualities, " +
"and disable video codecs such as VP9/HDR."
description = "Adds options to set default video qualities and always use the advanced video quality menu."
) {
dependsOn(
rememberVideoQualityPatch,

View File

@@ -34,6 +34,9 @@ Second \"item\" text"</string>
</patch>
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
</patch>
<patch id="misc.audio.forceOriginalAudioPatch">
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
</patch>
<patch id="misc.debugging.enableDebuggingPatch">
</patch>
<patch id="misc.privacy.sanitizeSharingLinksPatch">
@@ -59,6 +62,8 @@ Second \"item\" text"</string>
<!-- 'Visit Community' should be translated with the same localized wording that YouTube displays. -->
<!-- 'Visit store' should be translated with the same localized wording that YouTube displays. -->
<!-- 'Component path builder strings' is the technical name for identifying the Litho UI layout items to hide. This is an advanced feature and most users will never use this. -->
<!-- Translations should lanaguge similar to revanced_hide_upload_time_user_dialog_message -->
<!-- Translations should lanaguge similar to revanced_hide_view_count_user_dialog_message -->
<!-- For localization, it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc.) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
@@ -133,7 +138,7 @@ Second \"item\" text"</string>
<patch id="layout.buttons.overlay.hidePlayerOverlayButtonsPatch">
<!-- This button does not display any text, but 'Captions' should be translated using the same wording used as the translation of 'revanced_hide_player_flyout_captions_title'. -->
</patch>
<patch id="layout.hide.endscreencards.hideEndscreenCardsResourcePatch">
<patch id="layout.hide.endscreencards.hideEndScreenCardsResourcePatch">
</patch>
<patch id="layout.hide.fullscreenambientmode.disableFullscreenAmbientModePatch">
</patch>
@@ -225,9 +230,6 @@ Second \"item\" text"</string>
</patch>
<patch id="misc.links.openLinksExternallyPatch">
</patch>
<patch id="video.audio.forceOriginalAudioPatch">
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
</patch>
<patch id="video.quality.rememberVideoQualityPatch">
<!-- Translations should use the same text as 'revanced_custom_playback_speeds_auto'. -->
</patch>

View File

@@ -34,6 +34,9 @@ Second \"item\" text"</string>
</patch>
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
</patch>
<patch id="misc.audio.forceOriginalAudioPatch">
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
</patch>
<patch id="misc.debugging.enableDebuggingPatch">
</patch>
<patch id="misc.privacy.sanitizeSharingLinksPatch">
@@ -59,6 +62,8 @@ Second \"item\" text"</string>
<!-- 'Visit Community' should be translated with the same localized wording that YouTube displays. -->
<!-- 'Visit store' should be translated with the same localized wording that YouTube displays. -->
<!-- 'Component path builder strings' is the technical name for identifying the Litho UI layout items to hide. This is an advanced feature and most users will never use this. -->
<!-- Translations should lanaguge similar to revanced_hide_upload_time_user_dialog_message -->
<!-- Translations should lanaguge similar to revanced_hide_view_count_user_dialog_message -->
<!-- For localization, it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc.) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
@@ -133,7 +138,7 @@ Second \"item\" text"</string>
<patch id="layout.buttons.overlay.hidePlayerOverlayButtonsPatch">
<!-- This button does not display any text, but 'Captions' should be translated using the same wording used as the translation of 'revanced_hide_player_flyout_captions_title'. -->
</patch>
<patch id="layout.hide.endscreencards.hideEndscreenCardsResourcePatch">
<patch id="layout.hide.endscreencards.hideEndScreenCardsResourcePatch">
</patch>
<patch id="layout.hide.fullscreenambientmode.disableFullscreenAmbientModePatch">
</patch>
@@ -225,9 +230,6 @@ Second \"item\" text"</string>
</patch>
<patch id="misc.links.openLinksExternallyPatch">
</patch>
<patch id="video.audio.forceOriginalAudioPatch">
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
</patch>
<patch id="video.quality.rememberVideoQualityPatch">
<!-- Translations should use the same text as 'revanced_custom_playback_speeds_auto'. -->
</patch>

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