mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-23 10:41:03 +00:00
Compare commits
56 Commits
v5.35.0-de
...
v5.37.1-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1db131e90e | ||
|
|
abe3943f98 | ||
|
|
cb6d802de3 | ||
|
|
f11d1ef990 | ||
|
|
3d25da18bc | ||
|
|
fa04c8eecf | ||
|
|
105f6e0e97 | ||
|
|
7d59efe05d | ||
|
|
81ff5576b0 | ||
|
|
9a5c102c0d | ||
|
|
e6c79f1383 | ||
|
|
2a582eced8 | ||
|
|
2db0948bea | ||
|
|
a3ba92e742 | ||
|
|
2a85a3b290 | ||
|
|
eee72208dd | ||
|
|
dcd42454bd | ||
|
|
782353c18a | ||
|
|
b53b870e8f | ||
|
|
09b941abf0 | ||
|
|
678ef4052e | ||
|
|
0abfab79d7 | ||
|
|
61cadf72cd | ||
|
|
e12359b94f | ||
|
|
c001daba4a | ||
|
|
e136f62d6e | ||
|
|
8ec405a359 | ||
|
|
2f4b3a887b | ||
|
|
d1fabb242b | ||
|
|
a53b00dd51 | ||
|
|
850c13e98e | ||
|
|
4310789a26 | ||
|
|
c4a720fbd3 | ||
|
|
3bdb8dbce0 | ||
|
|
4894f33c96 | ||
|
|
7f6093ee66 | ||
|
|
9d4aa5cd16 | ||
|
|
5ace6f587c | ||
|
|
796f56745e | ||
|
|
88b47ef414 | ||
|
|
8cd8e59bbc | ||
|
|
6e72b14d07 | ||
|
|
52b088327b | ||
|
|
8e934cc56b | ||
|
|
b3140d909b | ||
|
|
97645aa9f4 | ||
|
|
603e2d018c | ||
|
|
144af2f07e | ||
|
|
b8629aacb6 | ||
|
|
3951527f51 | ||
|
|
7a8b618c4e | ||
|
|
c66c42e946 | ||
|
|
b340769cf3 | ||
|
|
0a8cd7a7db | ||
|
|
39f90e4b11 | ||
|
|
9256aa4548 |
4
.github/workflows/build_pull_request.yml
vendored
4
.github/workflows/build_pull_request.yml
vendored
@@ -12,10 +12,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|||||||
2
.github/workflows/open_pull_request.yml
vendored
2
.github/workflows/open_pull_request.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Open pull request
|
- name: Open pull request
|
||||||
uses: repo-sync/pull-request@v2
|
uses: repo-sync/pull-request@v2
|
||||||
|
|||||||
2
.github/workflows/pull_strings.yml
vendored
2
.github/workflows/pull_strings.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
clean: true
|
clean: true
|
||||||
|
|||||||
2
.github/workflows/push_strings.yml
vendored
2
.github/workflows/push_strings.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Preprocess strings
|
- name: Preprocess strings
|
||||||
env:
|
env:
|
||||||
|
|||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -18,10 +18,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
@@ -51,14 +51,14 @@ jobs:
|
|||||||
fingerprint: ${{ vars.GPG_FINGERPRINT }}
|
fingerprint: ${{ vars.GPG_FINGERPRINT }}
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: cycjimmy/semantic-release-action@v5
|
uses: cycjimmy/semantic-release-action@v4
|
||||||
id: release
|
id: release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Attest
|
- name: Attest
|
||||||
if: steps.release.outputs.new_release_published == 'true'
|
if: steps.release.outputs.new_release_published == 'true'
|
||||||
uses: actions/attest-build-provenance@v3
|
uses: actions/attest-build-provenance@v2
|
||||||
with:
|
with:
|
||||||
subject-name: 'ReVanced Patches ${{ steps.release.outputs.new_release_git_tag }}'
|
subject-name: 'ReVanced Patches ${{ steps.release.outputs.new_release_git_tag }}'
|
||||||
subject-path: patches/build/libs/patches-*.rvp
|
subject-path: patches/build/libs/patches-*.rvp
|
||||||
|
|||||||
2
.github/workflows/update-gradle-wrapper.yml
vendored
2
.github/workflows/update-gradle-wrapper.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Update Gradle Wrapper
|
- name: Update Gradle Wrapper
|
||||||
uses: gradle-update/update-gradle-wrapper-action@v1
|
uses: gradle-update/update-gradle-wrapper-action@v1
|
||||||
|
|||||||
210
CHANGELOG.md
210
CHANGELOG.md
@@ -1,3 +1,213 @@
|
|||||||
|
## [5.37.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.2...v5.37.1-dev.3) (2025-09-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spoof video streams:** Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for `Force original audio` to work with any spoof client ([#5861](https://github.com/ReVanced/revanced-patches/issues/5861)) ([abe3943](https://github.com/ReVanced/revanced-patches/commit/abe3943f98fd86dcd74c7e07cf65d3c7fc24fef9))
|
||||||
|
|
||||||
|
## [5.37.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.1...v5.37.1-dev.2) (2025-09-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Instagram - Hide navigation buttons:** Support v397.1.0.52.81 ([#5855](https://github.com/ReVanced/revanced-patches/issues/5855)) ([f11d1ef](https://github.com/ReVanced/revanced-patches/commit/f11d1ef9907082512f139d4ab0e2e9f707de7e48))
|
||||||
|
|
||||||
|
## [5.37.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.37.0...v5.37.1-dev.1) (2025-09-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube Music - Spoof video streams:** Fix playback issues when using a cellular network ([fa04c8e](https://github.com/ReVanced/revanced-patches/commit/fa04c8eecfbdd0b6ed082b464ca9032536d71762))
|
||||||
|
|
||||||
|
# [5.37.0](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0) (2025-09-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](https://github.com/ReVanced/revanced-patches/commit/e6c79f13834c83fef04e4dee5e628cb0b9a27765))
|
||||||
|
* Resolve patching with dev branch ([09b941a](https://github.com/ReVanced/revanced-patches/commit/09b941abf0e8029999565082b02a88b5de507ec4))
|
||||||
|
* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](https://github.com/ReVanced/revanced-patches/commit/dcd42454bd5f87dddd720534f6120c4ef90063a3))
|
||||||
|
* **Viber - Hide ads:** Add constrain to known working version ([2db0948](https://github.com/ReVanced/revanced-patches/commit/2db0948beaf2b68391a1fe7f21e92d31c7df61e7))
|
||||||
|
* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](https://github.com/ReVanced/revanced-patches/commit/2a85a3b29092729ae16d1fd93803634ce5f08e95))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](https://github.com/ReVanced/revanced-patches/commit/0abfab79d7cda15bf17c53679fbfffb021662649))
|
||||||
|
|
||||||
|
# [5.37.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.5...v5.37.0-dev.6) (2025-09-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](https://github.com/ReVanced/revanced-patches/commit/e6c79f13834c83fef04e4dee5e628cb0b9a27765))
|
||||||
|
|
||||||
|
# [5.37.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.4...v5.37.0-dev.5) (2025-09-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Viber - Hide ads:** Add constrain to known working version ([2db0948](https://github.com/ReVanced/revanced-patches/commit/2db0948beaf2b68391a1fe7f21e92d31c7df61e7))
|
||||||
|
|
||||||
|
# [5.37.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.3...v5.37.0-dev.4) (2025-09-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](https://github.com/ReVanced/revanced-patches/commit/2a85a3b29092729ae16d1fd93803634ce5f08e95))
|
||||||
|
|
||||||
|
# [5.37.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.2...v5.37.0-dev.3) (2025-09-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](https://github.com/ReVanced/revanced-patches/commit/dcd42454bd5f87dddd720534f6120c4ef90063a3))
|
||||||
|
|
||||||
|
# [5.37.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.1...v5.37.0-dev.2) (2025-09-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Resolve patching with dev branch ([09b941a](https://github.com/ReVanced/revanced-patches/commit/09b941abf0e8029999565082b02a88b5de507ec4))
|
||||||
|
|
||||||
|
# [5.37.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0-dev.1) (2025-09-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](https://github.com/ReVanced/revanced-patches/commit/0abfab79d7cda15bf17c53679fbfffb021662649))
|
||||||
|
|
||||||
|
# [5.36.0](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0) (2025-09-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411))
|
||||||
|
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0))
|
||||||
|
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](https://github.com/ReVanced/revanced-patches/commit/a53b00dd514dbe2b3406f3c1013a4f58a7f481c5))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe))
|
||||||
|
|
||||||
|
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411))
|
||||||
|
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0))
|
||||||
|
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](https://github.com/ReVanced/revanced-patches/commit/a53b00dd514dbe2b3406f3c1013a4f58a7f481c5))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe))
|
||||||
|
|
||||||
|
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411))
|
||||||
|
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0))
|
||||||
|
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](https://github.com/ReVanced/revanced-patches/commit/a53b00dd514dbe2b3406f3c1013a4f58a7f481c5))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe))
|
||||||
|
|
||||||
|
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411))
|
||||||
|
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe))
|
||||||
|
|
||||||
|
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411))
|
||||||
|
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe))
|
||||||
|
|
||||||
|
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.1-dev.1...v5.36.0-dev.1) (2025-09-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([2e042c4](https://github.com/ReVanced/revanced-patches/commit/2e042c4b3366fa3daf991d5560fcae991d00ad12))
|
||||||
|
|
||||||
|
## [5.35.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.35.1-dev.1) (2025-09-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([8491516](https://github.com/ReVanced/revanced-patches/commit/849151637389b8f399356d0d331bb74482f3f05d))
|
||||||
|
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([3af4126](https://github.com/ReVanced/revanced-patches/commit/3af41265338ddaab52d009f53370c57abddd4599))
|
||||||
|
|
||||||
|
# [5.35.0](https://github.com/ReVanced/revanced-patches/compare/v5.34.0...v5.35.0) (2025-09-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0d10e94](https://github.com/ReVanced/revanced-patches/commit/0d10e94663283fac09f3efc57c9b9805c38c4e13))
|
||||||
|
* **Proton mail:** Constrain patches to last working app target ([21c34b9](https://github.com/ReVanced/revanced-patches/commit/21c34b908e07a97de8c31c7c828b44a8cc4739b6))
|
||||||
|
* Revert dependency updates to fix Manager pre-release patching ([4c7a1a8](https://github.com/ReVanced/revanced-patches/commit/4c7a1a8554c67797bf663e5230f566c5a9b229af))
|
||||||
|
* **Spotify - Unlock Premium:** Make compatible with latest versions again by fixing fingerprint ([#5684](https://github.com/ReVanced/revanced-patches/issues/5684)) ([30dcff1](https://github.com/ReVanced/revanced-patches/commit/30dcff13a56883efc499b71faadb403877cd1c67))
|
||||||
|
* **YouTube - Hide layout components:** Hide Playable shelf header ([fbb5046](https://github.com/ReVanced/revanced-patches/commit/fbb50463f0e3f533a278c5251cfbce59f09ce641))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([08868c0](https://github.com/ReVanced/revanced-patches/commit/08868c00d3c4f1f37f4a77f333a03ca5a3259b59))
|
||||||
|
* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c5e8079](https://github.com/ReVanced/revanced-patches/commit/c5e8079eab08075a72078cd0fa79f3beb1f75d98))
|
||||||
|
* **Instagram:** Add `Hide navigation buttons` patch ([#5678](https://github.com/ReVanced/revanced-patches/issues/5678)) ([415cf0f](https://github.com/ReVanced/revanced-patches/commit/415cf0fb5b9b3dcaf4592943a69eea1c10447b07))
|
||||||
|
* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([3ae3251](https://github.com/ReVanced/revanced-patches/commit/3ae3251dc0317b6ced136fe9aa14be369642f203))
|
||||||
|
|
||||||
|
# [5.35.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.4...v5.35.0-dev.5) (2025-09-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([08868c0](https://github.com/ReVanced/revanced-patches/commit/08868c00d3c4f1f37f4a77f333a03ca5a3259b59))
|
||||||
|
* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([3ae3251](https://github.com/ReVanced/revanced-patches/commit/3ae3251dc0317b6ced136fe9aa14be369642f203))
|
||||||
|
|
||||||
|
# [5.35.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.3...v5.35.0-dev.4) (2025-09-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c5e8079](https://github.com/ReVanced/revanced-patches/commit/c5e8079eab08075a72078cd0fa79f3beb1f75d98))
|
||||||
|
|
||||||
|
# [5.35.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.2...v5.35.0-dev.3) (2025-09-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0d10e94](https://github.com/ReVanced/revanced-patches/commit/0d10e94663283fac09f3efc57c9b9805c38c4e13))
|
||||||
|
|
||||||
|
# [5.35.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.1...v5.35.0-dev.2) (2025-09-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Revert dependency updates to fix Manager pre-release patching ([4c7a1a8](https://github.com/ReVanced/revanced-patches/commit/4c7a1a8554c67797bf663e5230f566c5a9b229af))
|
||||||
|
|
||||||
# [5.35.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.34.1-dev.3...v5.35.0-dev.1) (2025-09-03)
|
# [5.35.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.34.1-dev.3...v5.35.0-dev.1) (2025-09-03)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
8
adsfund.json
Normal file
8
adsfund.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"info": "This is verification file for ads.fund project",
|
||||||
|
"project": {
|
||||||
|
"name": "Revanced Patches",
|
||||||
|
"walletAddress": "0x7ab4091e00363654bf84B34151225742cd92FCE5",
|
||||||
|
"tokenAddress": "0xadf325f255083a3f3d9a9d01ffb3db52a148d802"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
extensions/baconreader/build.gradle.kts
Normal file
5
extensions/baconreader/build.gradle.kts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
dependencies {
|
||||||
|
compileOnly(project(":extensions:shared:library"))
|
||||||
|
compileOnly(libs.annotation)
|
||||||
|
compileOnly(libs.okhttp)
|
||||||
|
}
|
||||||
1
extensions/baconreader/src/main/AndroidManifest.xml
Normal file
1
extensions/baconreader/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package app.revanced.extension.baconreader;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @noinspection unused
|
||||||
|
*/
|
||||||
|
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
|
||||||
|
static {
|
||||||
|
INSTANCE = new FixRedgifsApiPatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultUserAgent() {
|
||||||
|
// BaconReader uses a static user agent for Redgifs API calls
|
||||||
|
return "BaconReader";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OkHttpClient install(OkHttpClient.Builder builder) {
|
||||||
|
return builder.addInterceptor(INSTANCE).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(project(":extensions:shared:library"))
|
compileOnly(project(":extensions:shared:library"))
|
||||||
compileOnly(project(":extensions:boostforreddit:stub"))
|
compileOnly(project(":extensions:boostforreddit:stub"))
|
||||||
|
compileOnly(libs.annotation)
|
||||||
|
compileOnly(libs.okhttp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package app.revanced.extension.boostforreddit;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @noinspection unused
|
||||||
|
*/
|
||||||
|
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
|
||||||
|
static {
|
||||||
|
INSTANCE = new FixRedgifsApiPatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultUserAgent() {
|
||||||
|
// Boost uses a static user agent for Redgifs API calls
|
||||||
|
return "Boost";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OkHttpClient createClient() {
|
||||||
|
return new OkHttpClient.Builder().addInterceptor(INSTANCE).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
dependencies {
|
||||||
|
compileOnly(project(":extensions:shared:library"))
|
||||||
|
compileOnly(project(":extensions:youtube:stub"))
|
||||||
|
compileOnly(libs.annotation)
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package app.revanced.extension.music.patches.spoof;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
|
||||||
|
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
|
||||||
|
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class SpoofVideoStreamsPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setClientOrderToUse() {
|
||||||
|
List<ClientType> availableClients = List.of(
|
||||||
|
ANDROID_VR_1_43_32,
|
||||||
|
ANDROID_VR_1_61_48,
|
||||||
|
VISIONOS
|
||||||
|
);
|
||||||
|
|
||||||
|
StreamingDataRequest.setClientOrderToUse(availableClients, ANDROID_VR_1_43_32);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package app.revanced.extension.music.spoof;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @noinspection unused
|
|
||||||
*/
|
|
||||||
public class SpoofClientPatch {
|
|
||||||
private static final int CLIENT_TYPE_ID = 26;
|
|
||||||
private static final String CLIENT_VERSION = "6.21";
|
|
||||||
private static final String DEVICE_MODEL = "iPhone16,2";
|
|
||||||
private static final String OS_VERSION = "17.7.2.21H221";
|
|
||||||
|
|
||||||
public static int getClientId() {
|
|
||||||
return CLIENT_TYPE_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getClientVersion() {
|
|
||||||
return CLIENT_VERSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getClientModel() {
|
|
||||||
return DEVICE_MODEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getOsVersion() {
|
|
||||||
return OS_VERSION;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":extensions:shared:library"))
|
implementation(project(":extensions:shared:library"))
|
||||||
|
compileOnly(libs.okhttp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
|
compileOnly(libs.okhttp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package app.revanced.extension.shared.fixes.redgifs;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.Protocol;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class BaseFixRedgifsApiPatch implements Interceptor {
|
||||||
|
protected static BaseFixRedgifsApiPatch INSTANCE;
|
||||||
|
public abstract String getDefaultUserAgent();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Response intercept(@NonNull Chain chain) throws IOException {
|
||||||
|
Request request = chain.request();
|
||||||
|
if (!request.url().host().equals("api.redgifs.com")) {
|
||||||
|
return chain.proceed(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
String userAgent = getDefaultUserAgent();
|
||||||
|
|
||||||
|
if (request.header("Authorization") != null) {
|
||||||
|
Response response = chain.proceed(request.newBuilder().header("User-Agent", userAgent).build());
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
// It's possible that the user agent is being overwritten later down in the interceptor
|
||||||
|
// chain, so make sure we grab the new user agent from the request headers.
|
||||||
|
userAgent = response.request().header("User-Agent");
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
RedgifsTokenManager.RedgifsToken token = RedgifsTokenManager.refreshToken(userAgent);
|
||||||
|
|
||||||
|
// Emulate response for old OAuth endpoint
|
||||||
|
if (request.url().encodedPath().equals("/v2/oauth/client")) {
|
||||||
|
String responseBody = RedgifsTokenManager.getEmulatedOAuthResponseBody(token);
|
||||||
|
return new Response.Builder()
|
||||||
|
.message("OK")
|
||||||
|
.code(HttpURLConnection.HTTP_OK)
|
||||||
|
.protocol(Protocol.HTTP_1_1)
|
||||||
|
.request(request)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(ResponseBody.create(
|
||||||
|
responseBody, MediaType.get("application/json")))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
Request modifiedRequest = request.newBuilder()
|
||||||
|
.header("Authorization", "Bearer " + token.getAccessToken())
|
||||||
|
.header("User-Agent", userAgent)
|
||||||
|
.build();
|
||||||
|
return chain.proceed(modifiedRequest);
|
||||||
|
} catch (JSONException ex) {
|
||||||
|
Logger.printException(() -> "Could not parse Redgifs response", ex);
|
||||||
|
throw new IOException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package app.revanced.extension.shared.fixes.redgifs;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||||
|
|
||||||
|
import androidx.annotation.GuardedBy;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.requests.Requester;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages Redgifs token lifecycle.
|
||||||
|
*/
|
||||||
|
public class RedgifsTokenManager {
|
||||||
|
public static class RedgifsToken {
|
||||||
|
// Expire after 23 hours to provide some breathing room
|
||||||
|
private static final long EXPIRY_SECONDS = 23 * 60 * 60;
|
||||||
|
|
||||||
|
private final String accessToken;
|
||||||
|
private final long refreshTimeInSeconds;
|
||||||
|
|
||||||
|
public RedgifsToken(String accessToken, long refreshTime) {
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.refreshTimeInSeconds = refreshTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessToken() {
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getExpiryTimeInSeconds() {
|
||||||
|
return refreshTimeInSeconds + EXPIRY_SECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
if (accessToken == null) return false;
|
||||||
|
return getExpiryTimeInSeconds() >= System.currentTimeMillis() / 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static final String REDGIFS_API_HOST = "https://api.redgifs.com";
|
||||||
|
private static final String GET_TEMPORARY_TOKEN = REDGIFS_API_HOST + "/v2/auth/temporary";
|
||||||
|
@GuardedBy("itself")
|
||||||
|
private static final Map<String, RedgifsToken> tokenMap = new HashMap<>();
|
||||||
|
|
||||||
|
private static String getToken(String userAgent) throws IOException, JSONException {
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) new URL(GET_TEMPORARY_TOKEN).openConnection();
|
||||||
|
connection.setFixedLengthStreamingMode(0);
|
||||||
|
connection.setRequestMethod(GET.name());
|
||||||
|
connection.setRequestProperty("User-Agent", userAgent);
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json");
|
||||||
|
connection.setRequestProperty("Accept", "application/json");
|
||||||
|
connection.setUseCaches(false);
|
||||||
|
|
||||||
|
JSONObject responseObject = Requester.parseJSONObject(connection);
|
||||||
|
return responseObject.getString("token");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RedgifsToken refreshToken(String userAgent) throws IOException, JSONException {
|
||||||
|
synchronized(tokenMap) {
|
||||||
|
// Reference: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/pull/67
|
||||||
|
RedgifsToken token = tokenMap.get(userAgent);
|
||||||
|
if (token != null && token.isValid()) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy user agent from original request if present because Redgifs verifies
|
||||||
|
// that the user agent in subsequent requests matches the one in the OAuth token.
|
||||||
|
String accessToken = getToken(userAgent);
|
||||||
|
long refreshTime = System.currentTimeMillis() / 1000;
|
||||||
|
token = new RedgifsToken(accessToken, refreshTime);
|
||||||
|
tokenMap.put(userAgent, token);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getEmulatedOAuthResponseBody(RedgifsToken token) throws JSONException {
|
||||||
|
// Reference: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/pull/67
|
||||||
|
JSONObject responseObject = new JSONObject();
|
||||||
|
responseObject.put("access_token", token.accessToken);
|
||||||
|
responseObject.put("expiry_time", token.getExpiryTimeInSeconds() - (System.currentTimeMillis() / 1000));
|
||||||
|
responseObject.put("scope", "read");
|
||||||
|
responseObject.put("token_type", "Bearer");
|
||||||
|
return responseObject.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import static java.lang.Boolean.FALSE;
|
|||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||||
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability;
|
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability;
|
||||||
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.spoof.ClientType;
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
|
||||||
@@ -31,9 +30,6 @@ public class BaseSettings {
|
|||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||||
public static final EnumSetting<AppLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
|
public static final EnumSetting<AppLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
|
||||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
|
|
||||||
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
|
|
||||||
// Client type must be last spoof setting due to cyclic references.
|
// Client type must be last spoof setting due to cyclic references.
|
||||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_UNPLUGGED, true, parent(SPOOF_VIDEO_STREAMS));
|
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_61_48, true, parent(SPOOF_VIDEO_STREAMS));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,21 @@ package app.revanced.extension.shared.spoof;
|
|||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
|
||||||
|
|
||||||
public enum ClientType {
|
public enum ClientType {
|
||||||
|
/**
|
||||||
|
* Video not playable: Kids / Paid / Movie / Private / Age-restricted.
|
||||||
|
* This client can only be used when logged out.
|
||||||
|
*/
|
||||||
// https://dumps.tadiphone.dev/dumps/oculus/eureka
|
// https://dumps.tadiphone.dev/dumps/oculus/eureka
|
||||||
ANDROID_VR_NO_AUTH(
|
ANDROID_VR_1_61_48(
|
||||||
28,
|
28,
|
||||||
"ANDROID_VR",
|
"ANDROID_VR",
|
||||||
"com.google.android.apps.youtube.vr.oculus",
|
"com.google.android.apps.youtube.vr.oculus",
|
||||||
@@ -27,29 +31,32 @@ public enum ClientType {
|
|||||||
"1.61.48",
|
"1.61.48",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
"Android VR No auth"
|
"Android VR 1.61"
|
||||||
),
|
),
|
||||||
// Chromecast with Google TV 4K.
|
/**
|
||||||
// https://dumps.tadiphone.dev/dumps/google/kirkwood
|
* Uses non adaptive bitrate, which fixes audio stuttering with YT Music.
|
||||||
ANDROID_UNPLUGGED(
|
* Does not use AV1.
|
||||||
29,
|
*/
|
||||||
"ANDROID_UNPLUGGED",
|
ANDROID_VR_1_43_32(
|
||||||
"com.google.android.apps.youtube.unplugged",
|
ANDROID_VR_1_61_48.id,
|
||||||
"Google",
|
ANDROID_VR_1_61_48.clientName,
|
||||||
"Google TV Streamer",
|
Objects.requireNonNull(ANDROID_VR_1_61_48.packageName),
|
||||||
"Android",
|
ANDROID_VR_1_61_48.deviceMake,
|
||||||
"14",
|
ANDROID_VR_1_61_48.deviceModel,
|
||||||
"34",
|
ANDROID_VR_1_61_48.osName,
|
||||||
"UTT3.240625.001.K5",
|
ANDROID_VR_1_61_48.osVersion,
|
||||||
"132.0.6808.3",
|
Objects.requireNonNull(ANDROID_VR_1_61_48.androidSdkVersion),
|
||||||
"8.49.0",
|
Objects.requireNonNull(ANDROID_VR_1_61_48.buildId),
|
||||||
true,
|
"107.0.5284.2",
|
||||||
true,
|
"1.43.32",
|
||||||
"Android TV"
|
ANDROID_VR_1_61_48.requiresAuth,
|
||||||
|
ANDROID_VR_1_61_48.useAuth,
|
||||||
|
"Android VR 1.43"
|
||||||
),
|
),
|
||||||
// Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
|
/**
|
||||||
// Google Pixel 9 Pro Fold
|
* Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
|
||||||
// https://dumps.tadiphone.dev/dumps/google/barbet
|
* <a href="https://dumps.tadiphone.dev/dumps/google/barbet">Google Pixel 9 Pro Fold</a>
|
||||||
|
*/
|
||||||
ANDROID_CREATOR(
|
ANDROID_CREATOR(
|
||||||
14,
|
14,
|
||||||
"ANDROID_CREATOR",
|
"ANDROID_CREATOR",
|
||||||
@@ -66,58 +73,22 @@ public enum ClientType {
|
|||||||
true,
|
true,
|
||||||
"Android Creator"
|
"Android Creator"
|
||||||
),
|
),
|
||||||
IOS_UNPLUGGED(
|
/**
|
||||||
33,
|
* Internal YT client for an unreleased YT client. May stop working at any time.
|
||||||
"IOS_UNPLUGGED",
|
*/
|
||||||
"com.google.ios.youtubeunplugged",
|
VISIONOS(101,
|
||||||
|
"VISIONOS",
|
||||||
"Apple",
|
"Apple",
|
||||||
forceAVC()
|
"RealityDevice14,1",
|
||||||
// 11 Pro Max (last device with iOS 13)
|
"visionOS",
|
||||||
? "iPhone12,5"
|
"1.3.21O771",
|
||||||
// 15 Pro Max
|
"0.1",
|
||||||
: "iPhone16,2",
|
"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",
|
||||||
"iOS",
|
false,
|
||||||
forceAVC()
|
false,
|
||||||
// iOS 13 and earlier uses only AVC. 14+ adds VP9 and AV1.
|
"visionOS"
|
||||||
? "13.7.17H35"
|
|
||||||
: "18.2.22C152",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
// Version number should be a valid iOS release.
|
|
||||||
// https://www.ipa4fun.com/history/152043/
|
|
||||||
forceAVC()
|
|
||||||
// Some newer versions can also force AVC,
|
|
||||||
// but 6.45 is the last version that supports iOS 13.
|
|
||||||
? "6.45"
|
|
||||||
: "8.49",
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
forceAVC()
|
|
||||||
? "iOS TV Force AVC"
|
|
||||||
: "iOS TV"
|
|
||||||
),
|
|
||||||
ANDROID_VR_AUTH(
|
|
||||||
ANDROID_VR_NO_AUTH.id,
|
|
||||||
ANDROID_VR_NO_AUTH.clientName,
|
|
||||||
ANDROID_VR_NO_AUTH.packageName,
|
|
||||||
ANDROID_VR_NO_AUTH.deviceMake,
|
|
||||||
ANDROID_VR_NO_AUTH.deviceModel,
|
|
||||||
ANDROID_VR_NO_AUTH.osName,
|
|
||||||
ANDROID_VR_NO_AUTH.osVersion,
|
|
||||||
ANDROID_VR_NO_AUTH.androidSdkVersion,
|
|
||||||
ANDROID_VR_NO_AUTH.buildId,
|
|
||||||
ANDROID_VR_NO_AUTH.cronetVersion,
|
|
||||||
ANDROID_VR_NO_AUTH.clientVersion,
|
|
||||||
ANDROID_VR_NO_AUTH.requiresAuth,
|
|
||||||
true,
|
|
||||||
"Android VR Auth"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
private static boolean forceAVC() {
|
|
||||||
return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* YouTube
|
* YouTube
|
||||||
* <a href="https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients">client type</a>
|
* <a href="https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients">client type</a>
|
||||||
@@ -129,6 +100,7 @@ public enum ClientType {
|
|||||||
/**
|
/**
|
||||||
* App package name.
|
* App package name.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
private final String packageName;
|
private final String packageName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,17 +170,20 @@ public enum ClientType {
|
|||||||
*/
|
*/
|
||||||
public final String friendlyName;
|
public final String friendlyName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android constructor.
|
||||||
|
*/
|
||||||
@SuppressWarnings("ConstantLocale")
|
@SuppressWarnings("ConstantLocale")
|
||||||
ClientType(int id,
|
ClientType(int id,
|
||||||
String clientName,
|
String clientName,
|
||||||
String packageName,
|
@NonNull String packageName,
|
||||||
String deviceMake,
|
String deviceMake,
|
||||||
String deviceModel,
|
String deviceModel,
|
||||||
String osName,
|
String osName,
|
||||||
String osVersion,
|
String osVersion,
|
||||||
@Nullable String androidSdkVersion,
|
@NonNull String androidSdkVersion,
|
||||||
@Nullable String buildId,
|
@NonNull String buildId,
|
||||||
@Nullable String cronetVersion,
|
@NonNull String cronetVersion,
|
||||||
String clientVersion,
|
String clientVersion,
|
||||||
boolean requiresAuth,
|
boolean requiresAuth,
|
||||||
boolean useAuth,
|
boolean useAuth,
|
||||||
@@ -229,31 +204,44 @@ public enum ClientType {
|
|||||||
this.friendlyName = friendlyName;
|
this.friendlyName = friendlyName;
|
||||||
|
|
||||||
Locale defaultLocale = Locale.getDefault();
|
Locale defaultLocale = Locale.getDefault();
|
||||||
if (androidSdkVersion == null) {
|
this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s; Cronet/%s)",
|
||||||
// Convert version from '18.2.22C152' into '18_2_22'
|
packageName,
|
||||||
String userAgentOsVersion = osVersion
|
clientVersion,
|
||||||
.replaceAll("(\\d+\\.\\d+\\.\\d+).*", "$1")
|
osVersion,
|
||||||
.replace(".", "_");
|
defaultLocale,
|
||||||
// https://github.com/mitmproxy/mitmproxy/issues/4836
|
deviceModel,
|
||||||
this.userAgent = String.format("%s/%s (%s; U; CPU iOS %s like Mac OS X; %s)",
|
Objects.requireNonNull(buildId),
|
||||||
packageName,
|
Objects.requireNonNull(cronetVersion)
|
||||||
clientVersion,
|
);
|
||||||
deviceModel,
|
|
||||||
userAgentOsVersion,
|
|
||||||
defaultLocale
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s; Cronet/%s)",
|
|
||||||
packageName,
|
|
||||||
clientVersion,
|
|
||||||
osVersion,
|
|
||||||
defaultLocale,
|
|
||||||
deviceModel,
|
|
||||||
Objects.requireNonNull(buildId),
|
|
||||||
Objects.requireNonNull(cronetVersion)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Logger.printDebug(() -> "userAgent: " + this.userAgent);
|
Logger.printDebug(() -> "userAgent: " + this.userAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantLocale")
|
||||||
|
ClientType(int id,
|
||||||
|
String clientName,
|
||||||
|
String deviceMake,
|
||||||
|
String deviceModel,
|
||||||
|
String osName,
|
||||||
|
String osVersion,
|
||||||
|
String clientVersion,
|
||||||
|
String userAgent,
|
||||||
|
boolean requiresAuth,
|
||||||
|
boolean useAuth,
|
||||||
|
String friendlyName) {
|
||||||
|
this.id = id;
|
||||||
|
this.clientName = clientName;
|
||||||
|
this.deviceMake = deviceMake;
|
||||||
|
this.deviceModel = deviceModel;
|
||||||
|
this.osName = osName;
|
||||||
|
this.osVersion = osVersion;
|
||||||
|
this.clientVersion = clientVersion;
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
this.requiresAuth = requiresAuth;
|
||||||
|
this.useAuth = useAuth;
|
||||||
|
this.friendlyName = friendlyName;
|
||||||
|
this.packageName = null;
|
||||||
|
this.androidSdkVersion = null;
|
||||||
|
this.buildId = null;
|
||||||
|
this.cronetVersion = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.shared.settings.AppLanguage;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
||||||
@@ -19,13 +20,25 @@ public class SpoofVideoStreamsPatch {
|
|||||||
private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_VIDEO_STREAMS.get();
|
private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_VIDEO_STREAMS.get();
|
||||||
|
|
||||||
private static final boolean FIX_HLS_CURRENT_TIME = SPOOF_STREAMING_DATA
|
private static final boolean FIX_HLS_CURRENT_TIME = SPOOF_STREAMING_DATA
|
||||||
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
|
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.VISIONOS;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static volatile AppLanguage languageOverride;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any unreachable ip address. Used to intentionally fail requests.
|
* Domain used for internet connectivity verification.
|
||||||
|
* It has an empty response body and is only used to check for a 204 response code.
|
||||||
|
* <p>
|
||||||
|
* If an unreachable IP address (127.0.0.1) is used, no response code is provided.
|
||||||
|
* <p>
|
||||||
|
* YouTube handles unreachable IP addresses without issue.
|
||||||
|
* YouTube Music has an issue with waiting for the Cronet connect timeout of 30s on mobile networks.
|
||||||
|
* <p>
|
||||||
|
* Using a VPN or DNS can temporarily resolve this issue,
|
||||||
|
* But the ideal workaround is to avoid using an unreachable IP address.
|
||||||
*/
|
*/
|
||||||
private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0";
|
private static final String INTERNET_CONNECTION_CHECK_URI_STRING = "https://www.google.com/gen_204";
|
||||||
private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING);
|
private static final Uri INTERNET_CONNECTION_CHECK_URI = Uri.parse(INTERNET_CONNECTION_CHECK_URI_STRING);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If this patch was included during patching.
|
* @return If this patch was included during patching.
|
||||||
@@ -34,10 +47,21 @@ public class SpoofVideoStreamsPatch {
|
|||||||
return false; // Modified during patching.
|
return false; // Modified during patching.
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean notSpoofingToAndroid() {
|
public static boolean spoofingToClientWithNoMultiAudioStreams() {
|
||||||
return !isPatchIncluded()
|
return isPatchIncluded() && BaseSettings.SPOOF_VIDEO_STREAMS.get();
|
||||||
|| !BaseSettings.SPOOF_VIDEO_STREAMS.get()
|
}
|
||||||
|| BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
|
|
||||||
|
/**
|
||||||
|
* @param language Language override for non-authenticated requests. If this is null then
|
||||||
|
* {@link BaseSettings#SPOOF_VIDEO_STREAMS_LANGUAGE} is used.
|
||||||
|
*/
|
||||||
|
public static void setLanguageOverride(@Nullable AppLanguage language) {
|
||||||
|
languageOverride = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static AppLanguage getLanguageOverride() {
|
||||||
|
return languageOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,9 +77,9 @@ public class SpoofVideoStreamsPatch {
|
|||||||
String path = playerRequestUri.getPath();
|
String path = playerRequestUri.getPath();
|
||||||
|
|
||||||
if (path != null && path.contains("get_watch")) {
|
if (path != null && path.contains("get_watch")) {
|
||||||
Logger.printDebug(() -> "Blocking 'get_watch' by returning unreachable uri");
|
Logger.printDebug(() -> "Blocking 'get_watch' by returning internet connection check uri");
|
||||||
|
|
||||||
return UNREACHABLE_HOST_URI;
|
return INTERNET_CONNECTION_CHECK_URI;
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "blockGetWatchRequest failure", ex);
|
Logger.printException(() -> "blockGetWatchRequest failure", ex);
|
||||||
@@ -77,9 +101,9 @@ public class SpoofVideoStreamsPatch {
|
|||||||
String path = originalUri.getPath();
|
String path = originalUri.getPath();
|
||||||
|
|
||||||
if (path != null && path.contains("initplayback")) {
|
if (path != null && path.contains("initplayback")) {
|
||||||
Logger.printDebug(() -> "Blocking 'initplayback' by clearing query");
|
Logger.printDebug(() -> "Blocking 'initplayback' by returning internet connection check uri");
|
||||||
|
|
||||||
return originalUri.buildUpon().clearQuery().build().toString();
|
return INTERNET_CONNECTION_CHECK_URI_STRING;
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
|
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
|
||||||
@@ -252,16 +276,8 @@ public class SpoofVideoStreamsPatch {
|
|||||||
public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability {
|
public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability {
|
||||||
@Override
|
@Override
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
return BaseSettings.SPOOF_VIDEO_STREAMS.get()
|
// Since all current clients are un-authenticated, this works for all spoof clients.
|
||||||
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_VR_NO_AUTH;
|
return BaseSettings.SPOOF_VIDEO_STREAMS.get();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class SpoofiOSAvailability implements Setting.Availability {
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
return BaseSettings.SPOOF_VIDEO_STREAMS.get()
|
|
||||||
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package app.revanced.extension.shared.spoof.requests;
|
package app.revanced.extension.shared.spoof.requests;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
@@ -10,8 +12,10 @@ import java.util.Locale;
|
|||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.requests.Requester;
|
import app.revanced.extension.shared.requests.Requester;
|
||||||
import app.revanced.extension.shared.requests.Route;
|
import app.revanced.extension.shared.requests.Route;
|
||||||
|
import app.revanced.extension.shared.settings.AppLanguage;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.spoof.ClientType;
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||||
|
|
||||||
final class PlayerRoutes {
|
final class PlayerRoutes {
|
||||||
static final Route.CompiledRoute GET_STREAMING_DATA = new Route(
|
static final Route.CompiledRoute GET_STREAMING_DATA = new Route(
|
||||||
@@ -37,14 +41,16 @@ final class PlayerRoutes {
|
|||||||
try {
|
try {
|
||||||
JSONObject context = new JSONObject();
|
JSONObject context = new JSONObject();
|
||||||
|
|
||||||
// Can override default language only if no login is used.
|
AppLanguage language = SpoofVideoStreamsPatch.getLanguageOverride();
|
||||||
// Could use preferred audio for all clients that do not login,
|
if (language == null || BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ANDROID_VR_1_43_32) {
|
||||||
// but if this is a fall over client it will set the language even though
|
// Force original audio has not overrode the language.
|
||||||
// the audio language is not selectable in the UI.
|
// Or if YT has fallen over to the very last client (VR 1.43), then always
|
||||||
ClientType userSelectedClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
|
// use the app language because forcing an audio stream of specific languages
|
||||||
Locale streamLocale = userSelectedClient == ClientType.ANDROID_VR_NO_AUTH
|
// can sometimes fail so it's better to try and load something rather than nothing.
|
||||||
? BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get().getLocale()
|
language = BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get();
|
||||||
: Locale.getDefault();
|
}
|
||||||
|
//noinspection ExtractMethodRecommender
|
||||||
|
Locale streamLocale = language.getLocale();
|
||||||
|
|
||||||
JSONObject client = new JSONObject();
|
JSONObject client = new JSONObject();
|
||||||
client.put("deviceMake", clientType.deviceMake);
|
client.put("deviceMake", clientType.deviceMake);
|
||||||
|
|||||||
@@ -35,21 +35,27 @@ import app.revanced.extension.shared.spoof.ClientType;
|
|||||||
*/
|
*/
|
||||||
public class StreamingDataRequest {
|
public class StreamingDataRequest {
|
||||||
|
|
||||||
private static final ClientType[] CLIENT_ORDER_TO_USE;
|
private static volatile ClientType[] clientOrderToUse = ClientType.values();
|
||||||
|
|
||||||
static {
|
public static void setClientOrderToUse(List<ClientType> availableClients, ClientType preferredClient) {
|
||||||
ClientType[] allClientTypes = ClientType.values();
|
Objects.requireNonNull(preferredClient);
|
||||||
ClientType preferredClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
|
|
||||||
|
|
||||||
CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length];
|
int availableClientSize = availableClients.size();
|
||||||
CLIENT_ORDER_TO_USE[0] = preferredClient;
|
if (!availableClients.contains(preferredClient)) {
|
||||||
|
availableClientSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOrderToUse = new ClientType[availableClientSize];
|
||||||
|
clientOrderToUse[0] = preferredClient;
|
||||||
|
|
||||||
int i = 1;
|
int i = 1;
|
||||||
for (ClientType c : allClientTypes) {
|
for (ClientType c : availableClients) {
|
||||||
if (c != preferredClient) {
|
if (c != preferredClient) {
|
||||||
CLIENT_ORDER_TO_USE[i++] = c;
|
clientOrderToUse[i++] = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Available spoof clients: " + Arrays.toString(clientOrderToUse));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||||
@@ -193,9 +199,9 @@ public class StreamingDataRequest {
|
|||||||
|
|
||||||
// Retry with different client if empty response body is received.
|
// Retry with different client if empty response body is received.
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (ClientType clientType : CLIENT_ORDER_TO_USE) {
|
for (ClientType clientType : clientOrderToUse) {
|
||||||
// Show an error if the last client type fails, or if debug is enabled then show for all attempts.
|
// Show an error if the last client type fails, or if debug is enabled then show for all attempts.
|
||||||
final boolean showErrorToast = (++i == CLIENT_ORDER_TO_USE.length) || debugEnabled;
|
final boolean showErrorToast = (++i == clientOrderToUse.length) || debugEnabled;
|
||||||
|
|
||||||
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
|
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ dependencies {
|
|||||||
compileOnly(project(":extensions:shared:library"))
|
compileOnly(project(":extensions:shared:library"))
|
||||||
compileOnly(project(":extensions:syncforreddit:stub"))
|
compileOnly(project(":extensions:syncforreddit:stub"))
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
|
compileOnly(libs.okhttp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package app.revanced.extension.syncforreddit;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @noinspection unused
|
||||||
|
*/
|
||||||
|
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
|
||||||
|
static {
|
||||||
|
INSTANCE = new FixRedgifsApiPatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultUserAgent() {
|
||||||
|
// To be filled in by patch
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OkHttpClient install(OkHttpClient.Builder builder) {
|
||||||
|
return builder.addInterceptor(INSTANCE).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
import app.revanced.extension.shared.settings.AppLanguage;
|
||||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@@ -11,16 +11,20 @@ public class ForceOriginalAudioPatch {
|
|||||||
private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4";
|
private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the conditions to use this patch were present when the app launched.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean PATCH_AVAILABLE = SpoofVideoStreamsPatch.notSpoofingToAndroid();
|
public static void setPreferredLanguage() {
|
||||||
|
if (Settings.FORCE_ORIGINAL_AUDIO.get()) {
|
||||||
public static final class ForceOriginalAudioAvailability implements Setting.Availability {
|
// None of the current spoof clients support audio track menu,
|
||||||
@Override
|
// And all are un-authenticated and can request any language code
|
||||||
public boolean isAvailable() {
|
// (authenticated requests ignore the language code and always use the account language).
|
||||||
// Check conditions of launch and now. Otherwise if spoofing is changed
|
// To still support force original audio, if it's enabled then pick a language
|
||||||
// without a restart the setting will show as available when it's not.
|
// that is not auto-dubbed by YouTube: https://support.google.com/youtube/answer/15569972
|
||||||
return PATCH_AVAILABLE && SpoofVideoStreamsPatch.notSpoofingToAndroid();
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -513,6 +513,9 @@ public final class VideoInformation {
|
|||||||
Logger.printDebug(() -> "VideoQualities: " + Arrays.toString(currentQualities));
|
Logger.printDebug(() -> "VideoQualities: " + Arrays.toString(currentQualities));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On extremely slow internet connections the index can initially be -1
|
||||||
|
originalQualityIndex = Math.max(0, originalQualityIndex);
|
||||||
|
|
||||||
VideoQuality updatedCurrentQuality = qualities[originalQualityIndex];
|
VideoQuality updatedCurrentQuality = qualities[originalQualityIndex];
|
||||||
if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE
|
if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE
|
||||||
&& (currentQuality == null || currentQuality != updatedCurrentQuality)) {
|
&& (currentQuality == null || currentQuality != updatedCurrentQuality)) {
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
// Playable horizontal shelf header.
|
// Playable horizontal shelf header.
|
||||||
playablesBuffer = new ByteArrayFilterGroup(
|
playablesBuffer = new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_PLAYABLES,
|
Settings.HIDE_PLAYABLES,
|
||||||
"mini_game"
|
"FEmini_app_destination"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var quickActions = new StringFilterGroup(
|
final var quickActions = new StringFilterGroup(
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
|||||||
public class PlayerFlyoutMenuItemsFilter extends Filter {
|
public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||||
|
|
||||||
public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability {
|
public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability {
|
||||||
private static final boolean AVAILABLE_ON_LAUNCH = SpoofVideoStreamsPatch.notSpoofingToAndroid();
|
private static final boolean AVAILABLE_ON_LAUNCH = !SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
// Check conditions of launch and now. Otherwise if spoofing is changed
|
// Check conditions of launch and now. Otherwise if spoofing is changed
|
||||||
// without a restart the setting will show as available when it's not.
|
// without a restart the setting will show as available when it's not.
|
||||||
return AVAILABLE_ON_LAUNCH && SpoofVideoStreamsPatch.notSpoofingToAndroid();
|
return AVAILABLE_ON_LAUNCH && !SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package app.revanced.extension.youtube.patches.spoof;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_CREATOR;
|
||||||
|
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
|
||||||
|
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
|
||||||
|
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class SpoofVideoStreamsPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setClientOrderToUse() {
|
||||||
|
List<ClientType> availableClients = List.of(
|
||||||
|
ANDROID_VR_1_61_48,
|
||||||
|
ANDROID_CREATOR,
|
||||||
|
VISIONOS,
|
||||||
|
// VR 1.43 must be last as spoof streaming data handles it slightly differently.
|
||||||
|
ANDROID_VR_1_43_32
|
||||||
|
);
|
||||||
|
|
||||||
|
StreamingDataRequest.setClientOrderToUse(availableClients,
|
||||||
|
BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ import static app.revanced.extension.youtube.patches.ChangeHeaderPatch.HeaderLog
|
|||||||
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.ChangeStartPageTypeAvailability;
|
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.ChangeStartPageTypeAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
|
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.ExitFullscreenPatch.FullscreenMode;
|
||||||
import static app.revanced.extension.youtube.patches.ForceOriginalAudioPatch.ForceOriginalAudioAvailability;
|
|
||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
|
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
|
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
|
||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MINIMAL;
|
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MINIMAL;
|
||||||
@@ -75,7 +74,7 @@ public class Settings extends BaseSettings {
|
|||||||
"0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true);
|
"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
|
// Audio
|
||||||
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, new ForceOriginalAudioAvailability());
|
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, true);
|
||||||
|
|
||||||
// Ads
|
// Ads
|
||||||
public static final BooleanSetting HIDE_CREATOR_STORE_SHELF = new BooleanSetting("revanced_hide_creator_store_shelf", TRUE);
|
public static final BooleanSetting HIDE_CREATOR_STORE_SHELF = new BooleanSetting("revanced_hide_creator_store_shelf", TRUE);
|
||||||
@@ -436,6 +435,9 @@ public class Settings extends BaseSettings {
|
|||||||
public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue);
|
public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue);
|
||||||
public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#FF1684");
|
public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#FF1684");
|
||||||
public static final FloatSetting SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f);
|
public static final FloatSetting SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f);
|
||||||
|
public static final StringSetting SB_CATEGORY_HOOK = new StringSetting("sb_hook", IGNORE.reVancedKeyValue);
|
||||||
|
public static final StringSetting SB_CATEGORY_HOOK_COLOR = new StringSetting("sb_hook_color", "#395699");
|
||||||
|
public static final FloatSetting SB_CATEGORY_HOOK_OPACITY = new FloatSetting("sb_hook_opacity", 0.8f);
|
||||||
public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", MANUAL_SKIP.reVancedKeyValue);
|
public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", MANUAL_SKIP.reVancedKeyValue);
|
||||||
public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF");
|
public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF");
|
||||||
public static final FloatSetting SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f);
|
public static final FloatSetting SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f);
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package app.revanced.extension.youtube.settings.preference;
|
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.preference.SwitchPreference;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
import app.revanced.extension.youtube.patches.ForceOriginalAudioPatch;
|
|
||||||
|
|
||||||
@SuppressWarnings({"deprecation", "unused"})
|
|
||||||
public class ForceOriginalAudioSwitchPreference extends SwitchPreference {
|
|
||||||
|
|
||||||
{
|
|
||||||
if (!ForceOriginalAudioPatch.PATCH_AVAILABLE) {
|
|
||||||
// Show why force audio is not available.
|
|
||||||
String summary = str("revanced_force_original_audio_not_available");
|
|
||||||
setSummary(summary);
|
|
||||||
setSummaryOn(summary);
|
|
||||||
setSummaryOff(summary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
|
||||||
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
public ForceOriginalAudioSwitchPreference(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,8 +12,8 @@ import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
|||||||
public class HideAudioFlyoutMenuPreference extends SwitchPreference {
|
public class HideAudioFlyoutMenuPreference extends SwitchPreference {
|
||||||
|
|
||||||
{
|
{
|
||||||
// Audio menu is not available if spoofing to Android client type.
|
// Audio menu is not available if spoofing to most client types.
|
||||||
if (!SpoofVideoStreamsPatch.notSpoofingToAndroid()) {
|
if (SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams()) {
|
||||||
String summary = str("revanced_hide_player_flyout_audio_track_not_available");
|
String summary = str("revanced_hide_player_flyout_audio_track_not_available");
|
||||||
setSummary(summary);
|
setSummary(summary);
|
||||||
setSummaryOn(summary);
|
setSummaryOn(summary);
|
||||||
|
|||||||
@@ -78,20 +78,17 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
|||||||
Logger.printDebug(() -> "Updating spoof stream side effects preference");
|
Logger.printDebug(() -> "Updating spoof stream side effects preference");
|
||||||
setEnabled(BaseSettings.SPOOF_VIDEO_STREAMS.get());
|
setEnabled(BaseSettings.SPOOF_VIDEO_STREAMS.get());
|
||||||
|
|
||||||
String key = "revanced_spoof_video_streams_about_" +
|
String title = str("revanced_spoof_video_streams_about_title");
|
||||||
(clientType == ClientType.IOS_UNPLUGGED
|
// Currently only Android VR and VisionOS are supported, and both have the same base side effects.
|
||||||
? "ios_tv"
|
String summary = str("revanced_spoof_video_streams_about_android_summary");
|
||||||
: "android");
|
|
||||||
String title = str(key + "_title");
|
|
||||||
String summary = str(key + "_summary");
|
|
||||||
|
|
||||||
// Android VR supports AV1 but all other clients do not.
|
|
||||||
if (clientType != ClientType.ANDROID_VR_AUTH && clientType != ClientType.ANDROID_VR_NO_AUTH) {
|
|
||||||
summary += '\n' + str("revanced_spoof_video_streams_about_no_av1");
|
|
||||||
}
|
|
||||||
|
|
||||||
summary += '\n' + str("revanced_spoof_video_streams_about_kids_videos");
|
summary += '\n' + str("revanced_spoof_video_streams_about_kids_videos");
|
||||||
|
|
||||||
|
if (clientType == ClientType.VISIONOS) {
|
||||||
|
summary = str("revanced_spoof_video_streams_about_experimental")
|
||||||
|
+ '\n' + summary
|
||||||
|
+ '\n' + str("revanced_spoof_video_streams_about_no_av1");
|
||||||
|
}
|
||||||
|
|
||||||
setTitle(title);
|
setTitle(title);
|
||||||
setSummary(summary);
|
setSummary(summary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import android.graphics.drawable.shapes.RoundRectShape;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Range;
|
import android.util.Range;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
@@ -167,6 +168,11 @@ public class SegmentPlaybackController {
|
|||||||
*/
|
*/
|
||||||
private static WeakReference<Dialog> toastDialogRef = new WeakReference<>(null);
|
private static WeakReference<Dialog> toastDialogRef = new WeakReference<>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visibility of the ad progress UI component.
|
||||||
|
*/
|
||||||
|
private static volatile int adProgressTextVisibility = -1;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// Dismiss toast if app changes to PiP while undo skip is shown.
|
// Dismiss toast if app changes to PiP while undo skip is shown.
|
||||||
PlayerType.getOnChange().addObserver((PlayerType type) -> {
|
PlayerType.getOnChange().addObserver((PlayerType type) -> {
|
||||||
@@ -336,6 +342,7 @@ public class SegmentPlaybackController {
|
|||||||
*/
|
*/
|
||||||
static void executeDownloadSegments(String videoId) {
|
static void executeDownloadSegments(String videoId) {
|
||||||
Objects.requireNonNull(videoId);
|
Objects.requireNonNull(videoId);
|
||||||
|
Utils.verifyOffMainThread();
|
||||||
|
|
||||||
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
||||||
|
|
||||||
@@ -367,6 +374,35 @@ public class SegmentPlaybackController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static void setAdProgressTextVisibility(int visibility) {
|
||||||
|
if (adProgressTextVisibility != visibility) {
|
||||||
|
adProgressTextVisibility = visibility;
|
||||||
|
|
||||||
|
Logger.printDebug(() -> {
|
||||||
|
String visibilityMessage = switch (visibility) {
|
||||||
|
case View.VISIBLE -> "VISIBLE";
|
||||||
|
case View.GONE -> "GONE";
|
||||||
|
case View.INVISIBLE -> "INVISIBLE";
|
||||||
|
default -> "UNKNOWN";
|
||||||
|
};
|
||||||
|
return "AdProgressText visibility changed to: " + visibilityMessage;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a video ad is playing in a regular video player, segments or the Skip button should be hidden.
|
||||||
|
* @return Whether the Ad Progress TextView is visible in the regular video player.
|
||||||
|
*/
|
||||||
|
public static boolean isAdProgressTextVisible() {
|
||||||
|
return adProgressTextVisibility == View.VISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* Updates SponsorBlock every 1000ms.
|
* Updates SponsorBlock every 1000ms.
|
||||||
@@ -376,7 +412,8 @@ public class SegmentPlaybackController {
|
|||||||
try {
|
try {
|
||||||
if (!Settings.SB_ENABLED.get()
|
if (!Settings.SB_ENABLED.get()
|
||||||
|| PlayerType.getCurrent().isNoneOrHidden() // Shorts playback.
|
|| PlayerType.getCurrent().isNoneOrHidden() // Shorts playback.
|
||||||
|| segments == null || segments.length == 0) {
|
|| segments == null || segments.length == 0
|
||||||
|
|| isAdProgressTextVisible()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Logger.printDebug(() -> "setVideoTime: " + millis);
|
Logger.printDebug(() -> "setVideoTime: " + millis);
|
||||||
@@ -671,7 +708,14 @@ public class SegmentPlaybackController {
|
|||||||
// Check for any smaller embedded segments, and count those as auto-skipped.
|
// Check for any smaller embedded segments, and count those as auto-skipped.
|
||||||
final boolean showSkipToast = Settings.SB_TOAST_ON_SKIP.get();
|
final boolean showSkipToast = Settings.SB_TOAST_ON_SKIP.get();
|
||||||
for (SponsorSegment otherSegment : Objects.requireNonNull(segments)) {
|
for (SponsorSegment otherSegment : Objects.requireNonNull(segments)) {
|
||||||
if (segmentToSkip.end < otherSegment.start) {
|
if (otherSegment.end <= segmentToSkip.start) {
|
||||||
|
// Other segment does not overlap, and is before this skipped segment.
|
||||||
|
// This situation can only happen if a video is opened and adjusted to
|
||||||
|
// a later time in the video where earlier auto skip segments
|
||||||
|
// have not been encountered yet.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (segmentToSkip.end <= otherSegment.start) {
|
||||||
break; // No other segments can be contained.
|
break; // No other segments can be contained.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -922,7 +966,8 @@ public class SegmentPlaybackController {
|
|||||||
public static String appendTimeWithoutSegments(String totalTime) {
|
public static String appendTimeWithoutSegments(String totalTime) {
|
||||||
try {
|
try {
|
||||||
if (Settings.SB_ENABLED.get() && Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get()
|
if (Settings.SB_ENABLED.get() && Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get()
|
||||||
&& !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments)) {
|
&& !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments)
|
||||||
|
&& !isAdProgressTextVisible()) {
|
||||||
// Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages
|
// Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages
|
||||||
return "\u202D" + totalTime + timeWithoutSegments; // u202D = left to right override
|
return "\u202D" + totalTime + timeWithoutSegments; // u202D = left to right override
|
||||||
}
|
}
|
||||||
@@ -983,7 +1028,7 @@ public class SegmentPlaybackController {
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
|
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
|
||||||
try {
|
try {
|
||||||
if (segments == null) return;
|
if (segments == null || isAdProgressTextVisible()) return;
|
||||||
final long videoLength = VideoInformation.getVideoLength();
|
final long videoLength = VideoInformation.getVideoLength();
|
||||||
if (videoLength <= 0) return;
|
if (videoLength <= 0) return;
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ public enum SegmentCategory {
|
|||||||
sf("revanced_sb_skip_button_preview_beginning"), sf("revanced_sb_skip_button_preview_middle"), sf("revanced_sb_skip_button_preview_end"),
|
sf("revanced_sb_skip_button_preview_beginning"), sf("revanced_sb_skip_button_preview_middle"), sf("revanced_sb_skip_button_preview_end"),
|
||||||
sf("revanced_sb_skipped_preview_beginning"), sf("revanced_sb_skipped_preview_middle"), sf("revanced_sb_skipped_preview_end"),
|
sf("revanced_sb_skipped_preview_beginning"), sf("revanced_sb_skipped_preview_middle"), sf("revanced_sb_skipped_preview_end"),
|
||||||
SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR, SB_CATEGORY_PREVIEW_OPACITY),
|
SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR, SB_CATEGORY_PREVIEW_OPACITY),
|
||||||
|
HOOK("hook", sf("revanced_sb_segments_hook"), sf("revanced_sb_segments_hook_sum"), sf("revanced_sb_skip_button_hook"), sf("revanced_sb_skipped_hook"),
|
||||||
|
SB_CATEGORY_HOOK, SB_CATEGORY_HOOK_COLOR, SB_CATEGORY_HOOK_OPACITY),
|
||||||
FILLER("filler", sf("revanced_sb_segments_filler"), sf("revanced_sb_segments_filler_sum"), sf("revanced_sb_skip_button_filler"), sf("revanced_sb_skipped_filler"),
|
FILLER("filler", sf("revanced_sb_segments_filler"), sf("revanced_sb_segments_filler_sum"), sf("revanced_sb_skip_button_filler"), sf("revanced_sb_skipped_filler"),
|
||||||
SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR, SB_CATEGORY_FILLER_OPACITY),
|
SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR, SB_CATEGORY_FILLER_OPACITY),
|
||||||
MUSIC_OFFTOPIC("music_offtopic", sf("revanced_sb_segments_nomusic"), sf("revanced_sb_segments_nomusic_sum"), sf("revanced_sb_skip_button_nomusic"), sf("revanced_sb_skipped_nomusic"),
|
MUSIC_OFFTOPIC("music_offtopic", sf("revanced_sb_segments_nomusic"), sf("revanced_sb_segments_nomusic_sum"), sf("revanced_sb_skip_button_nomusic"), sf("revanced_sb_skipped_nomusic"),
|
||||||
@@ -69,6 +71,7 @@ public enum SegmentCategory {
|
|||||||
INTRO,
|
INTRO,
|
||||||
OUTRO,
|
OUTRO,
|
||||||
PREVIEW,
|
PREVIEW,
|
||||||
|
HOOK,
|
||||||
FILLER,
|
FILLER,
|
||||||
MUSIC_OFFTOPIC,
|
MUSIC_OFFTOPIC,
|
||||||
};
|
};
|
||||||
@@ -81,6 +84,7 @@ public enum SegmentCategory {
|
|||||||
INTRO,
|
INTRO,
|
||||||
OUTRO,
|
OUTRO,
|
||||||
PREVIEW,
|
PREVIEW,
|
||||||
|
HOOK,
|
||||||
FILLER,
|
FILLER,
|
||||||
MUSIC_OFFTOPIC,
|
MUSIC_OFFTOPIC,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
|
||||||
import app.revanced.extension.youtube.videoplayer.PlayerControlButton;
|
import app.revanced.extension.youtube.videoplayer.PlayerControlButton;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -26,7 +27,7 @@ public class CreateSegmentButton {
|
|||||||
controlsView,
|
controlsView,
|
||||||
"revanced_sb_create_segment_button",
|
"revanced_sb_create_segment_button",
|
||||||
null,
|
null,
|
||||||
CreateSegmentButton::shouldBeShown,
|
CreateSegmentButton::isButtonEnabled,
|
||||||
v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility(),
|
v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility(),
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
@@ -56,7 +57,8 @@ public class CreateSegmentButton {
|
|||||||
if (instance != null) instance.setVisibility(visible, animated);
|
if (instance != null) instance.setVisibility(visible, animated);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldBeShown() {
|
private static boolean isButtonEnabled() {
|
||||||
return Settings.SB_ENABLED.get() && Settings.SB_CREATE_NEW_SEGMENT.get();
|
return Settings.SB_ENABLED.get() && Settings.SB_CREATE_NEW_SEGMENT.get()
|
||||||
|
&& !SegmentPlaybackController.isAdProgressTextVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class VotingButton {
|
|||||||
controlsView,
|
controlsView,
|
||||||
"revanced_sb_voting_button",
|
"revanced_sb_voting_button",
|
||||||
null,
|
null,
|
||||||
VotingButton::shouldBeShown,
|
VotingButton::isButtonEnabled,
|
||||||
v -> SponsorBlockUtils.onVotingClicked(v.getContext()),
|
v -> SponsorBlockUtils.onVotingClicked(v.getContext()),
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
@@ -58,8 +58,9 @@ public class VotingButton {
|
|||||||
if (instance != null) instance.setVisibility(visible, animated);
|
if (instance != null) instance.setVisibility(visible, animated);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldBeShown() {
|
private static boolean isButtonEnabled() {
|
||||||
return Settings.SB_ENABLED.get() && Settings.SB_VOTING_BUTTON.get()
|
return Settings.SB_ENABLED.get() && Settings.SB_VOTING_BUTTON.get()
|
||||||
&& SegmentPlaybackController.videoHasSegments();
|
&& SegmentPlaybackController.videoHasSegments()
|
||||||
|
&& !SegmentPlaybackController.isAdProgressTextVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 5.35.0-dev.1
|
version = 5.37.1-dev.3
|
||||||
|
|||||||
@@ -268,6 +268,10 @@ public final class app/revanced/patches/instagram/hide/navigation/HideNavigation
|
|||||||
public static final fun getHideNavigationButtonsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getHideNavigationButtonsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/instagram/hide/stories/HideStoriesKt {
|
||||||
|
public static final fun getHideStoriesPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/instagram/misc/signature/SignatureCheckPatchKt {
|
public final class app/revanced/patches/instagram/misc/signature/SignatureCheckPatchKt {
|
||||||
public static final fun getSignatureCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSignatureCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -388,14 +392,21 @@ public final class app/revanced/patches/music/misc/gms/GmsCoreSupportPatchKt {
|
|||||||
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/music/misc/spoof/SpoofClientPatchKt {
|
public final class app/revanced/patches/music/misc/spoof/SpoofVideoStreamsKt {
|
||||||
public static final fun getSpoofClientPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSpoofVideoStreamsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/music/misc/spoof/UserAgentClientSpoofPatchKt {
|
public final class app/revanced/patches/music/misc/spoof/UserAgentClientSpoofPatchKt {
|
||||||
public static final fun getUserAgentClientSpoofPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getUserAgentClientSpoofPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/music/playservice/VersionCheckPatchKt {
|
||||||
|
public static final fun getVersionCheckPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||||
|
public static final fun is_7_33_or_greater ()Z
|
||||||
|
public static final fun is_8_11_or_greater ()Z
|
||||||
|
public static final fun is_8_15_or_greater ()Z
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/myexpenses/misc/pro/UnlockProPatchKt {
|
public final class app/revanced/patches/myexpenses/misc/pro/UnlockProPatchKt {
|
||||||
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -512,6 +523,13 @@ public final class app/revanced/patches/reddit/ad/general/HideAdsPatchKt {
|
|||||||
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/reddit/customclients/FixRedgifsApiPatchKt {
|
||||||
|
public static final field CREATE_NEW_CLIENT_METHOD Ljava/lang/String;
|
||||||
|
public static final field INSTALL_NEW_CLIENT_METHOD Ljava/lang/String;
|
||||||
|
public static final fun fixRedgifsApiPatch (Lapp/revanced/patcher/patch/Patch;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
public static synthetic fun fixRedgifsApiPatch$default (Lapp/revanced/patcher/patch/Patch;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/reddit/customclients/FixSLinksPatchKt {
|
public final class app/revanced/patches/reddit/customclients/FixSLinksPatchKt {
|
||||||
public static final field RESOLVE_S_LINK_METHOD Ljava/lang/String;
|
public static final field RESOLVE_S_LINK_METHOD Ljava/lang/String;
|
||||||
public static final field SET_ACCESS_TOKEN_METHOD Ljava/lang/String;
|
public static final field SET_ACCESS_TOKEN_METHOD Ljava/lang/String;
|
||||||
@@ -528,6 +546,14 @@ public final class app/revanced/patches/reddit/customclients/baconreader/api/Spo
|
|||||||
public static final fun getSpoofClientPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSpoofClientPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/reddit/customclients/baconreader/fix/redgifs/FixRedgifsApiPatchKt {
|
||||||
|
public static final fun getFixRedgifsApi ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/reddit/customclients/baconreader/misc/extension/SharedExtensionPatchKt {
|
||||||
|
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/reddit/customclients/boostforreddit/ads/DisableAdsPatchKt {
|
public final class app/revanced/patches/reddit/customclients/boostforreddit/ads/DisableAdsPatchKt {
|
||||||
public static final fun getDisableAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getDisableAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -540,6 +566,10 @@ public final class app/revanced/patches/reddit/customclients/boostforreddit/fix/
|
|||||||
public static final fun getFixAudioMissingInDownloadsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getFixAudioMissingInDownloadsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/reddit/customclients/boostforreddit/fix/redgifs/FixRedgifsApiPatchKt {
|
||||||
|
public static final fun getFixRedgifsApi ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/reddit/customclients/boostforreddit/fix/slink/FixSLinksPatchKt {
|
public final class app/revanced/patches/reddit/customclients/boostforreddit/fix/slink/FixSLinksPatchKt {
|
||||||
public static final field EXTENSION_CLASS_DESCRIPTOR Ljava/lang/String;
|
public static final field EXTENSION_CLASS_DESCRIPTOR Ljava/lang/String;
|
||||||
public static final fun getFixSlinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getFixSlinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
@@ -610,6 +640,10 @@ public final class app/revanced/patches/reddit/customclients/sync/syncforreddit/
|
|||||||
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/reddit/customclients/sync/syncforreddit/fix/redgifs/FixRedgifsApiPatchKt {
|
||||||
|
public static final fun getFixRedgifsApi ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/reddit/customclients/sync/syncforreddit/fix/slink/FixSLinksPatchKt {
|
public final class app/revanced/patches/reddit/customclients/sync/syncforreddit/fix/slink/FixSLinksPatchKt {
|
||||||
public static final field EXTENSION_CLASS_DESCRIPTOR Ljava/lang/String;
|
public static final field EXTENSION_CLASS_DESCRIPTOR Ljava/lang/String;
|
||||||
public static final fun getFixSLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getFixSLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
@@ -956,10 +990,6 @@ public final class app/revanced/patches/spotify/lite/ondemand/OnDemandPatchKt {
|
|||||||
public static final fun getOnDemandPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getOnDemandPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/spotify/misc/UnlockPremiumPatchKt {
|
|
||||||
public static final fun getUnlockPremiumPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/patches/spotify/misc/extension/ExtensionPatchKt {
|
public final class app/revanced/patches/spotify/misc/extension/ExtensionPatchKt {
|
||||||
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -1212,6 +1242,10 @@ public final class app/revanced/patches/twitter/misc/links/SanitizeSharingLinksP
|
|||||||
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/viber/ads/HideAdsPatchKt {
|
||||||
|
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/vsco/misc/pro/UnlockProPatchKt {
|
public final class app/revanced/patches/vsco/misc/pro/UnlockProPatchKt {
|
||||||
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,14 +19,16 @@ val disableAdsPatch = bytecodePatch(
|
|||||||
// SharedPreferences has a debug boolean value with key "disable_ads", which maps to "DebugCategory.DISABLE_ADS".
|
// SharedPreferences has a debug boolean value with key "disable_ads", which maps to "DebugCategory.DISABLE_ADS".
|
||||||
//
|
//
|
||||||
// MonetizationDebugSettings seems to be the most general setting to work fine.
|
// MonetizationDebugSettings seems to be the most general setting to work fine.
|
||||||
initializeMonetizationDebugSettingsFingerprint.method.apply {
|
initializeMonetizationDebugSettingsFingerprint
|
||||||
val insertIndex = initializeMonetizationDebugSettingsFingerprint.patternMatch!!.startIndex
|
.match(monetizationDebugSettingsToStringFingerprint.classDef)
|
||||||
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
.method.apply {
|
||||||
|
val insertIndex = initializeMonetizationDebugSettingsFingerprint.patternMatch!!.startIndex
|
||||||
|
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||||
|
|
||||||
addInstructions(
|
addInstructions(
|
||||||
insertIndex,
|
insertIndex,
|
||||||
"const/4 v$register, 0x1",
|
"const/4 v$register, 0x1",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,11 @@ import com.android.tools.smali.dexlib2.Opcode
|
|||||||
internal val initializeMonetizationDebugSettingsFingerprint = fingerprint {
|
internal val initializeMonetizationDebugSettingsFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||||
returns("V")
|
returns("V")
|
||||||
parameters(
|
// Parameters have not been reliable for fingerprinting between versions.
|
||||||
"Z", // disableAds
|
|
||||||
"Z", // useDebugBilling
|
|
||||||
"Z", // showManageSubscriptions
|
|
||||||
"Z", // alwaysShowSuperAds
|
|
||||||
// matches "Lcom/duolingo/debug/FamilyQuestOverride;" or "Lcom/duolingo/data/debug/monetization/FamilyQuestOverride;"
|
|
||||||
"Lcom/duolingo/",
|
|
||||||
)
|
|
||||||
opcodes(Opcode.IPUT_BOOLEAN)
|
opcodes(Opcode.IPUT_BOOLEAN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val monetizationDebugSettingsToStringFingerprint = fingerprint {
|
||||||
|
strings("MonetizationDebugSettings(") // Partial string match.
|
||||||
|
custom { method, _ -> method.name == "toString" }
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ internal val tabCreateButtonsLoopEndFingerprint = fingerprint {
|
|||||||
Opcode.IPUT_OBJECT,
|
Opcode.IPUT_OBJECT,
|
||||||
// Injection Jump
|
// Injection Jump
|
||||||
Opcode.ADD_INT_LIT8, //Increase Index
|
Opcode.ADD_INT_LIT8, //Increase Index
|
||||||
Opcode.GOTO_16 // Jump to loopStart
|
Opcode.GOTO // Jump to loopStart
|
||||||
// LoopEnd
|
// LoopEnd
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,20 @@ val hideNavigationButtonsPatch = bytecodePatch(
|
|||||||
description = "Hides navigation bar buttons, such as the Reels and Create button.",
|
description = "Hides navigation bar buttons, such as the Reels and Create button.",
|
||||||
use = false
|
use = false
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.instagram.android")
|
compatibleWith("com.instagram.android"("397.1.0.52.81"))
|
||||||
|
|
||||||
val hideReels by booleanOption(
|
val hideReels by booleanOption(
|
||||||
key = "hideReels",
|
key = "hideReels",
|
||||||
default = true,
|
default = true,
|
||||||
title = "Hide Reels"
|
title = "Hide Reels",
|
||||||
|
description = "Permanently hides the Reels button."
|
||||||
)
|
)
|
||||||
|
|
||||||
val hideCreate by booleanOption(
|
val hideCreate by booleanOption(
|
||||||
key = "hideCreate",
|
key = "hideCreate",
|
||||||
default = true,
|
default = true,
|
||||||
title = "Hide Create"
|
title = "Hide Create",
|
||||||
|
description = "Permanently hides the Create button."
|
||||||
)
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.patches.instagram.hide.stories
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
|
||||||
|
internal val getOrCreateAvatarViewFingerprint = fingerprint {
|
||||||
|
parameters()
|
||||||
|
returns("L")
|
||||||
|
custom { method, classDef ->
|
||||||
|
classDef.type == "Lcom/instagram/reels/ui/views/reelavatar/RecyclerReelAvatarView;"
|
||||||
|
}
|
||||||
|
opcodes(
|
||||||
|
Opcode.INVOKE_VIRTUAL,
|
||||||
|
Opcode.IPUT_OBJECT,
|
||||||
|
Opcode.INVOKE_VIRTUAL // Add View (Story)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package app.revanced.patches.instagram.hide.stories
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val hideStoriesPatch = bytecodePatch(
|
||||||
|
name = "Hide Stories from Home",
|
||||||
|
description = "Hides Stories from the main page, by removing the buttons.",
|
||||||
|
use = false
|
||||||
|
) {
|
||||||
|
compatibleWith("com.instagram.android")
|
||||||
|
|
||||||
|
execute {
|
||||||
|
val addStoryMethod = getOrCreateAvatarViewFingerprint.method // Creates Story
|
||||||
|
val addStoryEndIndex = getOrCreateAvatarViewFingerprint.patternMatch!!.endIndex
|
||||||
|
|
||||||
|
// Remove addView of Story.
|
||||||
|
addStoryMethod.removeInstruction(addStoryEndIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,11 @@ val hideVideoAdsPatch = bytecodePatch(
|
|||||||
name = "Hide music video ads",
|
name = "Hide music video ads",
|
||||||
description = "Hides ads that appear while listening to or streaming music videos, podcasts, or songs.",
|
description = "Hides ads that appear while listening to or streaming music videos, podcasts, or songs.",
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.google.android.apps.youtube.music")
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
navigate(showVideoAdsParentFingerprint.originalMethod)
|
navigate(showVideoAdsParentFingerprint.originalMethod)
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ val enableExclusiveAudioPlaybackPatch = bytecodePatch(
|
|||||||
name = "Enable exclusive audio playback",
|
name = "Enable exclusive audio playback",
|
||||||
description = "Enables the option to play audio without video.",
|
description = "Enables the option to play audio without video.",
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.google.android.apps.youtube.music")
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
allowExclusiveAudioPlaybackFingerprint.method.returnEarly(true)
|
allowExclusiveAudioPlaybackFingerprint.method.returnEarly(true)
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ val permanentRepeatPatch = bytecodePatch(
|
|||||||
description = "Permanently remember your repeating preference even if the playlist ends or another track is played.",
|
description = "Permanently remember your repeating preference even if the playlist ends or another track is played.",
|
||||||
use = false,
|
use = false,
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.google.android.apps.youtube.music")
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
val startIndex = repeatTrackFingerprint.patternMatch!!.endIndex
|
val startIndex = repeatTrackFingerprint.patternMatch!!.endIndex
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ val permanentShufflePatch = bytecodePatch(
|
|||||||
description = "Permanently remember your shuffle preference " +
|
description = "Permanently remember your shuffle preference " +
|
||||||
"even if the playlist ends or another track is played."
|
"even if the playlist ends or another track is played."
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.google.android.apps.youtube.music")
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
disableShuffleFingerprint.method.addInstruction(0, "return-void")
|
disableShuffleFingerprint.method.addInstruction(0, "return-void")
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ val hideCategoryBar = bytecodePatch(
|
|||||||
description = "Hides the category bar at the top of the homepage.",
|
description = "Hides the category bar at the top of the homepage.",
|
||||||
use = false,
|
use = false,
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.google.android.apps.youtube.music")
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
constructCategoryBarFingerprint.method.apply {
|
constructCategoryBarFingerprint.method.apply {
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ val hideGetPremiumPatch = bytecodePatch(
|
|||||||
name = "Hide 'Get Music Premium' label",
|
name = "Hide 'Get Music Premium' label",
|
||||||
description = "Hides the \"Get Music Premium\" label from the account menu and settings.",
|
description = "Hides the \"Get Music Premium\" label from the account menu and settings.",
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.google.android.apps.youtube.music")
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
hideGetPremiumFingerprint.method.apply {
|
hideGetPremiumFingerprint.method.apply {
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ val removeUpgradeButtonPatch = bytecodePatch(
|
|||||||
name = "Remove upgrade button",
|
name = "Remove upgrade button",
|
||||||
description = "Removes the upgrade tab from the pivot bar.",
|
description = "Removes the upgrade tab from the pivot bar.",
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.google.android.apps.youtube.music")
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
pivotBarConstructorFingerprint.method.apply {
|
pivotBarConstructorFingerprint.method.apply {
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ val bypassCertificateChecksPatch = bytecodePatch(
|
|||||||
name = "Bypass certificate checks",
|
name = "Bypass certificate checks",
|
||||||
description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.",
|
description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.",
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.google.android.apps.youtube.music")
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
checkCertificateFingerprint.method.returnEarly(true)
|
checkCertificateFingerprint.method.returnEarly(true)
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ val backgroundPlaybackPatch = bytecodePatch(
|
|||||||
name = "Remove background playback restrictions",
|
name = "Remove background playback restrictions",
|
||||||
description = "Removes restrictions on background playback, including playing kids videos in the background.",
|
description = "Removes restrictions on background playback, including playing kids videos in the background.",
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.google.android.apps.youtube.music")
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
kidsBackgroundPlaybackPolicyControllerFingerprint.method.addInstruction(
|
kidsBackgroundPlaybackPolicyControllerFingerprint.method.addInstruction(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import app.revanced.patcher.patch.Option
|
|||||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
||||||
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
||||||
import app.revanced.patches.music.misc.spoof.spoofClientPatch
|
import app.revanced.patches.music.misc.spoof.spoofVideoStreamsPatch
|
||||||
import app.revanced.patches.shared.castContextFetchFingerprint
|
import app.revanced.patches.shared.castContextFetchFingerprint
|
||||||
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
||||||
import app.revanced.patches.shared.primeMethodFingerprint
|
import app.revanced.patches.shared.primeMethodFingerprint
|
||||||
@@ -21,7 +21,7 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
|
|||||||
extensionPatch = sharedExtensionPatch,
|
extensionPatch = sharedExtensionPatch,
|
||||||
gmsCoreSupportResourcePatchFactory = ::gmsCoreSupportResourcePatch,
|
gmsCoreSupportResourcePatchFactory = ::gmsCoreSupportResourcePatch,
|
||||||
) {
|
) {
|
||||||
dependsOn(spoofClientPatch)
|
dependsOn(spoofVideoStreamsPatch)
|
||||||
|
|
||||||
compatibleWith(MUSIC_PACKAGE_NAME)
|
compatibleWith(MUSIC_PACKAGE_NAME)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
package app.revanced.patches.music.misc.spoof
|
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
|
|
||||||
internal val playerRequestConstructorFingerprint = fingerprint {
|
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
|
||||||
strings("player")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Matches using the class found in [playerRequestConstructorFingerprint].
|
|
||||||
*/
|
|
||||||
internal val createPlayerRequestBodyFingerprint = fingerprint {
|
|
||||||
parameters("L")
|
|
||||||
returns("V")
|
|
||||||
opcodes(
|
|
||||||
Opcode.CHECK_CAST,
|
|
||||||
Opcode.IGET,
|
|
||||||
Opcode.AND_INT_LIT16,
|
|
||||||
)
|
|
||||||
strings("ms")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to get a reference to other clientInfo fields.
|
|
||||||
*/
|
|
||||||
internal val setClientInfoFieldsFingerprint = fingerprint {
|
|
||||||
returns("L")
|
|
||||||
strings("Google Inc.")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to get a reference to the clientInfo and clientInfo.clientVersion field.
|
|
||||||
*/
|
|
||||||
internal val setClientInfoClientVersionFingerprint = fingerprint {
|
|
||||||
strings("10.29")
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package app.revanced.patches.music.misc.spoof
|
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
|
||||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
|
||||||
import app.revanced.util.getReference
|
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
|
||||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
|
||||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
|
||||||
|
|
||||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
|
||||||
"Lapp/revanced/extension/music/spoof/SpoofClientPatch;"
|
|
||||||
|
|
||||||
// TODO: Replace this patch with spoofVideoStreamsPatch once possible.
|
|
||||||
val spoofClientPatch = bytecodePatch(
|
|
||||||
name = "Spoof client",
|
|
||||||
description = "Spoofs the client to fix playback.",
|
|
||||||
) {
|
|
||||||
compatibleWith("com.google.android.apps.youtube.music")
|
|
||||||
|
|
||||||
dependsOn(
|
|
||||||
sharedExtensionPatch,
|
|
||||||
// TODO: Add settingsPatch
|
|
||||||
userAgentClientSpoofPatch,
|
|
||||||
)
|
|
||||||
|
|
||||||
execute {
|
|
||||||
val playerRequestClass = playerRequestConstructorFingerprint.classDef
|
|
||||||
|
|
||||||
val createPlayerRequestBodyMatch = createPlayerRequestBodyFingerprint.match(playerRequestClass)
|
|
||||||
|
|
||||||
val clientInfoContainerClass = createPlayerRequestBodyMatch.method
|
|
||||||
.getInstruction(createPlayerRequestBodyMatch.patternMatch!!.startIndex)
|
|
||||||
.getReference<TypeReference>()!!.type
|
|
||||||
|
|
||||||
val clientInfoField = setClientInfoClientVersionFingerprint.method.instructions.first {
|
|
||||||
it.opcode == Opcode.IPUT_OBJECT && it.getReference<FieldReference>()!!.definingClass == clientInfoContainerClass
|
|
||||||
}.getReference<FieldReference>()!!
|
|
||||||
|
|
||||||
val setClientInfoFieldInstructions = setClientInfoFieldsFingerprint.method.instructions.filter {
|
|
||||||
(it.opcode == Opcode.IPUT_OBJECT || it.opcode == Opcode.IPUT) &&
|
|
||||||
it.getReference<FieldReference>()!!.definingClass == clientInfoField.type
|
|
||||||
}.map { it.getReference<FieldReference>()!! }
|
|
||||||
|
|
||||||
// Offsets are known for the fields in the clientInfo object.
|
|
||||||
val clientIdField = setClientInfoFieldInstructions[0]
|
|
||||||
val clientModelField = setClientInfoFieldInstructions[5]
|
|
||||||
val osVersionField = setClientInfoFieldInstructions[7]
|
|
||||||
val clientVersionField = setClientInfoClientVersionFingerprint.method
|
|
||||||
.getInstruction(setClientInfoClientVersionFingerprint.stringMatches!!.first().index + 1)
|
|
||||||
.getReference<FieldReference>()
|
|
||||||
|
|
||||||
// Helper method to spoof the client info.
|
|
||||||
val spoofClientInfoMethod = ImmutableMethod(
|
|
||||||
playerRequestClass.type,
|
|
||||||
"spoofClientInfo",
|
|
||||||
listOf(ImmutableMethodParameter(clientInfoContainerClass, null, null)),
|
|
||||||
"V",
|
|
||||||
AccessFlags.PRIVATE.value or AccessFlags.STATIC.value,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
MutableMethodImplementation(3),
|
|
||||||
).toMutable().also(playerRequestClass.methods::add).apply {
|
|
||||||
addInstructions(
|
|
||||||
"""
|
|
||||||
iget-object v0, p0, $clientInfoField
|
|
||||||
|
|
||||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientId()I
|
|
||||||
move-result v1
|
|
||||||
iput v1, v0, $clientIdField
|
|
||||||
|
|
||||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientModel()Ljava/lang/String;
|
|
||||||
move-result-object v1
|
|
||||||
iput-object v1, v0, $clientModelField
|
|
||||||
|
|
||||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientVersion()Ljava/lang/String;
|
|
||||||
move-result-object v1
|
|
||||||
iput-object v1, v0, $clientVersionField
|
|
||||||
|
|
||||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getOsVersion()Ljava/lang/String;
|
|
||||||
move-result-object v1
|
|
||||||
iput-object v1, v0, $osVersionField
|
|
||||||
|
|
||||||
return-void
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
createPlayerRequestBodyMatch.method.apply {
|
|
||||||
val checkCastIndex = createPlayerRequestBodyMatch.patternMatch!!.startIndex
|
|
||||||
val clientInfoContainerRegister = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA
|
|
||||||
|
|
||||||
addInstruction(checkCastIndex + 1, "invoke-static {v$clientInfoContainerRegister}, $spoofClientInfoMethod")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package app.revanced.patches.music.misc.spoof
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
|
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.music.misc.gms.musicActivityOnCreateFingerprint
|
||||||
|
import app.revanced.patches.music.playservice.is_7_33_or_greater
|
||||||
|
import app.revanced.patches.music.playservice.is_8_11_or_greater
|
||||||
|
import app.revanced.patches.music.playservice.is_8_15_or_greater
|
||||||
|
import app.revanced.patches.music.playservice.versionCheckPatch
|
||||||
|
import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/spoof/SpoofVideoStreamsPatch;"
|
||||||
|
|
||||||
|
val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
|
||||||
|
fixMediaFetchHotConfigChanges = { true },
|
||||||
|
fixMediaFetchHotConfigAlternativeChanges = { is_8_11_or_greater && !is_8_15_or_greater },
|
||||||
|
fixParsePlaybackResponseFeatureFlag = { is_7_33_or_greater },
|
||||||
|
block = {
|
||||||
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
dependsOn(sharedExtensionPatch, versionCheckPatch, userAgentClientSpoofPatch)
|
||||||
|
},
|
||||||
|
executeBlock = {
|
||||||
|
musicActivityOnCreateFingerprint.method.addInstruction(
|
||||||
|
1, // Must use 1 index so context is set by extension patch.
|
||||||
|
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setClientOrderToUse()V"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
@file:Suppress("ktlint:standard:property-naming")
|
||||||
|
|
||||||
|
package app.revanced.patches.music.playservice
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
|
import app.revanced.util.findPlayStoreServicesVersion
|
||||||
|
|
||||||
|
var is_7_33_or_greater = false
|
||||||
|
private set
|
||||||
|
var is_8_11_or_greater = false
|
||||||
|
private set
|
||||||
|
var is_8_15_or_greater = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
val versionCheckPatch = resourcePatch(
|
||||||
|
description = "Uses the Play Store service version to find the major/minor version of the YouTube Music target app.",
|
||||||
|
) {
|
||||||
|
execute {
|
||||||
|
// The app version is missing from the decompiled manifest,
|
||||||
|
// so instead use the Google Play services version and compare against specific releases.
|
||||||
|
val playStoreServicesVersion = findPlayStoreServicesVersion()
|
||||||
|
|
||||||
|
// All bug fix releases always seem to use the same play store version as the minor version.
|
||||||
|
is_7_33_or_greater = 245199000 <= playStoreServicesVersion
|
||||||
|
is_8_11_or_greater = 251199000 <= playStoreServicesVersion
|
||||||
|
is_8_15_or_greater = 251530000 <= playStoreServicesVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.patches.reddit.customclients
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
|
||||||
|
const val INSTALL_NEW_CLIENT_METHOD = "install(Lokhttp3/OkHttpClient${'$'}Builder;)Lokhttp3/OkHttpClient;"
|
||||||
|
const val CREATE_NEW_CLIENT_METHOD = "createClient()Lokhttp3/OkHttpClient;"
|
||||||
|
|
||||||
|
fun fixRedgifsApiPatch(
|
||||||
|
extensionPatch: Patch<*>,
|
||||||
|
block: BytecodePatchBuilder.() -> Unit = {},
|
||||||
|
) = bytecodePatch(name = "Fix Redgifs API") {
|
||||||
|
dependsOn(extensionPatch)
|
||||||
|
|
||||||
|
block()
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package app.revanced.patches.reddit.customclients.baconreader.fix.redgifs
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
|
||||||
|
|
||||||
|
internal val getOkHttpClientFingerprint = fingerprint {
|
||||||
|
returns("Lokhttp3/OkHttpClient;")
|
||||||
|
parameters()
|
||||||
|
custom { method, classDef ->
|
||||||
|
classDef.type == "Lcom/onelouder/baconreader/media/gfycat/RedGifsManager;" && method.name == "getOkhttpClient"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package app.revanced.patches.reddit.customclients.baconreader.fix.redgifs
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
|
import app.revanced.patches.reddit.customclients.INSTALL_NEW_CLIENT_METHOD
|
||||||
|
import app.revanced.patches.reddit.customclients.baconreader.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.reddit.customclients.fixRedgifsApiPatch
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||||
|
|
||||||
|
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/baconreader/FixRedgifsApiPatch;"
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val fixRedgifsApi = fixRedgifsApiPatch(
|
||||||
|
extensionPatch = sharedExtensionPatch
|
||||||
|
) {
|
||||||
|
compatibleWith(
|
||||||
|
"com.onelouder.baconreader",
|
||||||
|
"com.onelouder.baconreader.premium",
|
||||||
|
)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
// region Patch Redgifs OkHttp3 client.
|
||||||
|
|
||||||
|
getOkHttpClientFingerprint.method.apply {
|
||||||
|
// Remove conflicting OkHttp interceptors.
|
||||||
|
val originalInterceptorInstallIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
opcode == Opcode.NEW_INSTANCE && getReference<TypeReference>()?.type == "Lcom/onelouder/baconreader/media/gfycat/RedGifsManager\$HeaderInterceptor;"
|
||||||
|
}
|
||||||
|
removeInstructions(originalInterceptorInstallIndex, 5)
|
||||||
|
|
||||||
|
val index = indexOfFirstInstructionOrThrow {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.name == "build" && reference.definingClass == "Lokhttp3/OkHttpClient\$Builder;"
|
||||||
|
}
|
||||||
|
val register = getInstruction<FiveRegisterInstruction>(index).registerC
|
||||||
|
replaceInstruction(
|
||||||
|
index,
|
||||||
|
"""
|
||||||
|
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->$INSTALL_NEW_CLIENT_METHOD
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package app.revanced.patches.reddit.customclients.baconreader.misc.extension
|
||||||
|
|
||||||
|
import app.revanced.patches.reddit.customclients.baconreader.misc.extension.hooks.initHook
|
||||||
|
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||||
|
|
||||||
|
val sharedExtensionPatch = sharedExtensionPatch("baconreader", initHook)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.patches.reddit.customclients.baconreader.misc.extension.hooks
|
||||||
|
|
||||||
|
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||||
|
|
||||||
|
internal val initHook = extensionHook {
|
||||||
|
custom { method, _ ->
|
||||||
|
method.definingClass == "Lcom/onelouder/baconreader/BaconReader;" && method.name == "onCreate"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package app.revanced.patches.reddit.customclients.boostforreddit.fix.redgifs
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
internal val createOkHttpClientFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PRIVATE)
|
||||||
|
opcodes(
|
||||||
|
Opcode.NEW_INSTANCE,
|
||||||
|
Opcode.INVOKE_DIRECT,
|
||||||
|
Opcode.NEW_INSTANCE,
|
||||||
|
Opcode.INVOKE_DIRECT,
|
||||||
|
Opcode.NEW_INSTANCE,
|
||||||
|
Opcode.INVOKE_DIRECT,
|
||||||
|
Opcode.INVOKE_VIRTUAL,
|
||||||
|
Opcode.MOVE_RESULT_OBJECT
|
||||||
|
)
|
||||||
|
custom { _, classDef -> classDef.sourceFile == "RedGifsAPIv2.java" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package app.revanced.patches.reddit.customclients.boostforreddit.fix.redgifs
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
|
import app.revanced.patches.reddit.customclients.CREATE_NEW_CLIENT_METHOD
|
||||||
|
import app.revanced.patches.reddit.customclients.boostforreddit.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.reddit.customclients.fixRedgifsApiPatch
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/boostforreddit/FixRedgifsApiPatch;"
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val fixRedgifsApi = fixRedgifsApiPatch(
|
||||||
|
extensionPatch = sharedExtensionPatch
|
||||||
|
) {
|
||||||
|
compatibleWith("com.rubenmayayo.reddit")
|
||||||
|
|
||||||
|
execute {
|
||||||
|
// region Patch Redgifs OkHttp3 client.
|
||||||
|
|
||||||
|
createOkHttpClientFingerprint.method.apply {
|
||||||
|
val index = indexOfFirstInstructionOrThrow {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.name == "build" && reference.definingClass == "Lokhttp3/OkHttpClient\$Builder;"
|
||||||
|
}
|
||||||
|
replaceInstruction(
|
||||||
|
index,
|
||||||
|
"""
|
||||||
|
invoke-static { }, ${EXTENSION_CLASS_DESCRIPTOR}->$CREATE_NEW_CLIENT_METHOD
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package app.revanced.patches.reddit.customclients.sync.syncforreddit.fix.redgifs
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
|
import app.revanced.util.writeRegister
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction11n
|
||||||
|
|
||||||
|
|
||||||
|
internal val createOkHttpClientFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
|
||||||
|
returns("V")
|
||||||
|
parameters()
|
||||||
|
custom { method, classDef ->
|
||||||
|
// There are four functions (each creating a client) defined in this file with very similar fingerprints.
|
||||||
|
// We're looking for the one that only creates one object (the builder) and sets client options true
|
||||||
|
// (thus never reloading the register with a 0).
|
||||||
|
classDef.sourceFile == "OkHttpHelper.java" &&
|
||||||
|
method.instructions.count { it.opcode == Opcode.NEW_INSTANCE } == 1 &&
|
||||||
|
method.indexOfFirstInstruction {
|
||||||
|
opcode == Opcode.CONST_4 && writeRegister == 1 && (this as Instruction11n).narrowLiteral == 0
|
||||||
|
} == -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val getDefaultUserAgentFingerprint = fingerprint {
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "getDefaultUserAgent" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val getOriginalUserAgentFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||||
|
returns("Ljava/lang/String;")
|
||||||
|
parameters()
|
||||||
|
custom { _, classDef -> classDef.sourceFile == "AccountSingleton.java" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package app.revanced.patches.reddit.customclients.sync.syncforreddit.fix.redgifs
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
|
import app.revanced.patches.reddit.customclients.INSTALL_NEW_CLIENT_METHOD
|
||||||
|
import app.revanced.patches.reddit.customclients.fixRedgifsApiPatch
|
||||||
|
import app.revanced.patches.reddit.customclients.sync.syncforreddit.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
|
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/syncforreddit/FixRedgifsApiPatch;"
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val fixRedgifsApi = fixRedgifsApiPatch(
|
||||||
|
extensionPatch = sharedExtensionPatch
|
||||||
|
) {
|
||||||
|
compatibleWith(
|
||||||
|
"com.laurencedawson.reddit_sync",
|
||||||
|
"com.laurencedawson.reddit_sync.pro",
|
||||||
|
"com.laurencedawson.reddit_sync.dev",
|
||||||
|
)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
// region Patch Redgifs OkHttp3 client.
|
||||||
|
|
||||||
|
createOkHttpClientFingerprint.method.apply {
|
||||||
|
val index = indexOfFirstInstructionOrThrow {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.name == "build" && reference.definingClass == "Lokhttp3/OkHttpClient\$Builder;"
|
||||||
|
}
|
||||||
|
val register = getInstruction<FiveRegisterInstruction>(index).registerC
|
||||||
|
replaceInstruction(
|
||||||
|
index,
|
||||||
|
"""
|
||||||
|
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->$INSTALL_NEW_CLIENT_METHOD
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultUserAgentFingerprint.method.apply {
|
||||||
|
addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-static { }, ${getOriginalUserAgentFingerprint.method}
|
||||||
|
move-result-object v0
|
||||||
|
return-object v0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package app.revanced.patches.shared.misc.spoof
|
package app.revanced.patches.shared.misc.spoof
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
import app.revanced.util.literal
|
import app.revanced.util.literal
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
internal val buildInitPlaybackRequestFingerprint = fingerprint {
|
internal val buildInitPlaybackRequestFingerprint = fingerprint {
|
||||||
returns("Lorg/chromium/net/UrlRequest\$Builder;")
|
returns("Lorg/chromium/net/UrlRequest\$Builder;")
|
||||||
@@ -35,8 +38,15 @@ internal val buildPlayerRequestURIFingerprint = fingerprint {
|
|||||||
|
|
||||||
internal val buildRequestFingerprint = fingerprint {
|
internal val buildRequestFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||||
returns("Lorg/chromium/net/UrlRequest;")
|
returns("Lorg/chromium/net/UrlRequest") // UrlRequest; or UrlRequest$Builder;
|
||||||
custom { methodDef, _ ->
|
custom { methodDef, _ ->
|
||||||
|
if (methodDef.indexOfFirstInstruction {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.name == "newUrlRequestBuilder"
|
||||||
|
} < 0) {
|
||||||
|
return@custom false
|
||||||
|
}
|
||||||
|
|
||||||
// Different targets have slightly different parameters
|
// Different targets have slightly different parameters
|
||||||
|
|
||||||
// Earlier targets have parameters:
|
// Earlier targets have parameters:
|
||||||
@@ -58,12 +68,22 @@ internal val buildRequestFingerprint = fingerprint {
|
|||||||
// Lorg/chromium/net/UrlRequest\$Callback;
|
// Lorg/chromium/net/UrlRequest\$Callback;
|
||||||
// L
|
// L
|
||||||
|
|
||||||
|
// 20.16+ uses a refactored and extracted method:
|
||||||
|
// L
|
||||||
|
// Ljava/util/Map;
|
||||||
|
// [B
|
||||||
|
// L
|
||||||
|
// Lorg/chromium/net/UrlRequest$Callback;
|
||||||
|
// L
|
||||||
|
|
||||||
val parameterTypes = methodDef.parameterTypes
|
val parameterTypes = methodDef.parameterTypes
|
||||||
(parameterTypes.size == 7 || parameterTypes.size == 8) &&
|
val parameterTypesSize = parameterTypes.size
|
||||||
parameterTypes[1] == "Ljava/util/Map;" // URL headers.
|
(parameterTypesSize == 6 || parameterTypesSize == 7 || parameterTypesSize == 8) &&
|
||||||
|
parameterTypes[1] == "Ljava/util/Map;" // URL headers.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal val protobufClassParseByteBufferFingerprint = fingerprint {
|
internal val protobufClassParseByteBufferFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PROTECTED, AccessFlags.STATIC)
|
accessFlags(AccessFlags.PROTECTED, AccessFlags.STATIC)
|
||||||
returns("L")
|
returns("L")
|
||||||
@@ -148,7 +168,8 @@ internal val mediaFetchHotConfigFingerprint = fingerprint {
|
|||||||
literal { MEDIA_FETCH_HOT_CONFIG_FEATURE_FLAG }
|
literal { MEDIA_FETCH_HOT_CONFIG_FEATURE_FLAG }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 20.10+
|
// YT 20.10+, YT Music 8.11 - 8.14.
|
||||||
|
// Flag is missing in YT Music 8.15+, and it is not known if a replacement flag/feature exists.
|
||||||
internal const val MEDIA_FETCH_HOT_CONFIG_ALTERNATIVE_FEATURE_FLAG = 45683169L
|
internal const val MEDIA_FETCH_HOT_CONFIG_ALTERNATIVE_FEATURE_FLAG = 45683169L
|
||||||
|
|
||||||
internal val mediaFetchHotConfigAlternativeFingerprint = fingerprint {
|
internal val mediaFetchHotConfigAlternativeFingerprint = fingerprint {
|
||||||
@@ -162,7 +183,6 @@ internal val mediaFetchHotConfigAlternativeFingerprint = fingerprint {
|
|||||||
internal const val PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG = 45665455L
|
internal const val PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG = 45665455L
|
||||||
|
|
||||||
internal val playbackStartDescriptorFeatureFlagFingerprint = fingerprint {
|
internal val playbackStartDescriptorFeatureFlagFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
|
||||||
parameters()
|
parameters()
|
||||||
returns("Z")
|
returns("Z")
|
||||||
literal { PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG }
|
literal { PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG }
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import app.revanced.patcher.patch.BytecodePatchContext
|
|||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
|
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.util.findFreeRegister
|
import app.revanced.util.findFreeRegister
|
||||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
@@ -53,9 +54,8 @@ fun spoofVideoStreamsPatch(
|
|||||||
|
|
||||||
// region Block /initplayback requests to fall back to /get_watch requests.
|
// region Block /initplayback requests to fall back to /get_watch requests.
|
||||||
|
|
||||||
val moveUriStringIndex = buildInitPlaybackRequestFingerprint.patternMatch!!.startIndex
|
|
||||||
|
|
||||||
buildInitPlaybackRequestFingerprint.method.apply {
|
buildInitPlaybackRequestFingerprint.method.apply {
|
||||||
|
val moveUriStringIndex = buildInitPlaybackRequestFingerprint.patternMatch!!.startIndex
|
||||||
val targetRegister = getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
|
val targetRegister = getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
|
||||||
|
|
||||||
addInstructions(
|
addInstructions(
|
||||||
@@ -63,7 +63,7 @@ fun spoofVideoStreamsPatch(
|
|||||||
"""
|
"""
|
||||||
invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
|
invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
|
||||||
move-result-object v$targetRegister
|
move-result-object v$targetRegister
|
||||||
""",
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,9 +71,8 @@ fun spoofVideoStreamsPatch(
|
|||||||
|
|
||||||
// region Block /get_watch requests to fall back to /player requests.
|
// region Block /get_watch requests to fall back to /player requests.
|
||||||
|
|
||||||
val invokeToStringIndex = buildPlayerRequestURIFingerprint.patternMatch!!.startIndex
|
|
||||||
|
|
||||||
buildPlayerRequestURIFingerprint.method.apply {
|
buildPlayerRequestURIFingerprint.method.apply {
|
||||||
|
val invokeToStringIndex = buildPlayerRequestURIFingerprint.patternMatch!!.startIndex
|
||||||
val uriRegister = getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
|
val uriRegister = getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
|
||||||
|
|
||||||
addInstructions(
|
addInstructions(
|
||||||
@@ -81,7 +80,7 @@ fun spoofVideoStreamsPatch(
|
|||||||
"""
|
"""
|
||||||
invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
||||||
move-result-object v$uriRegister
|
move-result-object v$uriRegister
|
||||||
""",
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,9 +177,9 @@ fun spoofVideoStreamsPatch(
|
|||||||
|
|
||||||
:disabled
|
:disabled
|
||||||
return-void
|
return-void
|
||||||
""",
|
"""
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,17 +198,17 @@ fun spoofVideoStreamsPatch(
|
|||||||
addInstructions(
|
addInstructions(
|
||||||
targetIndex,
|
targetIndex,
|
||||||
"""
|
"""
|
||||||
# Field a: Stream uri.
|
# Field a: Stream uri.
|
||||||
# Field c: Http method.
|
# Field c: Http method.
|
||||||
# Field d: Post data.
|
# Field d: Post data.
|
||||||
move-object v0, p0 # method has over 15 registers and must copy p0 to a lower register.
|
move-object v0, p0 # method has over 15 registers and must copy p0 to a lower register.
|
||||||
iget-object v1, v0, $definingClass->a:Landroid/net/Uri;
|
iget-object v1, v0, $definingClass->a:Landroid/net/Uri;
|
||||||
iget v2, v0, $definingClass->c:I
|
iget v2, v0, $definingClass->c:I
|
||||||
iget-object v3, v0, $definingClass->d:[B
|
iget-object v3, v0, $definingClass->d:[B
|
||||||
invoke-static { v1, v2, v3 }, $EXTENSION_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B
|
invoke-static { v1, v2, v3 }, $EXTENSION_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B
|
||||||
move-result-object v1
|
move-result-object v1
|
||||||
iput-object v1, v0, $definingClass->d:[B
|
iput-object v1, v0, $definingClass->d:[B
|
||||||
""",
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -187,11 +187,16 @@ val customThemePatch = resourcePatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Login screen gradient.
|
// Login screen gradient.
|
||||||
document("res/drawable/start_screen_gradient.xml").use { document ->
|
try {
|
||||||
val gradientNode = document.getElementsByTagName("gradient").item(0) as Element
|
document("res/drawable/start_screen_gradient.xml").use { document ->
|
||||||
|
val gradientNode = document.getElementsByTagName("gradient").item(0) as Element
|
||||||
|
|
||||||
gradientNode.setAttribute("android:startColor", "@color/gray_7")
|
gradientNode.setAttribute("android:startColor", "@color/gray_7")
|
||||||
gradientNode.setAttribute("android:endColor", "@color/gray_7")
|
gradientNode.setAttribute("android:endColor", "@color/gray_7")
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
// Fails for 9.0.66+
|
||||||
|
// printWarn("Failed to locate start_screen_gradient.xml, skipping modification.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
package app.revanced.patches.spotify.misc
|
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
|
||||||
import app.revanced.patcher.patch.BytecodePatchContext
|
|
||||||
import app.revanced.util.getReference
|
|
||||||
import app.revanced.util.indexOfFirstInstruction
|
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
|
||||||
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
internal val accountAttributeFingerprint get() = fingerprint {
|
|
||||||
custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/AccountAttribute;" }
|
|
||||||
}
|
|
||||||
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
internal val productStateProtoGetMapFingerprint get() = fingerprint {
|
|
||||||
returns("Ljava/util/Map;")
|
|
||||||
custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/ProductStateProto;" }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val buildQueryParametersFingerprint = fingerprint {
|
|
||||||
strings("trackRows", "device_type:tablet")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val contextMenuViewModelClassFingerprint = fingerprint {
|
|
||||||
strings("ContextMenuViewModel(header=")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used in versions older than "9.0.60.128".
|
|
||||||
*/
|
|
||||||
internal val oldContextMenuViewModelAddItemFingerprint = fingerprint {
|
|
||||||
parameters("L")
|
|
||||||
returns("V")
|
|
||||||
custom { method, _ ->
|
|
||||||
method.indexOfFirstInstruction {
|
|
||||||
getReference<MethodReference>()?.name == "add"
|
|
||||||
} >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val contextMenuViewModelConstructorFingerprint = fingerprint {
|
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to find the interface name of a context menu item.
|
|
||||||
*/
|
|
||||||
internal val removeItemFromPlaylistContextMenuItemClassFingerprint = fingerprint {
|
|
||||||
strings("spotify:playlist:", "REMOVE_TRACK")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal const val CONTEXT_MENU_ITEM_CLASS_DESCRIPTOR_PLACEHOLDER = "Lapp/revanced/ContextMenuItemPlaceholder;"
|
|
||||||
internal val extensionFilterContextMenuItemsFingerprint = fingerprint {
|
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
|
||||||
returns("Ljava/util/List;")
|
|
||||||
parameters("Ljava/util/List;")
|
|
||||||
custom { method, classDef ->
|
|
||||||
method.name == "filterContextMenuItems" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val getViewModelFingerprint = fingerprint {
|
|
||||||
custom { method, _ -> method.name == "getViewModel" }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val contextFromJsonFingerprint = fingerprint {
|
|
||||||
opcodes(
|
|
||||||
Opcode.INVOKE_STATIC,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
Opcode.INVOKE_VIRTUAL,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
Opcode.INVOKE_STATIC
|
|
||||||
)
|
|
||||||
custom { method, classDef ->
|
|
||||||
method.name == "fromJson" &&
|
|
||||||
classDef.type.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val readPlayerOptionOverridesFingerprint = fingerprint {
|
|
||||||
custom { method, classDef ->
|
|
||||||
method.name == "readPlayerOptionOverrides" &&
|
|
||||||
classDef.type.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val protobufListsFingerprint = fingerprint {
|
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
|
||||||
custom { method, _ -> method.name == "emptyProtobufList" }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val abstractProtobufListEnsureIsMutableFingerprint = fingerprint {
|
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
|
||||||
parameters()
|
|
||||||
returns("V")
|
|
||||||
custom { method, _ ->
|
|
||||||
method.indexOfFirstInstruction {
|
|
||||||
getReference<TypeReference>()?.type == "Ljava/lang/UnsupportedOperationException;"
|
|
||||||
} >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun structureGetSectionsFingerprint(className: String) = fingerprint {
|
|
||||||
custom { method, classDef ->
|
|
||||||
classDef.type.endsWith(className) && method.indexOfFirstInstruction {
|
|
||||||
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.name == "sections_"
|
|
||||||
} >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val homeSectionFingerprint = fingerprint {
|
|
||||||
custom { _, classDef -> classDef.type.endsWith("homeapi/proto/Section;") }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val homeStructureGetSectionsFingerprint =
|
|
||||||
structureGetSectionsFingerprint("homeapi/proto/HomeStructure;")
|
|
||||||
|
|
||||||
internal val browseSectionFingerprint = fingerprint {
|
|
||||||
custom { _, classDef-> classDef.type.endsWith("browsita/v1/resolved/Section;") }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val browseStructureGetSectionsFingerprint =
|
|
||||||
structureGetSectionsFingerprint("browsita/v1/resolved/BrowseStructure;")
|
|
||||||
|
|
||||||
internal fun reactivexFunctionApplyWithClassInitFingerprint(className: String) = fingerprint {
|
|
||||||
returns("Ljava/lang/Object;")
|
|
||||||
parameters("Ljava/lang/Object;")
|
|
||||||
custom { method, _ ->
|
|
||||||
method.name == "apply" && method.indexOfFirstInstruction {
|
|
||||||
opcode == Opcode.NEW_INSTANCE && getReference<TypeReference>()?.type?.endsWith(className) == true
|
|
||||||
} >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal const val PENDRAGON_JSON_FETCH_MESSAGE_REQUEST_CLASS_NAME = "FetchMessageRequest;"
|
|
||||||
internal val pendragonJsonFetchMessageRequestFingerprint =
|
|
||||||
reactivexFunctionApplyWithClassInitFingerprint(PENDRAGON_JSON_FETCH_MESSAGE_REQUEST_CLASS_NAME)
|
|
||||||
|
|
||||||
internal const val PENDRAGON_PROTO_FETCH_MESSAGE_LIST_REQUEST_CLASS_NAME = "FetchMessageListRequest;"
|
|
||||||
internal val pendragonProtoFetchMessageListRequestFingerprint =
|
|
||||||
reactivexFunctionApplyWithClassInitFingerprint(PENDRAGON_PROTO_FETCH_MESSAGE_LIST_REQUEST_CLASS_NAME)
|
|
||||||
@@ -12,9 +12,9 @@ import app.revanced.util.returnEarly
|
|||||||
|
|
||||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/fix/SpoofClientPatch;"
|
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/fix/SpoofClientPatch;"
|
||||||
|
|
||||||
|
@Deprecated("Patch no longer functions")
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val spoofClientPatch = bytecodePatch(
|
val spoofClientPatch = bytecodePatch(
|
||||||
name = "Spoof client",
|
|
||||||
description = "Spoofs the client to fix various functions of the app.",
|
description = "Spoofs the client to fix various functions of the app.",
|
||||||
) {
|
) {
|
||||||
val requestListenerPort by intOption(
|
val requestListenerPort by intOption(
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package app.revanced.patches.spotify.navbar
|
package app.revanced.patches.spotify.navbar
|
||||||
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patches.spotify.misc.unlockPremiumPatch
|
|
||||||
|
|
||||||
@Deprecated("Superseded by unlockPremiumPatch", ReplaceWith("unlockPremiumPatch"))
|
@Deprecated("Obsolete and will be deleted soon")
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val premiumNavbarTabPatch = bytecodePatch(
|
val premiumNavbarTabPatch = bytecodePatch(
|
||||||
description = "Hides the premium tab from the navigation bar.",
|
description = "Hides the premium tab from the navigation bar.",
|
||||||
) {
|
)
|
||||||
dependsOn(unlockPremiumPatch)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package app.revanced.patches.viber.ads
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
internal val adsFreeFingerprint = fingerprint {
|
||||||
|
returns("I")
|
||||||
|
parameters()
|
||||||
|
custom { method, classDef ->
|
||||||
|
classDef.type.contains("com/viber/voip/feature/viberplus") &&
|
||||||
|
classDef.superclass?.contains("com/viber/voip/core/feature") == true && // Must extend com.viber.voip.core.feature.?
|
||||||
|
classDef.methods.count() == 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.patches.viber.ads
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.util.returnEarly
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val hideAdsPatch = bytecodePatch(
|
||||||
|
name = "Hide Ads",
|
||||||
|
description = "Hides ad banners between chats.",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.viber.voip"("25.9.2.0"))
|
||||||
|
|
||||||
|
execute {
|
||||||
|
// Return 1 (true) indicating ads should be disabled.
|
||||||
|
adsFreeFingerprint.method.returnEarly(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,7 +89,7 @@ val downloadsPatch = bytecodePatch(
|
|||||||
|
|
||||||
// Main activity is used to launch downloader intent.
|
// Main activity is used to launch downloader intent.
|
||||||
mainActivityOnCreateFingerprint.method.addInstruction(
|
mainActivityOnCreateFingerprint.method.addInstruction(
|
||||||
1,
|
0,
|
||||||
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->activityCreated(Landroid/app/Activity;)V"
|
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->activityCreated(Landroid/app/Activity;)V"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.revanced.patches.youtube.layout.seekbar
|
package app.revanced.patches.youtube.layout.seekbar
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.patches.youtube.shared.YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
import app.revanced.util.containsLiteralInstruction
|
import app.revanced.util.containsLiteralInstruction
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstruction
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
@@ -103,7 +104,7 @@ internal val launchScreenLayoutTypeFingerprint = fingerprint {
|
|||||||
custom { method, _ ->
|
custom { method, _ ->
|
||||||
val firstParameter = method.parameterTypes.firstOrNull()
|
val firstParameter = method.parameterTypes.firstOrNull()
|
||||||
// 19.25 - 19.45
|
// 19.25 - 19.45
|
||||||
(firstParameter == "Lcom/google/android/apps/youtube/app/watchwhile/MainActivity;"
|
(firstParameter == YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
|| firstParameter == "Landroid/app/Activity;") // 19.46+
|
|| firstParameter == "Landroid/app/Activity;") // 19.46+
|
||||||
&& method.containsLiteralInstruction(launchScreenLayoutTypeLotteFeatureFlag)
|
&& method.containsLiteralInstruction(launchScreenLayoutTypeLotteFeatureFlag)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ val shortsAutoplayPatch = bytecodePatch(
|
|||||||
|
|
||||||
// Main activity is used to check if app is in pip mode.
|
// Main activity is used to check if app is in pip mode.
|
||||||
mainActivityOnCreateFingerprint.method.addInstruction(
|
mainActivityOnCreateFingerprint.method.addInstruction(
|
||||||
1,
|
0,
|
||||||
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->setMainActivity(Landroid/app/Activity;)V",
|
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->setMainActivity(Landroid/app/Activity;)V",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -90,8 +90,8 @@ val openShortsInRegularPlayerPatch = bytecodePatch(
|
|||||||
|
|
||||||
// Activity is used as the context to launch an Intent.
|
// Activity is used as the context to launch an Intent.
|
||||||
mainActivityOnCreateFingerprint.method.addInstruction(
|
mainActivityOnCreateFingerprint.method.addInstruction(
|
||||||
1,
|
0,
|
||||||
"invoke-static/range { p0 .. p0 }, ${EXTENSION_CLASS_DESCRIPTOR}->" +
|
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" +
|
||||||
"setMainActivity(Landroid/app/Activity;)V",
|
"setMainActivity(Landroid/app/Activity;)V",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package app.revanced.patches.youtube.layout.sponsorblock
|
package app.revanced.patches.youtube.layout.sponsorblock
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstructionReversed
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
@@ -56,3 +59,20 @@ internal val rectangleFieldInvalidatorFingerprint = fingerprint {
|
|||||||
reference?.parameterTypes?.size == 1 && reference.name == "invalidate" // the reference is the invalidate(..) method
|
reference?.parameterTypes?.size == 1 && reference.name == "invalidate" // the reference is the invalidate(..) method
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val adProgressTextViewVisibilityFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
returns("V")
|
||||||
|
parameters("Z")
|
||||||
|
custom { method, _ ->
|
||||||
|
indexOfAdProgressTextViewVisibilityInstruction(method) >= 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun indexOfAdProgressTextViewVisibilityInstruction(method: Method) =
|
||||||
|
method.indexOfFirstInstructionReversed {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.definingClass ==
|
||||||
|
"Lcom/google/android/libraries/youtube/ads/player/ui/AdProgressTextView;"
|
||||||
|
&& reference.name =="setVisibility"
|
||||||
|
}
|
||||||
|
|||||||
@@ -252,5 +252,16 @@ val sponsorBlockPatch = bytecodePatch(
|
|||||||
}
|
}
|
||||||
} ?: throw PatchException("Could not find the method which contains the replaceMeWith* strings")
|
} ?: throw PatchException("Could not find the method which contains the replaceMeWith* strings")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adProgressTextViewVisibilityFingerprint.method.apply {
|
||||||
|
val index = indexOfAdProgressTextViewVisibilityInstruction(this)
|
||||||
|
val register = getInstruction<FiveRegisterInstruction>(index).registerD
|
||||||
|
|
||||||
|
addInstructionsAtControlFlowLabel(
|
||||||
|
index,
|
||||||
|
"invoke-static { v$register }, $EXTENSION_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setAdProgressTextVisibility(I)V"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.revanced.patches.youtube.layout.theme
|
package app.revanced.patches.youtube.layout.theme
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.patches.youtube.shared.YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
import app.revanced.util.literal
|
import app.revanced.util.literal
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
@@ -37,6 +38,6 @@ internal val splashScreenStyleFingerprint = fingerprint {
|
|||||||
parameters("Landroid/os/Bundle;")
|
parameters("Landroid/os/Bundle;")
|
||||||
literal { SPLASH_SCREEN_STYLE_FEATURE_FLAG }
|
literal { SPLASH_SCREEN_STYLE_FEATURE_FLAG }
|
||||||
custom { method, classDef ->
|
custom { method, classDef ->
|
||||||
method.name == "onCreate" && classDef.endsWith("/MainActivity;")
|
method.name == "onCreate" && classDef.type == YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ val announcementsPatch = bytecodePatch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
mainActivityOnCreateFingerprint.method.addInstruction(
|
mainActivityOnCreateFingerprint.method.addInstruction(
|
||||||
// Insert index must be greater than the insert index used by GmsCoreSupport,
|
0,
|
||||||
// as both patch the same method and GmsCore check should be first.
|
|
||||||
1,
|
|
||||||
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->showAnnouncement(Landroid/app/Activity;)V",
|
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->showAnnouncement(Landroid/app/Activity;)V",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,7 @@ val checkWatchHistoryDomainNameResolutionPatch = bytecodePatch(
|
|||||||
addResources("youtube", "misc.dns.checkWatchHistoryDomainNameResolutionPatch")
|
addResources("youtube", "misc.dns.checkWatchHistoryDomainNameResolutionPatch")
|
||||||
|
|
||||||
mainActivityOnCreateFingerprint.method.addInstruction(
|
mainActivityOnCreateFingerprint.method.addInstruction(
|
||||||
// FIXME: Insert index must be greater than the insert index used by GmsCoreSupport,
|
0,
|
||||||
// as both patch the same method and GmsCoreSupport check should be first,
|
|
||||||
// but the patch does not depend on GmsCoreSupport, so it should not be possible to enforce this
|
|
||||||
// unless a third patch is added that this patch and GmsCoreSupport depend on to manage
|
|
||||||
// the order of the patches.
|
|
||||||
1,
|
|
||||||
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->checkDnsResolver(Landroid/app/Activity;)V",
|
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->checkDnsResolver(Landroid/app/Activity;)V",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ package app.revanced.patches.youtube.misc.extension
|
|||||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.youtube.misc.extension.hooks.*
|
import app.revanced.patches.youtube.misc.extension.hooks.*
|
||||||
|
|
||||||
val sharedExtensionPatch = sharedExtensionPatch("youtube", applicationInitHook)
|
val sharedExtensionPatch = sharedExtensionPatch("youtube",
|
||||||
|
applicationInitHook, applicationInitOnCrateHook)
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
package app.revanced.patches.youtube.misc.extension.hooks
|
package app.revanced.patches.youtube.misc.extension.hooks
|
||||||
|
|
||||||
import app.revanced.patches.shared.misc.extension.extensionHook
|
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||||
|
import app.revanced.patches.youtube.shared.YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks the context when the app is launched as a regular application (and is not an embedded video playback).
|
* Hooks the context when the app is launched as a regular application (and is not an embedded video playback).
|
||||||
*/
|
*/
|
||||||
// Extension context is the Activity itself.
|
// Extension context is the Activity itself.
|
||||||
internal val applicationInitHook = extensionHook {
|
internal val applicationInitHook = extensionHook {
|
||||||
|
// Does _not_ resolve to the YouTube main activity.
|
||||||
|
// Required as some hooked code runs before the main activity is launched.
|
||||||
strings("Application creation", "Application.onCreate")
|
strings("Application creation", "Application.onCreate")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val applicationInitOnCrateHook = extensionHook {
|
||||||
|
returns("V")
|
||||||
|
parameters("Landroid/os/Bundle;")
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "onCreate" && classDef.type == YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,8 +127,7 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
|
|||||||
// Litho filtering based on navigation tab before the tab is updated.
|
// Litho filtering based on navigation tab before the tab is updated.
|
||||||
mainActivityOnBackPressedFingerprint.method.addInstruction(
|
mainActivityOnBackPressedFingerprint.method.addInstruction(
|
||||||
0,
|
0,
|
||||||
"invoke-static { p0 }, " +
|
"invoke-static { p0 }, $EXTENSION_CLASS_DESCRIPTOR->onBackPressed(Landroid/app/Activity;)V",
|
||||||
"$EXTENSION_CLASS_DESCRIPTOR->onBackPressed(Landroid/app/Activity;)V",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hook the search bar.
|
// Hook the search bar.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
package app.revanced.patches.youtube.misc.playservice
|
package app.revanced.patches.youtube.misc.playservice
|
||||||
|
|
||||||
import app.revanced.patcher.patch.resourcePatch
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
import app.revanced.util.findElementByAttributeValueOrThrow
|
import app.revanced.util.findPlayStoreServicesVersion
|
||||||
|
|
||||||
@Deprecated("19.34.42 is the lowest supported version")
|
@Deprecated("19.34.42 is the lowest supported version")
|
||||||
var is_19_03_or_greater = false
|
var is_19_03_or_greater = false
|
||||||
@@ -77,12 +77,7 @@ val versionCheckPatch = resourcePatch(
|
|||||||
execute {
|
execute {
|
||||||
// The app version is missing from the decompiled manifest,
|
// The app version is missing from the decompiled manifest,
|
||||||
// so instead use the Google Play services version and compare against specific releases.
|
// so instead use the Google Play services version and compare against specific releases.
|
||||||
val playStoreServicesVersion = document("res/values/integers.xml").use { document ->
|
val playStoreServicesVersion = findPlayStoreServicesVersion()
|
||||||
document.documentElement.childNodes.findElementByAttributeValueOrThrow(
|
|
||||||
"name",
|
|
||||||
"google_play_services_version",
|
|
||||||
).textContent.toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
// All bug fix releases always seem to use the same play store version as the minor version.
|
// All bug fix releases always seem to use the same play store version as the minor version.
|
||||||
is_19_03_or_greater = 240402000 <= playStoreServicesVersion
|
is_19_03_or_greater = 240402000 <= playStoreServicesVersion
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package app.revanced.patches.youtube.misc.spoof
|
package app.revanced.patches.youtube.misc.spoof
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
||||||
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
||||||
@@ -13,56 +14,69 @@ import app.revanced.patches.youtube.misc.playservice.is_20_14_or_greater
|
|||||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
|
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
|
||||||
|
|
||||||
val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
|
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/spoof/SpoofVideoStreamsPatch;"
|
||||||
compatibleWith(
|
|
||||||
"com.google.android.youtube"(
|
val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
|
||||||
"19.34.42",
|
block = {
|
||||||
"19.43.41",
|
compatibleWith(
|
||||||
"19.47.53",
|
"com.google.android.youtube"(
|
||||||
"20.07.39",
|
"19.34.42",
|
||||||
"20.12.46",
|
"19.43.41",
|
||||||
"20.13.41",
|
"19.47.53",
|
||||||
|
"20.07.39",
|
||||||
|
"20.12.46",
|
||||||
|
"20.13.41",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
dependsOn(
|
dependsOn(
|
||||||
userAgentClientSpoofPatch,
|
userAgentClientSpoofPatch,
|
||||||
settingsPatch,
|
settingsPatch,
|
||||||
versionCheckPatch
|
versionCheckPatch
|
||||||
)
|
)
|
||||||
}, {
|
},
|
||||||
is_19_34_or_greater
|
fixMediaFetchHotConfigChanges = {
|
||||||
}, {
|
is_19_34_or_greater
|
||||||
// In 20.14 the flag was merged with 20.03 start playback flag.
|
},
|
||||||
is_20_10_or_greater && !is_20_14_or_greater
|
fixMediaFetchHotConfigAlternativeChanges = {
|
||||||
}, {
|
// In 20.14 the flag was merged with 20.03 start playback flag.
|
||||||
is_20_03_or_greater
|
is_20_10_or_greater && !is_20_14_or_greater
|
||||||
}, {
|
},
|
||||||
addResources("youtube", "misc.fix.playback.spoofVideoStreamsPatch")
|
fixParsePlaybackResponseFeatureFlag = {
|
||||||
|
is_20_03_or_greater
|
||||||
|
},
|
||||||
|
executeBlock = {
|
||||||
|
addResources("youtube", "misc.fix.playback.spoofVideoStreamsPatch")
|
||||||
|
|
||||||
PreferenceScreen.MISC.addPreferences(
|
PreferenceScreen.MISC.addPreferences(
|
||||||
PreferenceScreenPreference(
|
PreferenceScreenPreference(
|
||||||
key = "revanced_spoof_video_streams_screen",
|
key = "revanced_spoof_video_streams_screen",
|
||||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||||
preferences = setOf(
|
preferences = setOf(
|
||||||
SwitchPreference("revanced_spoof_video_streams"),
|
SwitchPreference("revanced_spoof_video_streams"),
|
||||||
ListPreference("revanced_spoof_video_streams_client_type"),
|
ListPreference("revanced_spoof_video_streams_client_type"),
|
||||||
NonInteractivePreference(
|
NonInteractivePreference(
|
||||||
// Requires a key and title but the actual text is chosen at runtime.
|
// Requires a key and title but the actual text is chosen at runtime.
|
||||||
key = "revanced_spoof_video_streams_about_android",
|
key = "revanced_spoof_video_streams_about_android",
|
||||||
tag = "app.revanced.extension.youtube.settings.preference.SpoofStreamingDataSideEffectsPreference"
|
tag = "app.revanced.extension.youtube.settings.preference.SpoofStreamingDataSideEffectsPreference"
|
||||||
),
|
),
|
||||||
ListPreference(
|
ListPreference(
|
||||||
key = "revanced_spoof_video_streams_language",
|
key = "revanced_spoof_video_streams_language",
|
||||||
// Language strings are declared in Setting patch.
|
// Language strings are declared in Setting patch.
|
||||||
entriesKey = "revanced_language_entries",
|
entriesKey = "revanced_language_entries",
|
||||||
entryValuesKey = "revanced_language_entry_values",
|
entryValuesKey = "revanced_language_entry_values",
|
||||||
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference"
|
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference"
|
||||||
),
|
),
|
||||||
SwitchPreference("revanced_spoof_video_streams_ios_force_avc"),
|
SwitchPreference("revanced_spoof_streaming_data_stats_for_nerds"),
|
||||||
SwitchPreference("revanced_spoof_streaming_data_stats_for_nerds"),
|
)
|
||||||
),
|
)
|
||||||
),
|
)
|
||||||
)
|
|
||||||
})
|
mainActivityOnCreateFingerprint.method.addInstruction(
|
||||||
|
0,
|
||||||
|
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setClientOrderToUse()V"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import app.revanced.patcher.fingerprint
|
|||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
internal const val YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE = "Lcom/google/android/apps/youtube/app/watchwhile/MainActivity;"
|
||||||
|
|
||||||
internal val conversionContextFingerprintToString = fingerprint {
|
internal val conversionContextFingerprintToString = fingerprint {
|
||||||
parameters()
|
parameters()
|
||||||
strings(
|
strings(
|
||||||
@@ -48,7 +50,7 @@ internal val mainActivityConstructorFingerprint = fingerprint {
|
|||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||||
parameters()
|
parameters()
|
||||||
custom { _, classDef ->
|
custom { _, classDef ->
|
||||||
classDef.endsWith("/MainActivity;")
|
classDef.type == YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +59,7 @@ internal val mainActivityOnBackPressedFingerprint = fingerprint {
|
|||||||
returns("V")
|
returns("V")
|
||||||
parameters()
|
parameters()
|
||||||
custom { method, classDef ->
|
custom { method, classDef ->
|
||||||
method.name == "onBackPressed" && classDef.endsWith("/MainActivity;")
|
method.name == "onBackPressed" && classDef.type == YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +67,7 @@ internal val mainActivityOnCreateFingerprint = fingerprint {
|
|||||||
returns("V")
|
returns("V")
|
||||||
parameters("Landroid/os/Bundle;")
|
parameters("Landroid/os/Bundle;")
|
||||||
custom { method, classDef ->
|
custom { method, classDef ->
|
||||||
method.name == "onCreate" && classDef.endsWith("/MainActivity;")
|
method.name == "onCreate" && classDef.type == YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package app.revanced.patches.youtube.video.audio
|
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.addInstructions
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
@@ -14,6 +15,7 @@ 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.playservice.versionCheckPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
|
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
|
||||||
import app.revanced.util.findMethodFromToString
|
import app.revanced.util.findMethodFromToString
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
import app.revanced.util.insertLiteralOverride
|
import app.revanced.util.insertLiteralOverride
|
||||||
@@ -55,10 +57,12 @@ val forceOriginalAudioPatch = bytecodePatch(
|
|||||||
addResources("youtube", "video.audio.forceOriginalAudioPatch")
|
addResources("youtube", "video.audio.forceOriginalAudioPatch")
|
||||||
|
|
||||||
PreferenceScreen.VIDEO.addPreferences(
|
PreferenceScreen.VIDEO.addPreferences(
|
||||||
SwitchPreference(
|
SwitchPreference("revanced_force_original_audio")
|
||||||
key = "revanced_force_original_audio",
|
)
|
||||||
tag = "app.revanced.extension.youtube.settings.preference.ForceOriginalAudioSwitchPreference"
|
|
||||||
)
|
mainActivityOnCreateFingerprint.method.addInstruction(
|
||||||
|
0,
|
||||||
|
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setPreferredLanguage()V"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Disable feature flag that ignores the default track flag
|
// Disable feature flag that ignores the default track flag
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ internal val Instruction.registersUsed: List<Int>
|
|||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
is FiveRegisterInstruction -> {
|
is FiveRegisterInstruction -> {
|
||||||
when (registerCount) {
|
when (registerCount) {
|
||||||
|
0 -> listOf()
|
||||||
1 -> listOf(registerC)
|
1 -> listOf(registerC)
|
||||||
2 -> listOf(registerC, registerD)
|
2 -> listOf(registerC, registerD)
|
||||||
3 -> listOf(registerC, registerD, registerE)
|
3 -> listOf(registerC, registerD, registerE)
|
||||||
|
|||||||
@@ -178,3 +178,15 @@ internal fun Element.copyAttributesFrom(oldContainer: Element) {
|
|||||||
setAttribute(attr.name, attr.value)
|
setAttribute(attr.name, attr.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The play store services version.
|
||||||
|
*/
|
||||||
|
internal fun ResourcePatchContext.findPlayStoreServicesVersion(): Int =
|
||||||
|
document("res/values/integers.xml").use { document ->
|
||||||
|
document.documentElement.childNodes.findElementByAttributeValueOrThrow(
|
||||||
|
"name",
|
||||||
|
"google_play_services_version",
|
||||||
|
).textContent.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1075,10 +1075,12 @@ Second \"item\" text"</string>
|
|||||||
<string name="revanced_sb_segments_intro_sum">فاصل زمني بدون محتوى فعلي. قد يكون توقفًا مؤقتًا، أو إطارًا ثابتًا، أو رسومًا متحركة متكررة. لا يتضمن انتقالات تحتوي على معلومات</string>
|
<string name="revanced_sb_segments_intro_sum">فاصل زمني بدون محتوى فعلي. قد يكون توقفًا مؤقتًا، أو إطارًا ثابتًا، أو رسومًا متحركة متكررة. لا يتضمن انتقالات تحتوي على معلومات</string>
|
||||||
<string name="revanced_sb_segments_outro">الخاتمة / تترات النهاية</string>
|
<string name="revanced_sb_segments_outro">الخاتمة / تترات النهاية</string>
|
||||||
<string name="revanced_sb_segments_outro_sum">تتر النهاية أو عندما تظهر بطاقات نهاية YouTube، نهايات غير منطوقة. ليس للاستنتاجات مع المعلومات</string>
|
<string name="revanced_sb_segments_outro_sum">تتر النهاية أو عندما تظهر بطاقات نهاية YouTube، نهايات غير منطوقة. ليس للاستنتاجات مع المعلومات</string>
|
||||||
<string name="revanced_sb_segments_preview">معاينة/موجز/ربط</string>
|
<string name="revanced_sb_segments_hook">مقدمة / تحيات</string>
|
||||||
|
<string name="revanced_sb_segments_hook_sum">مقاطع دعائية مروية للفيديو القادم، تحيات ووداعات. لا تشمل الأقسام التي تضيف محتوى إضافيًا</string>
|
||||||
|
<string name="revanced_sb_segments_preview">معاينة / ملخص</string>
|
||||||
<string name="revanced_sb_segments_preview_sum">مجموعة من المقاطع التي توضح ما هو قادم أو ما حدث في الفيديو أو في مقاطع فيديو أخرى من السلسلة، حيث تتكرر جميع المعلومات في مكان آخر</string>
|
<string name="revanced_sb_segments_preview_sum">مجموعة من المقاطع التي توضح ما هو قادم أو ما حدث في الفيديو أو في مقاطع فيديو أخرى من السلسلة، حيث تتكرر جميع المعلومات في مكان آخر</string>
|
||||||
<string name="revanced_sb_segments_filler">خارج الموضوع/النكات</string>
|
<string name="revanced_sb_segments_filler">استطراد / نكات</string>
|
||||||
<string name="revanced_sb_segments_filler_sum">تم إضافة مشاهد ملتقطة خارج الموضوع أو الفكاهة التي ليست مطلوبة لفهم المحتوى الرئيسي للفيديو. لا تتضمن مقاطع توفر تَعبِير أو تفاصيل الخلفية</string>
|
<string name="revanced_sb_segments_filler_sum">مشاهد أو نكات جانبية غير ضرورية لفهم المحتوى الرئيسي للفيديو. لا تشمل الأقسام التي توفر سياقًا أو تفاصيل خلفية</string>
|
||||||
<string name="revanced_sb_segments_nomusic">الموسيقى: مقطع غير موسيقي</string>
|
<string name="revanced_sb_segments_nomusic">الموسيقى: مقطع غير موسيقي</string>
|
||||||
<string name="revanced_sb_segments_nomusic_sum">فقط للاستخدام في المقاطع الموسيقية. أقسام المقاطع الموسيقية بدون موسيقى، والتي لم يتم تغطيتها بالفعل من قبل فئة أخرى</string>
|
<string name="revanced_sb_segments_nomusic_sum">فقط للاستخدام في المقاطع الموسيقية. أقسام المقاطع الموسيقية بدون موسيقى، والتي لم يتم تغطيتها بالفعل من قبل فئة أخرى</string>
|
||||||
<string name="revanced_sb_skip_button_compact">تخطي</string>
|
<string name="revanced_sb_skip_button_compact">تخطي</string>
|
||||||
@@ -1091,10 +1093,11 @@ Second \"item\" text"</string>
|
|||||||
<string name="revanced_sb_skip_button_intro_middle">تخطي الفاصل</string>
|
<string name="revanced_sb_skip_button_intro_middle">تخطي الفاصل</string>
|
||||||
<string name="revanced_sb_skip_button_intro_end">تخطي الفاصل</string>
|
<string name="revanced_sb_skip_button_intro_end">تخطي الفاصل</string>
|
||||||
<string name="revanced_sb_skip_button_outro">تخطي الخاتمة</string>
|
<string name="revanced_sb_skip_button_outro">تخطي الخاتمة</string>
|
||||||
|
<string name="revanced_sb_skip_button_hook">تخطي المقدمة</string>
|
||||||
<string name="revanced_sb_skip_button_preview_beginning">تخطي النظرة العامة</string>
|
<string name="revanced_sb_skip_button_preview_beginning">تخطي النظرة العامة</string>
|
||||||
<string name="revanced_sb_skip_button_preview_middle">تخطي النظرة العامة</string>
|
<string name="revanced_sb_skip_button_preview_middle">تخطي النظرة العامة</string>
|
||||||
<string name="revanced_sb_skip_button_preview_end">تخطي الملخص</string>
|
<string name="revanced_sb_skip_button_preview_end">تخطي الملخص</string>
|
||||||
<string name="revanced_sb_skip_button_filler">تخطي مقطع غير ذي صلة</string>
|
<string name="revanced_sb_skip_button_filler">تخطي الجزء الجانبي</string>
|
||||||
<string name="revanced_sb_skip_button_nomusic">تخطي غير الموسيقى</string>
|
<string name="revanced_sb_skip_button_nomusic">تخطي غير الموسيقى</string>
|
||||||
<string name="revanced_sb_skip_button_unsubmitted">تخطي المقطع</string>
|
<string name="revanced_sb_skip_button_unsubmitted">تخطي المقطع</string>
|
||||||
<string name="revanced_sb_skipped_sponsor">تم تخطي الراعي</string>
|
<string name="revanced_sb_skipped_sponsor">تم تخطي الراعي</string>
|
||||||
@@ -1105,10 +1108,11 @@ Second \"item\" text"</string>
|
|||||||
<string name="revanced_sb_skipped_intro_middle">تم تخطي الفاصل</string>
|
<string name="revanced_sb_skipped_intro_middle">تم تخطي الفاصل</string>
|
||||||
<string name="revanced_sb_skipped_intro_end">تم تخطي الفاصل</string>
|
<string name="revanced_sb_skipped_intro_end">تم تخطي الفاصل</string>
|
||||||
<string name="revanced_sb_skipped_outro">تم تخطي الخاتمة</string>
|
<string name="revanced_sb_skipped_outro">تم تخطي الخاتمة</string>
|
||||||
|
<string name="revanced_sb_skipped_hook">تم تخطي المقدمة</string>
|
||||||
<string name="revanced_sb_skipped_preview_beginning">تم تخطي النظرة العامة</string>
|
<string name="revanced_sb_skipped_preview_beginning">تم تخطي النظرة العامة</string>
|
||||||
<string name="revanced_sb_skipped_preview_middle">تم تخطي النظرة العامة</string>
|
<string name="revanced_sb_skipped_preview_middle">تم تخطي النظرة العامة</string>
|
||||||
<string name="revanced_sb_skipped_preview_end">تم تخطي الملخص</string>
|
<string name="revanced_sb_skipped_preview_end">تم تخطي الملخص</string>
|
||||||
<string name="revanced_sb_skipped_filler">تم تخطي مقطع غير ذي صلة</string>
|
<string name="revanced_sb_skipped_filler">تم تخطي الجزء الجانبي</string>
|
||||||
<string name="revanced_sb_skipped_nomusic">تم تخطي جزء غير موسيقي</string>
|
<string name="revanced_sb_skipped_nomusic">تم تخطي جزء غير موسيقي</string>
|
||||||
<string name="revanced_sb_skipped_unsubmitted">تم تخطي المقطع الغير المرسل</string>
|
<string name="revanced_sb_skipped_unsubmitted">تم تخطي المقطع الغير المرسل</string>
|
||||||
<string name="revanced_sb_skipped_multiple_segments">تم تخطي عدة مقاطع</string>
|
<string name="revanced_sb_skipped_multiple_segments">تم تخطي عدة مقاطع</string>
|
||||||
|
|||||||
@@ -1074,10 +1074,12 @@ Sizin istifadəçi Id-niz parol kimidir və heç vaxt paylaşmaq olmaz.
|
|||||||
<string name="revanced_sb_segments_intro_sum">Həqiqi məzmunu olmayan aralıq. Fasilə, statik kadr və ya təkrarlanan animasiya ola bilər. Məlumat ehtiva edən keçidlər daxil deyil</string>
|
<string name="revanced_sb_segments_intro_sum">Həqiqi məzmunu olmayan aralıq. Fasilə, statik kadr və ya təkrarlanan animasiya ola bilər. Məlumat ehtiva edən keçidlər daxil deyil</string>
|
||||||
<string name="revanced_sb_segments_outro">Son kartlar / Zaminlər</string>
|
<string name="revanced_sb_segments_outro">Son kartlar / Zaminlər</string>
|
||||||
<string name="revanced_sb_segments_outro_sum">Zaminlər və ya YouTube bitiş kartları görünəndə. Məlumat daxil olan nəticələr üçün deyil</string>
|
<string name="revanced_sb_segments_outro_sum">Zaminlər və ya YouTube bitiş kartları görünəndə. Məlumat daxil olan nəticələr üçün deyil</string>
|
||||||
<string name="revanced_sb_segments_preview">Önizləmə/Anons/Qısa</string>
|
<string name="revanced_sb_segments_hook">Giriş / Salamlar</string>
|
||||||
|
<string name="revanced_sb_segments_hook_sum">Yaxınlaşan video üçün danışılan kadrlar, salamlama və sağollaşma. Əlavə məzmun əlavə edən bölmələr daxil deyil</string>
|
||||||
|
<string name="revanced_sb_segments_preview">Önizləmə / Anons</string>
|
||||||
<string name="revanced_sb_segments_preview_sum">Videoda və ya seriyanın digər videolarında nə gözlənildiyini və ya baş verdiyini göstərən, bütün məlumatların başqa yerdə təkrarlandığı bölüm toplusu</string>
|
<string name="revanced_sb_segments_preview_sum">Videoda və ya seriyanın digər videolarında nə gözlənildiyini və ya baş verdiyini göstərən, bütün məlumatların başqa yerdə təkrarlandığı bölüm toplusu</string>
|
||||||
<string name="revanced_sb_segments_filler">Əlaqəsiz/Zarafatlar</string>
|
<string name="revanced_sb_segments_filler">Əlaqəsiz / Zarafatlar</string>
|
||||||
<string name="revanced_sb_segments_filler_sum">Videonun əsas məzmununu başa düşmək tələb olunmayan yalnız əlaqəsiz və ya yumor üçün əlavə edilmiş təsiredici səhnələr. Məzmun və ya arxa plan detallarını təqdim edən bölümlər daxil deyil</string>
|
<string name="revanced_sb_segments_filler_sum">Videonun əsas məzmunun başa düşmək tələb olunmayan əlaqəsiz səhnələr və ya zarafatlar. Məzmun və ya arxa plan təfərrüatlarını təmin edən bölmələr daxil deyil</string>
|
||||||
<string name="revanced_sb_segments_nomusic">Musiqi: Musiqi olmayan bölmə</string>
|
<string name="revanced_sb_segments_nomusic">Musiqi: Musiqi olmayan bölmə</string>
|
||||||
<string name="revanced_sb_segments_nomusic_sum">Yalnız musiqi videolarında istifadə üçün. Artıq başqa kateqoriyaya aid edilməyən musiqisiz musiqi videoları bölmələri</string>
|
<string name="revanced_sb_segments_nomusic_sum">Yalnız musiqi videolarında istifadə üçün. Artıq başqa kateqoriyaya aid edilməyən musiqisiz musiqi videoları bölmələri</string>
|
||||||
<string name="revanced_sb_skip_button_compact">Ötür</string>
|
<string name="revanced_sb_skip_button_compact">Ötür</string>
|
||||||
@@ -1090,10 +1092,11 @@ Sizin istifadəçi Id-niz parol kimidir və heç vaxt paylaşmaq olmaz.
|
|||||||
<string name="revanced_sb_skip_button_intro_middle">Fasiləni ötür</string>
|
<string name="revanced_sb_skip_button_intro_middle">Fasiləni ötür</string>
|
||||||
<string name="revanced_sb_skip_button_intro_end">Fasiləni ötür</string>
|
<string name="revanced_sb_skip_button_intro_end">Fasiləni ötür</string>
|
||||||
<string name="revanced_sb_skip_button_outro">Bitişi ötür</string>
|
<string name="revanced_sb_skip_button_outro">Bitişi ötür</string>
|
||||||
|
<string name="revanced_sb_skip_button_hook">Girişi ötür</string>
|
||||||
<string name="revanced_sb_skip_button_preview_beginning">Önizləməni ötür</string>
|
<string name="revanced_sb_skip_button_preview_beginning">Önizləməni ötür</string>
|
||||||
<string name="revanced_sb_skip_button_preview_middle">Önizləməni ötür</string>
|
<string name="revanced_sb_skip_button_preview_middle">Önizləməni ötür</string>
|
||||||
<string name="revanced_sb_skip_button_preview_end">Anonsu ötür</string>
|
<string name="revanced_sb_skip_button_preview_end">Anonsu ötür</string>
|
||||||
<string name="revanced_sb_skip_button_filler">Əlaqəsiz hissəni ötür</string>
|
<string name="revanced_sb_skip_button_filler">Əlaqəsiz olanı ötür</string>
|
||||||
<string name="revanced_sb_skip_button_nomusic">Musiqisiz hissəni ötür</string>
|
<string name="revanced_sb_skip_button_nomusic">Musiqisiz hissəni ötür</string>
|
||||||
<string name="revanced_sb_skip_button_unsubmitted">Bölümü ötür</string>
|
<string name="revanced_sb_skip_button_unsubmitted">Bölümü ötür</string>
|
||||||
<string name="revanced_sb_skipped_sponsor">Sponsorlu hissə ötürüldü</string>
|
<string name="revanced_sb_skipped_sponsor">Sponsorlu hissə ötürüldü</string>
|
||||||
@@ -1104,10 +1107,11 @@ Sizin istifadəçi Id-niz parol kimidir və heç vaxt paylaşmaq olmaz.
|
|||||||
<string name="revanced_sb_skipped_intro_middle">Fasilə ötürüldü</string>
|
<string name="revanced_sb_skipped_intro_middle">Fasilə ötürüldü</string>
|
||||||
<string name="revanced_sb_skipped_intro_end">Fasilə ötürüldü</string>
|
<string name="revanced_sb_skipped_intro_end">Fasilə ötürüldü</string>
|
||||||
<string name="revanced_sb_skipped_outro">Bitiş ötürüldü</string>
|
<string name="revanced_sb_skipped_outro">Bitiş ötürüldü</string>
|
||||||
|
<string name="revanced_sb_skipped_hook">Giriş ötürüldü</string>
|
||||||
<string name="revanced_sb_skipped_preview_beginning">Önbaxış ötürüldü</string>
|
<string name="revanced_sb_skipped_preview_beginning">Önbaxış ötürüldü</string>
|
||||||
<string name="revanced_sb_skipped_preview_middle">Önbaxış ötürüldü</string>
|
<string name="revanced_sb_skipped_preview_middle">Önbaxış ötürüldü</string>
|
||||||
<string name="revanced_sb_skipped_preview_end">Anons ötürüldü</string>
|
<string name="revanced_sb_skipped_preview_end">Anons ötürüldü</string>
|
||||||
<string name="revanced_sb_skipped_filler">Əlaqəsiz hissə ötürüldü</string>
|
<string name="revanced_sb_skipped_filler">Əlaqəsiz ötürüldü</string>
|
||||||
<string name="revanced_sb_skipped_nomusic">Musiqi olmayan bölmə ötürüldü</string>
|
<string name="revanced_sb_skipped_nomusic">Musiqi olmayan bölmə ötürüldü</string>
|
||||||
<string name="revanced_sb_skipped_unsubmitted">Göndərilməmiş bölüm ötürüldü</string>
|
<string name="revanced_sb_skipped_unsubmitted">Göndərilməmiş bölüm ötürüldü</string>
|
||||||
<string name="revanced_sb_skipped_multiple_segments">Çoxlu bölümlər ötürüldü</string>
|
<string name="revanced_sb_skipped_multiple_segments">Çoxlu bölümlər ötürüldü</string>
|
||||||
|
|||||||
@@ -1076,10 +1076,12 @@ Second \"item\" text"</string>
|
|||||||
<string name="revanced_sb_segments_intro_sum">Інтэрвал без фактычнага зместу. Гэта можа быць паўза, статычны кадр або паўтаральная анімацыя. Не ўключае пераходы, якія змяшчаюць інфармацыю</string>
|
<string name="revanced_sb_segments_intro_sum">Інтэрвал без фактычнага зместу. Гэта можа быць паўза, статычны кадр або паўтаральная анімацыя. Не ўключае пераходы, якія змяшчаюць інфармацыю</string>
|
||||||
<string name="revanced_sb_segments_outro">Канцоўкі / Цітры</string>
|
<string name="revanced_sb_segments_outro">Канцоўкі / Цітры</string>
|
||||||
<string name="revanced_sb_segments_outro_sum">Крэдыты або калі з\"яўляюцца канцавыя карткі YouTube. Не для высноў з інфармацыяй</string>
|
<string name="revanced_sb_segments_outro_sum">Крэдыты або калі з\"яўляюцца канцавыя карткі YouTube. Не для высноў з інфармацыяй</string>
|
||||||
<string name="revanced_sb_segments_preview">Папярэдні прагляд/Паўзвядзенне/Хук</string>
|
<string name="revanced_sb_segments_hook">Уступ / Прывітанні</string>
|
||||||
|
<string name="revanced_sb_segments_hook_sum">Агучаныя трэйлеры для будучага відэа, прывітанні і развітанні. Не ўключае раздзелы, якія дадаюць дадатковы змест</string>
|
||||||
|
<string name="revanced_sb_segments_preview">Прадпрагляд / Паўтарэнне</string>
|
||||||
<string name="revanced_sb_segments_preview_sum">Калекцыя кліпаў, якія паказваюць, што адбываецца або што адбылося ў відэа ці ў іншых відэа серыі, дзе ўся інфармацыя паўтараецца ў іншым месцы</string>
|
<string name="revanced_sb_segments_preview_sum">Калекцыя кліпаў, якія паказваюць, што адбываецца або што адбылося ў відэа ці ў іншых відэа серыі, дзе ўся інфармацыя паўтараецца ў іншым месцы</string>
|
||||||
<string name="revanced_sb_segments_filler">Філер Тангенс / Жарты</string>
|
<string name="revanced_sb_segments_filler">Лірычнае адступленне / Жарты</string>
|
||||||
<string name="revanced_sb_segments_filler_sum">Датычныя сцэны дададзены толькі для напаўнення або гумару, якія не патрэбныя для разумення асноўнага зместу відэа. Не ўключае сегменты, якія прадстаўляюць кантэкст або даведку</string>
|
<string name="revanced_sb_segments_filler_sum">Тангенцыяльныя сцэны або жарты, якія не патрабуюцца для разумення асноўнага зместу відэа. Не ўключае раздзелы, якія прадастаўляюць кантэкст або фонавыя дэталі</string>
|
||||||
<string name="revanced_sb_segments_nomusic">Музыка: немузычны раздзел</string>
|
<string name="revanced_sb_segments_nomusic">Музыка: немузычны раздзел</string>
|
||||||
<string name="revanced_sb_segments_nomusic_sum">Толькі для выкарыстання ў музычных відэа. Раздзелы музычных відэа без музыкі, якія яшчэ не ахоплены іншай катэгорыяй</string>
|
<string name="revanced_sb_segments_nomusic_sum">Толькі для выкарыстання ў музычных відэа. Раздзелы музычных відэа без музыкі, якія яшчэ не ахоплены іншай катэгорыяй</string>
|
||||||
<string name="revanced_sb_skip_button_compact">Прапусціць</string>
|
<string name="revanced_sb_skip_button_compact">Прапусціць</string>
|
||||||
@@ -1092,10 +1094,11 @@ Second \"item\" text"</string>
|
|||||||
<string name="revanced_sb_skip_button_intro_middle">Прапусціць антракт</string>
|
<string name="revanced_sb_skip_button_intro_middle">Прапусціць антракт</string>
|
||||||
<string name="revanced_sb_skip_button_intro_end">Прапусціць антракт</string>
|
<string name="revanced_sb_skip_button_intro_end">Прапусціць антракт</string>
|
||||||
<string name="revanced_sb_skip_button_outro">Прапусціць іншы</string>
|
<string name="revanced_sb_skip_button_outro">Прапусціць іншы</string>
|
||||||
|
<string name="revanced_sb_skip_button_hook">Прапусціць уступ</string>
|
||||||
<string name="revanced_sb_skip_button_preview_beginning">Прапусціць папярэдні прагляд</string>
|
<string name="revanced_sb_skip_button_preview_beginning">Прапусціць папярэдні прагляд</string>
|
||||||
<string name="revanced_sb_skip_button_preview_middle">Прапусціць папярэдні прагляд</string>
|
<string name="revanced_sb_skip_button_preview_middle">Прапусціць папярэдні прагляд</string>
|
||||||
<string name="revanced_sb_skip_button_preview_end">Прапусціць рэзюмэ</string>
|
<string name="revanced_sb_skip_button_preview_end">Прапусціць рэзюмэ</string>
|
||||||
<string name="revanced_sb_skip_button_filler">Скіп напаўняльнік</string>
|
<string name="revanced_sb_skip_button_filler">Прапусціць тангенс</string>
|
||||||
<string name="revanced_sb_skip_button_nomusic">Прапусціць не музыку</string>
|
<string name="revanced_sb_skip_button_nomusic">Прапусціць не музыку</string>
|
||||||
<string name="revanced_sb_skip_button_unsubmitted">Прапусціць сегмент</string>
|
<string name="revanced_sb_skip_button_unsubmitted">Прапусціць сегмент</string>
|
||||||
<string name="revanced_sb_skipped_sponsor">Прапусціў спонсара</string>
|
<string name="revanced_sb_skipped_sponsor">Прапусціў спонсара</string>
|
||||||
@@ -1106,10 +1109,11 @@ Second \"item\" text"</string>
|
|||||||
<string name="revanced_sb_skipped_intro_middle">Прапушчаны антракт</string>
|
<string name="revanced_sb_skipped_intro_middle">Прапушчаны антракт</string>
|
||||||
<string name="revanced_sb_skipped_intro_end">Прапушчаны антракт</string>
|
<string name="revanced_sb_skipped_intro_end">Прапушчаны антракт</string>
|
||||||
<string name="revanced_sb_skipped_outro">Прапушчаны выхад</string>
|
<string name="revanced_sb_skipped_outro">Прапушчаны выхад</string>
|
||||||
|
<string name="revanced_sb_skipped_hook">Уступ прапушчаны</string>
|
||||||
<string name="revanced_sb_skipped_preview_beginning">Прапушчаны папярэдні прагляд</string>
|
<string name="revanced_sb_skipped_preview_beginning">Прапушчаны папярэдні прагляд</string>
|
||||||
<string name="revanced_sb_skipped_preview_middle">Прапушчаны папярэдні прагляд</string>
|
<string name="revanced_sb_skipped_preview_middle">Прапушчаны папярэдні прагляд</string>
|
||||||
<string name="revanced_sb_skipped_preview_end">Прапушчаны рэзюмэ</string>
|
<string name="revanced_sb_skipped_preview_end">Прапушчаны рэзюмэ</string>
|
||||||
<string name="revanced_sb_skipped_filler">Прапушчаны напаўняльнік</string>
|
<string name="revanced_sb_skipped_filler">Тангенс прапушчаны</string>
|
||||||
<string name="revanced_sb_skipped_nomusic">Прапусціў немузычны раздзел</string>
|
<string name="revanced_sb_skipped_nomusic">Прапусціў немузычны раздзел</string>
|
||||||
<string name="revanced_sb_skipped_unsubmitted">Прапушчаны неадпраўлены сегмент</string>
|
<string name="revanced_sb_skipped_unsubmitted">Прапушчаны неадпраўлены сегмент</string>
|
||||||
<string name="revanced_sb_skipped_multiple_segments">Прапушчаны некалькі сегментаў</string>
|
<string name="revanced_sb_skipped_multiple_segments">Прапушчаны некалькі сегментаў</string>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user