Compare commits

...

43 Commits

Author SHA1 Message Date
oSumAtrIX
3401467a6d feat(Instagram): Disable Disable Reels scrolling by default 2026-01-15 23:30:17 +01:00
Swakshan
87247590de fix(Instagram): Sanitize sharing links (#6483) 2026-01-13 20:10:25 +01:00
xehpuk
41e2590584 chore(Strava): Restructure media package (#6480) 2026-01-13 15:46:49 +01:00
xehpuk
778d13ce8b feat(Strava): Add Add media download patch (#6449)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-12 23:28:15 +01:00
ekaunt
19f146c01d feat(YouTube): Add Pause on audio interrupt patch (#6464)
Co-authored-by: bengross <bengross@vecta.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-12 23:18:09 +01:00
Pun Butrach
12b819d20e ci: Schedule Crowdin to runs weekly instead of every 12 hours (#6466) 2026-01-12 02:40:19 +01:00
Pun Butrach
004b5908db build: Use Gradle credentials system (#6467) 2026-01-11 16:59:48 +01:00
semantic-release-bot
f4af27dfec chore: Release v5.48.0-dev.9 [skip ci]
# [5.48.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.8...v5.48.0-dev.9) (2026-01-08)

### Features

* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](4cc315952d))
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](6312fe8d60))
2026-01-08 00:11:57 +00:00
xehpuk
4cc315952d feat: Add Disable Sentry telemetry patch (#6416) 2026-01-08 01:08:52 +01:00
1fexd
6312fe8d60 feat: Disable Play Integrity patch (#6412)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-08 01:06:05 +01:00
Ushie
3d754575a4 ci: Simplify Crowdin translation file destination path (#6463) 2026-01-07 22:31:15 +01:00
semantic-release-bot
a3f7609fe3 chore: Release v5.48.0-dev.8 [skip ci]
# [5.48.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.7...v5.48.0-dev.8) (2026-01-04)

### Features

* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](d25dcfe49a))
2026-01-04 13:17:54 +00:00
Swakshan
d25dcfe49a feat(Letterboxd): Add Unlock app icons patch (#6415) 2026-01-04 14:14:29 +01:00
semantic-release-bot
1cc2cb9cb2 chore: Release v5.48.0-dev.7 [skip ci]
# [5.48.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.6...v5.48.0-dev.7) (2026-01-04)

### Features

* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](f5cbb31724))
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](b42ae27ce6))
2026-01-04 02:43:29 +00:00
xehpuk
f5cbb31724 feat(Strava): Add Disable Quick Edit patch (#6452)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-04 03:38:04 +01:00
xehpuk
b42ae27ce6 feat(Strava): Add Overwrite media upload parameters patch (#6410) 2026-01-04 03:36:08 +01:00
semantic-release-bot
43ab29d03d chore: Release v5.48.0-dev.6 [skip ci]
# [5.48.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.5...v5.48.0-dev.6) (2026-01-04)

### Bug Fixes

* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](789f0a5628))
2026-01-04 02:07:07 +00:00
xehpuk
789f0a5628 fix: Fix build error introduced in 4046bee (#6417) 2026-01-04 03:03:44 +01:00
semantic-release-bot
da836b667c chore: Release v5.48.0-dev.5 [skip ci]
# [5.48.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.4...v5.48.0-dev.5) (2025-12-30)

### Bug Fixes

* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](44e7dbcf4d))
2025-12-30 18:37:28 +00:00
ILoveOpenSourceApplications
44e7dbcf4d fix(Disney+ - Skip ads): Remove unsupported package names (#6422) 2025-12-30 19:32:17 +01:00
semantic-release-bot
195c239000 chore: Release v5.48.0-dev.4 [skip ci]
# [5.48.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.3...v5.48.0-dev.4) (2025-12-29)

### Features

* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](c47beae213))
2025-12-29 21:25:35 +00:00
xehpuk
c47beae213 feat(Strava): Add Block Snowplow tracking patch (#6413)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-29 22:20:26 +01:00
semantic-release-bot
cebcfab86a chore: Release v5.48.0-dev.3 [skip ci]
# [5.48.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.2...v5.48.0-dev.3) (2025-12-28)

### Bug Fixes

* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](71c6cb569e))

### Features

* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](6bb6281149))
2025-12-28 22:33:37 +00:00
xehpuk
71c6cb569e fix: Fix compilation error introduced in 6bb6281 (#6409) 2025-12-28 23:30:32 +01:00
PainfulPaladins
6bb6281149 feat(Instagram - Hides navigation buttons): Add more buttons to hide (#6390) 2025-12-27 18:50:08 +01:00
semantic-release-bot
16bd96e2bb chore: Release v5.48.0-dev.2 [skip ci]
# [5.48.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.1...v5.48.0-dev.2) (2025-12-27)

### Features

* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](8f3f4c95bb))
2025-12-27 17:48:32 +00:00
xehpuk
8f3f4c95bb feat(Strava): Add Enable password login patch (#6396)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-27 18:45:09 +01:00
semantic-release-bot
da02d68587 chore: Release v5.48.0-dev.1 [skip ci]
# [5.48.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0-dev.1) (2025-12-23)

### Bug Fixes

* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](a429824bb7))
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](dc69f2433e))

### Features

* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](e0f33468e6))
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](315931cbf8))
2025-12-23 01:30:13 +00:00
xehpuk
a429824bb7 fix: Fix compilation error introduced in dc69f243 (#6392) 2025-12-23 02:26:54 +01:00
Sylvain Finot
e0f33468e6 feat(ProtonVPN): Add Unlock split tunneling patch (#6353)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-22 15:00:08 +01:00
trespyian
315931cbf8 feat(SBS On Demand): Add Remove ads patch (#6378)
Co-authored-by: Trespyian <trespyian@nowhere.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-22 14:49:19 +01:00
ILoveOpenSourceApplications
dc69f2433e fix(YouTube - Hide layout components): Hide new type of crowdfunding box (#6380) 2025-12-21 23:10:35 +01:00
semantic-release-bot
73e43b2a49 chore: Release v5.47.0 [skip ci]
# [5.47.0](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0) (2025-12-18)

### Bug Fixes

* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](bb745b555b))
* **Lightroom:** Add `Disable version check` patch to fix opening the app  ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](018d176914))
* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](f8bd1239cc))
* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](34830ba63b))
* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](ded8370207))
* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](9495cf49ef))
* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](654d091e65))

### Features

* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](1f4f252c81))
* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](6bd7dca75b))
* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](0ea3491227))
* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](94ae84ad0f))
* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](2f0de15e67))
* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](0928dcd00d))
* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](0af0ee92c4))
* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](847ee189a9))
* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](bbd8932b2e))
* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](ac583d40d0))
* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](a5d197b977))
* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](02831a6069))
* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](a7c220a4ae))
2025-12-18 12:14:21 +00:00
oSumAtrIX
918f04793f chore: Merge branch dev to main (#6282) 2025-12-18 13:10:41 +01:00
semantic-release-bot
f1a9537f01 chore: Release v5.47.0-dev.18 [skip ci]
# [5.47.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.17...v5.47.0-dev.18) (2025-12-18)

### Features

* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](1f4f252c81))
2025-12-18 12:09:57 +00:00
vippium
1f4f252c81 feat(Disney+ - SkipAds): Add other package names the patch is compatible with (#6372) 2025-12-18 12:59:47 +01:00
semantic-release-bot
2b560f5fe9 chore: Release v5.47.0-dev.17 [skip ci]
# [5.47.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.16...v5.47.0-dev.17) (2025-12-18)

### Bug Fixes

* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](f8bd1239cc))
2025-12-18 02:05:14 +00:00
g9q
f8bd1239cc fix(Reddit - Hide ads): Update patch for new versions of Reddit (#6342)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-18 03:01:25 +01:00
semantic-release-bot
c825ebda37 chore: Release v5.47.0-dev.16 [skip ci]
# [5.47.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.15...v5.47.0-dev.16) (2025-12-15)

### Bug Fixes

* **Lightroom:** Add `Disable version check` patch to fix opening the app  ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](018d176914))

### Features

* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](0ea3491227))
2025-12-15 11:34:05 +00:00
oSumAtrIX
255c00b183 chore: Fix minor syntax error 2025-12-15 12:28:53 +01:00
Alex Katlein
0ea3491227 feat(IdAustria - Remove device integrity check): Update patch to work with latest version (#6360)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-15 12:26:59 +01:00
Pun Butrach
5d437b08dd docs: Use American spelling (#6233) 2025-12-14 16:38:55 +01:00
f1re4xx
018d176914 fix(Lightroom): Add Disable version check patch to fix opening the app (#6315)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-14 16:38:26 +01:00
81 changed files with 2359 additions and 326 deletions

View File

@@ -25,7 +25,8 @@ jobs:
- name: Build - name: Build
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid --no-daemon run: ./gradlew :patches:buildAndroid --no-daemon
- name: Upload artifacts - name: Upload artifacts

View File

@@ -2,7 +2,7 @@ name: Pull strings
on: on:
schedule: schedule:
- cron: "0 */12 * * *" - cron: "0 0 * * 0"
workflow_dispatch: workflow_dispatch:
jobs: jobs:

View File

@@ -31,7 +31,8 @@ jobs:
- name: Build - name: Build
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid clean run: ./gradlew :patches:buildAndroid clean
- name: Setup Node.js - name: Setup Node.js

View File

@@ -1,3 +1,136 @@
# [5.48.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.8...v5.48.0-dev.9) (2026-01-08)
### Features
* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](https://github.com/ReVanced/revanced-patches/commit/4cc315952db557c565872de9e8484805f2e42305))
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](https://github.com/ReVanced/revanced-patches/commit/6312fe8d60da24465c0c1b0fa4e94ceb79873d9c))
# [5.48.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.7...v5.48.0-dev.8) (2026-01-04)
### Features
* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](https://github.com/ReVanced/revanced-patches/commit/d25dcfe49ac331c9b3dca739ba0be95dbab669cc))
# [5.48.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.6...v5.48.0-dev.7) (2026-01-04)
### Features
* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](https://github.com/ReVanced/revanced-patches/commit/f5cbb31724d15f7e939b96ee0186fd0a108f9fdc))
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](https://github.com/ReVanced/revanced-patches/commit/b42ae27ce66ebad9e9cfc5b70fc121df5bad7567))
# [5.48.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.5...v5.48.0-dev.6) (2026-01-04)
### Bug Fixes
* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](https://github.com/ReVanced/revanced-patches/commit/789f0a562861825065633d172445ebf35a1ba8d8))
# [5.48.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.4...v5.48.0-dev.5) (2025-12-30)
### Bug Fixes
* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](https://github.com/ReVanced/revanced-patches/commit/44e7dbcf4d7eaf94dd0164baba847d3e19250154))
# [5.48.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.3...v5.48.0-dev.4) (2025-12-29)
### Features
* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](https://github.com/ReVanced/revanced-patches/commit/c47beae21376dd17ab8bc09afe73e9094481bde9))
# [5.48.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.2...v5.48.0-dev.3) (2025-12-28)
### Bug Fixes
* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](https://github.com/ReVanced/revanced-patches/commit/71c6cb569ebf7b93cf73ee391839e5220557ce7c))
### Features
* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](https://github.com/ReVanced/revanced-patches/commit/6bb62811493da04812cc3e392e68d874f95cbef9))
# [5.48.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.1...v5.48.0-dev.2) (2025-12-27)
### Features
* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](https://github.com/ReVanced/revanced-patches/commit/8f3f4c95bb8f151fc9a2c272bf7d0e905c2f01fc))
# [5.48.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0-dev.1) (2025-12-23)
### Bug Fixes
* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](https://github.com/ReVanced/revanced-patches/commit/a429824bb77b49aea14b0b54f2204ae24d5209a1))
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](https://github.com/ReVanced/revanced-patches/commit/dc69f2433e2650654e2dffdd76b0b0c8a52bf515))
### Features
* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](https://github.com/ReVanced/revanced-patches/commit/e0f33468e6e96b9f10cf35ec67622d6488528c90))
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](https://github.com/ReVanced/revanced-patches/commit/315931cbf8f61cd4b3a54ace1ff03685d748614c))
# [5.47.0](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0) (2025-12-18)
### Bug Fixes
* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](https://github.com/ReVanced/revanced-patches/commit/bb745b555b3808b7679c5995319aa365630fbd76))
* **Lightroom:** Add `Disable version check` patch to fix opening the app ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](https://github.com/ReVanced/revanced-patches/commit/018d176914a06a30e9007a3eb2e6b0f459078413))
* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](https://github.com/ReVanced/revanced-patches/commit/f8bd1239cc0f0bd1c2dca39f846951bf512891e3))
* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](https://github.com/ReVanced/revanced-patches/commit/34830ba63b436146064f0f89f948d51cd0cb9146))
* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](https://github.com/ReVanced/revanced-patches/commit/ded83702077701aac8a8749d71bf7376427f37d6))
* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](https://github.com/ReVanced/revanced-patches/commit/9495cf49ef8a872be64de6c971c1919b4b9a8720))
* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](https://github.com/ReVanced/revanced-patches/commit/654d091e650cda37650b57cbf3ba6f1cdd6d47d3))
### Features
* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](https://github.com/ReVanced/revanced-patches/commit/1f4f252c81e9a89267f6e37548e66027b1bc1a1a))
* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](https://github.com/ReVanced/revanced-patches/commit/6bd7dca75bd2ea335a596aa93a8b767d39be5f83))
* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](https://github.com/ReVanced/revanced-patches/commit/0ea3491227fc50c03555d43d3fec78eb82906b26))
* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](https://github.com/ReVanced/revanced-patches/commit/94ae84ad0fc3a9197c82d5356301d464730c3b17))
* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](https://github.com/ReVanced/revanced-patches/commit/2f0de15e67e4f99ed6ecdc136d04cceb23b0d069))
* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](https://github.com/ReVanced/revanced-patches/commit/0928dcd00dc2a9c1eef9a23c1e26ff5dc9ee670a))
* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](https://github.com/ReVanced/revanced-patches/commit/0af0ee92c48bb2ffc332197e05439e20c5c05d83))
* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](https://github.com/ReVanced/revanced-patches/commit/847ee189a971e6d4a99823998569f8e561b8319c))
* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](https://github.com/ReVanced/revanced-patches/commit/bbd8932b2e740aff96ba047332e541bff3e09436))
* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](https://github.com/ReVanced/revanced-patches/commit/ac583d40d0f4c0e6544e3661ff3e82a25912f2b0))
* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](https://github.com/ReVanced/revanced-patches/commit/a5d197b9775b98d7a37bfdee9e5f726d5e04d8cf))
* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](https://github.com/ReVanced/revanced-patches/commit/02831a6069fc30ffa3a87f8e4de653d003a2187e))
* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](https://github.com/ReVanced/revanced-patches/commit/a7c220a4aea93ea7ae7005b5760443d7571c4228))
# [5.47.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.17...v5.47.0-dev.18) (2025-12-18)
### Features
* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](https://github.com/ReVanced/revanced-patches/commit/1f4f252c81e9a89267f6e37548e66027b1bc1a1a))
# [5.47.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.16...v5.47.0-dev.17) (2025-12-18)
### Bug Fixes
* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](https://github.com/ReVanced/revanced-patches/commit/f8bd1239cc0f0bd1c2dca39f846951bf512891e3))
# [5.47.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.15...v5.47.0-dev.16) (2025-12-15)
### Bug Fixes
* **Lightroom:** Add `Disable version check` patch to fix opening the app ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](https://github.com/ReVanced/revanced-patches/commit/018d176914a06a30e9007a3eb2e6b0f459078413))
### Features
* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](https://github.com/ReVanced/revanced-patches/commit/0ea3491227fc50c03555d43d3fec78eb82906b26))
# [5.47.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.14...v5.47.0-dev.15) (2025-12-13) # [5.47.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.14...v5.47.0-dev.15) (2025-12-13)

View File

@@ -97,9 +97,9 @@ Thank you for considering contributing to ReVanced Patches. You can find the con
To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation). To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
## 📜 Licence ## 📜 License
ReVanced Patches is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information. ReVanced Patches is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information.
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patches as long as you track changes/dates in source files. [tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patches as long as you track changes/dates in source files.
Any modifications to ReVanced Patches must also be made available under the GPL, Any modifications to ReVanced Patches must also be made available under the GPL,
along with build & install instructions. along with build & install instructions.

View File

@@ -1,8 +1,9 @@
project_id_env: "CROWDIN_PROJECT_ID" project_id_env: "CROWDIN_PROJECT_ID"
api_token_env: "CROWDIN_PERSONAL_TOKEN" api_token_env: "CROWDIN_PERSONAL_TOKEN"
preserve_hierarchy: false preserve_hierarchy: true
files: files:
- source: patches/src/main/resources/addresources/values/strings.xml - source: patches/src/main/resources/addresources/values/strings.xml
dest: patches.xml
translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml
skip_untranslated_strings: true skip_untranslated_strings: true

View File

@@ -0,0 +1,20 @@
android {
namespace = "app.revanced.extension"
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildFeatures {
aidl = true
}
}
dependencies {
compileOnly(libs.annotation)
}

View File

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

View File

@@ -0,0 +1,8 @@
package com.google.android.play.core.integrity.protocol;
import android.os.Bundle;
import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback;
interface IExpressIntegrityService {
oneway void requestIntegrityToken(in Bundle request, IExpressIntegrityServiceCallback callback) = 2;
}

View File

@@ -0,0 +1,5 @@
package com.google.android.play.core.integrity.protocol;
interface IExpressIntegrityServiceCallback {
oneway void onRequestExpressIntegrityTokenResult(in Bundle result) = 2;
}

View File

@@ -0,0 +1,8 @@
package com.google.android.play.core.integrity.protocol;
import android.os.Bundle;
import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback;
interface IIntegrityService {
oneway void requestIntegrityToken(in Bundle request, IIntegrityServiceCallback callback) = 1;
}

View File

@@ -0,0 +1,7 @@
package com.google.android.play.core.integrity.protocol;
import android.os.Bundle;
interface IIntegrityServiceCallback {
oneway void onResult(in Bundle result) = 1;
}

View File

@@ -0,0 +1,10 @@
package android.ext;
/** @hide */
// Int values that are assigned to packages in this interface can be retrieved at runtime from
// ApplicationInfo.ext().getPackageId() or from AndroidPackage.ext().getPackageId() (in system_server).
//
// PackageIds are assigned to parsed APKs only after they are verified, either by a certificate check
// or by a check that the APK is stored on an immutable OS partition.
public interface PackageId {
String PLAY_STORE_NAME = "com.android.vending";
}

View File

@@ -0,0 +1,62 @@
package android.os;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.FileDescriptor;
/** @hide */
public class BinderWrapper implements IBinder {
protected final IBinder base;
public BinderWrapper(IBinder base) {
this.base = base;
}
@Override
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
return base.transact(code, data, reply, flags);
}
@Nullable
@Override
public IInterface queryLocalInterface(@NonNull String descriptor) {
return base.queryLocalInterface(descriptor);
}
@Nullable
@Override
public String getInterfaceDescriptor() throws RemoteException {
return base.getInterfaceDescriptor();
}
@Override
public boolean pingBinder() {
return base.pingBinder();
}
@Override
public boolean isBinderAlive() {
return base.isBinderAlive();
}
@Override
public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException {
base.dump(fd, args);
}
@Override
public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException {
base.dumpAsync(fd, args);
}
@Override
public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException {
base.linkToDeath(recipient, flags);
}
@Override
public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
return base.unlinkToDeath(recipient, flags);
}
}

View File

@@ -0,0 +1,41 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.os.FakeBackgroundHandler;
import com.google.android.play.core.integrity.protocol.IIntegrityService;
import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback;
class ClassicPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper {
ClassicPlayIntegrityServiceWrapper(IBinder base) {
super(base);
requestIntegrityTokenTxnCode = 2; // IIntegrityService.Stub.TRANSACTION_requestIntegrityToken
}
static class TokenRequestStub extends IIntegrityService.Stub {
public void requestIntegrityToken(Bundle request, IIntegrityServiceCallback callback) {
Runnable r = () -> {
var result = new Bundle();
// https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/IntegrityErrorCode.html#API_NOT_AVAILABLE
final int API_NOT_AVAILABLE = -1;
result.putInt("error", API_NOT_AVAILABLE);
try {
callback.onResult(result);
} catch (RemoteException e) {
Log.e("IIntegrityService.Stub", "", e);
}
};
FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay());
}
};
@Override
protected Binder createTokenRequestStub() {
return new TokenRequestStub();
}
}

View File

@@ -0,0 +1,48 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.os.Binder;
import android.os.BinderWrapper;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.Nullable;
abstract class PlayIntegrityServiceWrapper extends BinderWrapper {
final String TAG;
protected int requestIntegrityTokenTxnCode;
public PlayIntegrityServiceWrapper(IBinder base) {
super(base);
TAG = getClass().getSimpleName();
}
protected abstract Binder createTokenRequestStub();
@Override
public boolean transact(int code, Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
if (code == requestIntegrityTokenTxnCode) {
if (maybeStubOutIntegrityTokenRequest(code, data, reply, flags)) {
return true;
}
}
return super.transact(code, data, reply, flags);
}
private boolean maybeStubOutIntegrityTokenRequest(int code, Parcel data, @Nullable Parcel reply, int flags) {
Log.d(TAG, "integrity token request detected");
try {
createTokenRequestStub().transact(code, data, reply, flags);
} catch (RemoteException e) {
// this is a local call
throw new IllegalStateException(e);
}
return true;
}
protected static long getTokenRequestResultDelay() {
return 500L;
}
}

View File

@@ -0,0 +1,35 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.content.Intent;
import android.content.ServiceConnection;
import android.ext.PackageId;
import android.os.IBinder;
import androidx.annotation.Nullable;
import app.grapheneos.gmscompat.lib.util.ServiceConnectionWrapper;
import java.util.function.UnaryOperator;
public class PlayIntegrityUtils {
public static @Nullable ServiceConnection maybeReplaceServiceConnection(Intent service, ServiceConnection orig) {
if (PackageId.PLAY_STORE_NAME.equals(service.getPackage())) {
UnaryOperator<IBinder> binderOverride = null;
final String CLASSIC_SERVICE =
"com.google.android.play.core.integrityservice.BIND_INTEGRITY_SERVICE";
final String STANDARD_SERVICE =
"com.google.android.play.core.expressintegrityservice.BIND_EXPRESS_INTEGRITY_SERVICE";
String action = service.getAction();
if (STANDARD_SERVICE.equals(action)) {
binderOverride = StandardPlayIntegrityServiceWrapper::new;
} else if (CLASSIC_SERVICE.equals(action)) {
binderOverride = ClassicPlayIntegrityServiceWrapper::new;
}
if (binderOverride != null) {
return new ServiceConnectionWrapper(orig, binderOverride);
}
}
return null;
}
}

View File

@@ -0,0 +1,42 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.annotation.SuppressLint;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.os.FakeBackgroundHandler;
import com.google.android.play.core.integrity.protocol.IExpressIntegrityService;
import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback;
@SuppressLint("LongLogTag")
class StandardPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper {
StandardPlayIntegrityServiceWrapper(IBinder base) {
super(base);
requestIntegrityTokenTxnCode = 3; // IExpressIntegrityService.Stub.TRANSACTION_requestIntegrityToken
}
static class TokenRequestStub extends IExpressIntegrityService.Stub {
public void requestIntegrityToken(Bundle request, IExpressIntegrityServiceCallback callback) {
Runnable r = () -> {
var result = new Bundle();
// https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/StandardIntegrityErrorCode.html#API_NOT_AVAILABLE
final int API_NOT_AVAILABLE = -1;
result.putInt("error", API_NOT_AVAILABLE);
try {
callback.onRequestExpressIntegrityTokenResult(result);
} catch (RemoteException e) {
Log.e("IExpressIntegrityService.Stub", "", e);
}
};
FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay());
}
};
@Override
protected Binder createTokenRequestStub() {
return new TokenRequestStub();
}
}

View File

@@ -0,0 +1,49 @@
package app.grapheneos.gmscompat.lib.util;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;
import java.util.function.UnaryOperator;
public class ServiceConnectionWrapper implements ServiceConnection {
private final ServiceConnection base;
private final UnaryOperator<IBinder> binderOverride;
public ServiceConnectionWrapper(ServiceConnection base, UnaryOperator<IBinder> binderOverride) {
this.base = base;
this.binderOverride = binderOverride;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
IBinder override = binderOverride.apply(service);
if (override != null) {
service = override;
}
}
base.onServiceConnected(name, service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
base.onServiceDisconnected(name);
}
@Override
public void onBindingDied(ComponentName name) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
base.onBindingDied(name);
}
}
@Override
public void onNullBinding(ComponentName name) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
base.onNullBinding(name);
}
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.extension.playintegrity;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import app.grapheneos.gmscompat.lib.playintegrity.PlayIntegrityUtils;
public class DisablePlayIntegrityPatch {
public static boolean bindService(Context context, Intent service, ServiceConnection conn, int flags) {
ServiceConnection override = PlayIntegrityUtils.maybeReplaceServiceConnection(service, conn);
if (override != null) {
conn = override;
}
return context.bindService(service, conn, flags);
}
}

View File

@@ -0,0 +1,11 @@
package com.android.internal.os;
import android.os.Handler;
import android.os.Looper;
public class FakeBackgroundHandler {
public static Handler getHandler() {
return new Handler(Looper.getMainLooper());
}
}

View File

@@ -311,6 +311,10 @@ public class Utils {
return resourceId; return resourceId;
} }
public static String getResourceString(int id) throws Resources.NotFoundException {
return getContext().getResources().getString(id);
}
public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException { public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getInteger(getResourceIdentifierOrThrow(resourceIdentifierName, "integer")); return getContext().getResources().getInteger(getResourceIdentifierOrThrow(resourceIdentifierName, "integer"));
} }

View File

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

View File

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

View File

@@ -0,0 +1,216 @@
package app.revanced.extension.strava;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
import com.strava.core.data.MediaType;
import com.strava.photos.data.Media;
import okhttp3.*;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import app.revanced.extension.shared.Utils;
@SuppressLint("NewApi")
public final class AddMediaDownloadPatch {
public static final int ACTION_DOWNLOAD = -1;
public static final int ACTION_OPEN_LINK = -2;
public static final int ACTION_COPY_LINK = -3;
private static final OkHttpClient client = new OkHttpClient();
public static boolean handleAction(int actionId, Media media) {
String url = getUrl(media);
switch (actionId) {
case ACTION_DOWNLOAD:
String name = media.getId();
if (media.getType() == MediaType.VIDEO) {
downloadVideo(url, name);
} else {
downloadPhoto(url, name);
}
return true;
case ACTION_OPEN_LINK:
Utils.openLink(url);
return true;
case ACTION_COPY_LINK:
copyLink(url);
return true;
default:
return false;
}
}
public static void copyLink(CharSequence url) {
Utils.setClipboard(url);
showInfoToast("link_copied_to_clipboard", "🔗");
}
public static void downloadPhoto(String url, String name) {
showInfoToast("loading", "");
Utils.runOnBackgroundThread(() -> {
try (Response response = fetch(url)) {
ResponseBody body = response.body();
String mimeType = body.contentType().toString();
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
ContentResolver resolver = Utils.getContext().getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, name + '.' + extension);
values.put(MediaStore.Images.Media.IS_PENDING, 1);
values.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/Strava");
Uri collection = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
? MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
: MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Uri row = resolver.insert(collection, values);
try (OutputStream outputStream = resolver.openOutputStream(row)) {
transferTo(body.byteStream(), outputStream);
} finally {
values.clear();
values.put(MediaStore.Images.Media.IS_PENDING, 0);
resolver.update(row, values, null);
}
showInfoToast("yis_2024_local_save_image_success", "✔️");
} catch (IOException e) {
showErrorToast("download_failure", "", e);
}
});
}
/**
* Downloads a video in the M3U8 / HLS (HTTP Live Streaming) format.
*/
public static void downloadVideo(String url, String name) {
// The first request yields multiple URLs with different stream options.
// In case of Strava, the first one is always of highest quality.
// Each stream can consist of multiple chunks.
// The second request yields the URLs of all of these chunks.
// Fetch all of them concurrently and pipe their streams into the file in order.
showInfoToast("loading", "");
Utils.runOnBackgroundThread(() -> {
try {
String highestQualityStreamUrl;
try (Response response = fetch(url)) {
highestQualityStreamUrl = replaceFileName(url, lines(response).findFirst().get());
}
List<Future<Response>> futures;
try (Response response = fetch(highestQualityStreamUrl)) {
futures = lines(response)
.map(line -> replaceFileName(highestQualityStreamUrl, line))
.map(chunkUrl -> Utils.submitOnBackgroundThread(() -> fetch(chunkUrl)))
.collect(Collectors.toList());
}
ContentResolver resolver = Utils.getContext().getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DISPLAY_NAME, name + '.' + "mp4");
values.put(MediaStore.Video.Media.IS_PENDING, 1);
values.put(MediaStore.Video.Media.MIME_TYPE, MimeTypeMap.getSingleton().getMimeTypeFromExtension("mp4"));
values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES + "/Strava");
Uri collection = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
? MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
: MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
Uri row = resolver.insert(collection, values);
try (OutputStream outputStream = resolver.openOutputStream(row)) {
Throwable error = null;
for (Future<Response> future : futures) {
if (error != null) {
if (future.cancel(true)) {
continue;
}
}
try (Response response = future.get()) {
if (error == null) {
transferTo(response.body().byteStream(), outputStream);
}
} catch (InterruptedException | IOException e) {
error = e;
} catch (ExecutionException e) {
error = e.getCause();
}
}
if (error != null) {
throw new IOException(error);
}
} finally {
values.clear();
values.put(MediaStore.Video.Media.IS_PENDING, 0);
resolver.update(row, values, null);
}
showInfoToast("yis_2024_local_save_video_success", "✔️");
} catch (IOException e) {
showErrorToast("download_failure", "", e);
}
});
}
private static String getUrl(Media media) {
return media.getType() == MediaType.VIDEO
? ((Media.Video) media).getVideoUrl()
: media.getLargestUrl();
}
private static String getString(String name, String fallback) {
int id = Utils.getResourceIdentifier(name, "string");
return id != 0
? Utils.getResourceString(id)
: fallback;
}
private static void showInfoToast(String resourceName, String fallback) {
String text = getString(resourceName, fallback);
Utils.showToastShort(text);
}
private static void showErrorToast(String resourceName, String fallback, IOException exception) {
String text = getString(resourceName, fallback);
Utils.showToastLong(text + ' ' + exception.getLocalizedMessage());
}
private static Response fetch(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("Got HTTP status code " + response.code());
}
return response;
}
/**
* {@code inputStream.transferTo(outputStream)} is "too new".
*/
private static void transferTo(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024 * 8];
int length;
while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
}
/**
* Gets all file names.
*/
private static Stream<String> lines(Response response) {
BufferedReader reader = new BufferedReader(response.body().charStream());
return reader.lines().filter(line -> !line.startsWith("#"));
}
private static String replaceFileName(String uri, String newName) {
return uri.substring(0, uri.lastIndexOf('/') + 1) + newName;
}
}

View File

@@ -0,0 +1,12 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 21
}
}

View File

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

View File

@@ -0,0 +1,15 @@
package com.strava.core.data;
import java.io.Serializable;
public interface MediaContent extends Serializable {
String getCaption();
String getId();
String getReferenceId();
MediaType getType();
void setCaption(String caption);
}

View File

@@ -0,0 +1,44 @@
package com.strava.core.data;
import java.io.Serializable;
public final class MediaDimension implements Comparable<MediaDimension>, Serializable {
private final int height;
private final int width;
public MediaDimension(int width, int height) {
this.width = width;
this.height = height;
}
public int getHeight() {
return height;
}
public float getHeightScale() {
if (width <= 0 || height <= 0) {
return 1f;
}
return height / width;
}
public int getWidth() {
return width;
}
public float getWidthScale() {
if (width <= 0 || height <= 0) {
return 1f;
}
return width / height;
}
public boolean isLandscape() {
return width > 0 && width >= height;
}
@Override
public int compareTo(MediaDimension other) {
return 0;
}
}

View File

@@ -0,0 +1,16 @@
package com.strava.core.data;
public enum MediaType {
PHOTO(1),
VIDEO(2);
private final int remoteValue;
private MediaType(int remoteValue) {
this.remoteValue = remoteValue;
}
public int getRemoteValue() {
return remoteValue;
}
}

View File

@@ -0,0 +1,17 @@
package com.strava.core.data;
import java.util.SortedMap;
public interface RemoteMediaContent extends MediaContent {
MediaDimension getLargestSize();
String getLargestUrl();
SortedMap<Integer, MediaDimension> getSizes();
String getSmallestUrl();
RemoteMediaStatus getStatus();
SortedMap<Integer, String> getUrls();
}

View File

@@ -0,0 +1,11 @@
package com.strava.core.data;
public enum RemoteMediaStatus {
NEW,
PENDING,
PROCESSED,
REPORTED,
REINSTATED,
DELETED,
FAILED
}

View File

@@ -0,0 +1,286 @@
package com.strava.photos.data;
import com.strava.core.data.MediaDimension;
import com.strava.core.data.MediaType;
import com.strava.core.data.RemoteMediaContent;
import com.strava.core.data.RemoteMediaStatus;
import java.util.SortedMap;
public abstract class Media implements RemoteMediaContent {
public static final class Photo extends Media {
private final Long activityId;
private final String activityName;
private final long athleteId;
private String caption;
private final String createdAt;
private final String createdAtLocal;
private final String cursor;
private final String id;
private final SortedMap<Integer, MediaDimension> sizes;
private final RemoteMediaStatus status;
private final String tag;
private final MediaType type;
private final SortedMap<Integer, String> urls;
@Override
public Long getActivityId() {
return activityId;
}
@Override
public String getActivityName() {
return activityName;
}
@Override
public long getAthleteId() {
return athleteId;
}
@Override
public String getCaption() {
return caption;
}
@Override
public String getCreatedAt() {
return createdAt;
}
@Override
public String getCreatedAtLocal() {
return createdAtLocal;
}
@Override
public String getCursor() {
return cursor;
}
@Override
public String getId() {
return id;
}
@Override
public SortedMap<Integer, MediaDimension> getSizes() {
return sizes;
}
@Override
public RemoteMediaStatus getStatus() {
return status;
}
@Override
public String getTag() {
return tag;
}
@Override
public MediaType getType() {
return type;
}
@Override
public SortedMap<Integer, String> getUrls() {
return urls;
}
@Override
public void setCaption(String caption) {
this.caption = caption;
}
public Photo(String id,
String caption,
SortedMap<Integer, String> urls,
SortedMap<Integer, MediaDimension> sizes,
long athleteId,
String createdAt,
String createdAtLocal,
Long activityId,
String activityName,
RemoteMediaStatus status,
String tag,
String cursor) {
this.id = id;
this.caption = caption;
this.urls = urls;
this.sizes = sizes;
this.athleteId = athleteId;
this.createdAt = createdAt;
this.createdAtLocal = createdAtLocal;
this.activityId = activityId;
this.activityName = activityName;
this.status = status;
this.tag = tag;
this.cursor = cursor;
this.type = MediaType.PHOTO;
}
}
public static final class Video extends Media {
private final Long activityId;
private final String activityName;
private final long athleteId;
private String caption;
private final String createdAt;
private final String createdAtLocal;
private final String cursor;
private final Float durationSeconds;
private final String id;
private final SortedMap<Integer, MediaDimension> sizes;
private final RemoteMediaStatus status;
private final String tag;
private final MediaType type;
private final SortedMap<Integer, String> urls;
private final String videoUrl;
@Override
public Long getActivityId() {
return activityId;
}
@Override
public String getActivityName() {
return activityName;
}
@Override
public long getAthleteId() {
return athleteId;
}
@Override
public String getCaption() {
return caption;
}
@Override
public String getCreatedAt() {
return createdAt;
}
@Override
public String getCreatedAtLocal() {
return createdAtLocal;
}
@Override
public String getCursor() {
return cursor;
}
public final Float getDurationSeconds() {
return durationSeconds;
}
@Override
public String getId() {
return id;
}
@Override
public SortedMap<Integer, MediaDimension> getSizes() {
return sizes;
}
@Override
public RemoteMediaStatus getStatus() {
return status;
}
@Override
public String getTag() {
return tag;
}
@Override
public MediaType getType() {
return type;
}
@Override
public SortedMap<Integer, String> getUrls() {
return urls;
}
public final String getVideoUrl() {
return videoUrl;
}
@Override
public void setCaption(String caption) {
this.caption = caption;
}
public Video(String id,
String caption,
SortedMap<Integer, String> urls,
SortedMap<Integer, MediaDimension> sizes,
long athleteId,
String createdAt,
String createdAtLocal,
Long activityId,
String activityName,
RemoteMediaStatus status,
String videoUrl,
Float durationSeconds,
String tag,
String cursor) {
this.id = id;
this.caption = caption;
this.urls = urls;
this.sizes = sizes;
this.athleteId = athleteId;
this.createdAt = createdAt;
this.createdAtLocal = createdAtLocal;
this.activityId = activityId;
this.activityName = activityName;
this.status = status;
this.videoUrl = videoUrl;
this.durationSeconds = durationSeconds;
this.tag = tag;
this.cursor = cursor;
this.type = MediaType.VIDEO;
}
}
public abstract Long getActivityId();
public abstract String getActivityName();
public abstract long getAthleteId();
public abstract String getCreatedAt();
public abstract String getCreatedAtLocal();
public abstract String getCursor();
@Override
public MediaDimension getLargestSize() {
return null;
}
@Override
public String getLargestUrl() {
return null;
}
@Override
public String getReferenceId() {
return null;
}
@Override
public String getSmallestUrl() {
return null;
}
public abstract String getTag();
private Media() {
}
}

View File

@@ -0,0 +1,30 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class PauseOnAudioInterruptPatch {
private static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = -3;
private static final int AUDIOFOCUS_LOSS_TRANSIENT = -2;
/**
* Injection point for AudioFocusRequest builder.
* Returns true if audio ducking should be disabled (willPauseWhenDucked = true).
*/
public static boolean shouldPauseOnAudioInterrupt() {
return Settings.PAUSE_ON_AUDIO_INTERRUPT.get();
}
/**
* Injection point for onAudioFocusChange callback.
* Converts AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK to AUDIOFOCUS_LOSS_TRANSIENT
* when the setting is enabled, causing YouTube to pause instead of ducking.
*/
public static int overrideAudioFocusChange(int focusChange) {
if (Settings.PAUSE_ON_AUDIO_INTERRUPT.get() && focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
return AUDIOFOCUS_LOSS_TRANSIENT;
}
return focusChange;
}
}

View File

@@ -247,8 +247,13 @@ public final class LayoutComponentsFilter extends Filter {
"sponsorships" "sponsorships"
); );
final var crowdfundingBox = new StringFilterGroup(
Settings.HIDE_CROWDFUNDING_BOX,
"donation_shelf"
);
final var channelWatermark = new StringFilterGroup( final var channelWatermark = new StringFilterGroup(
Settings.HIDE_VIDEO_CHANNEL_WATERMARK, Settings.HIDE_CHANNEL_WATERMARK,
"featured_channel_watermark_overlay" "featured_channel_watermark_overlay"
); );
@@ -312,6 +317,7 @@ public final class LayoutComponentsFilter extends Filter {
compactChannelBar, compactChannelBar,
compactChannelBarInner, compactChannelBarInner,
communityPosts, communityPosts,
crowdfundingBox,
emergencyBox, emergencyBox,
expandableMetadata, expandableMetadata,
forYouShelf, forYouShelf,
@@ -427,7 +433,7 @@ public final class LayoutComponentsFilter extends Filter {
* Injection point. * Injection point.
*/ */
public static boolean showWatermark() { public static boolean showWatermark() {
return !Settings.HIDE_VIDEO_CHANNEL_WATERMARK.get(); return !Settings.HIDE_CHANNEL_WATERMARK.get();
} }
/** /**

View File

@@ -96,7 +96,6 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_CHIPS_SHELF = new BooleanSetting("revanced_hide_chips_shelf", TRUE); public static final BooleanSetting HIDE_CHIPS_SHELF = new BooleanSetting("revanced_hide_chips_shelf", TRUE);
public static final BooleanSetting HIDE_COMMUNITY_POSTS = new BooleanSetting("revanced_hide_community_posts", FALSE); public static final BooleanSetting HIDE_COMMUNITY_POSTS = new BooleanSetting("revanced_hide_community_posts", FALSE);
public static final BooleanSetting HIDE_COMPACT_BANNER = new BooleanSetting("revanced_hide_compact_banner", TRUE); public static final BooleanSetting HIDE_COMPACT_BANNER = new BooleanSetting("revanced_hide_compact_banner", TRUE);
public static final BooleanSetting HIDE_CROWDFUNDING_BOX = new BooleanSetting("revanced_hide_crowdfunding_box", FALSE, true);
public static final BooleanSetting HIDE_DOODLES = new BooleanSetting("revanced_hide_doodles", FALSE, true, "revanced_hide_doodles_user_dialog_message"); public static final BooleanSetting HIDE_DOODLES = new BooleanSetting("revanced_hide_doodles", FALSE, true, "revanced_hide_doodles_user_dialog_message");
public static final BooleanSetting HIDE_EXPANDABLE_CARD = new BooleanSetting("revanced_hide_expandable_card", TRUE); public static final BooleanSetting HIDE_EXPANDABLE_CARD = new BooleanSetting("revanced_hide_expandable_card", TRUE);
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_FEED = new BooleanSetting("revanced_hide_filter_bar_feed_in_feed", FALSE, true); public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_FEED = new BooleanSetting("revanced_hide_filter_bar_feed_in_feed", FALSE, true);
@@ -158,6 +157,8 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE); public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true); public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE); public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE);
public static final BooleanSetting HIDE_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE);
public static final BooleanSetting HIDE_CROWDFUNDING_BOX = new BooleanSetting("revanced_hide_crowdfunding_box", FALSE, true);
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE); public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", FALSE); public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", FALSE);
public static final BooleanSetting HIDE_END_SCREEN_SUGGESTED_VIDEO = new BooleanSetting("revanced_end_screen_suggested_video", FALSE, true); public static final BooleanSetting HIDE_END_SCREEN_SUGGESTED_VIDEO = new BooleanSetting("revanced_end_screen_suggested_video", FALSE, true);
@@ -172,7 +173,6 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_RELATED_VIDEOS = new BooleanSetting("revanced_hide_related_videos", FALSE); public static final BooleanSetting HIDE_RELATED_VIDEOS = new BooleanSetting("revanced_hide_related_videos", FALSE);
public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE); public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE);
public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE); public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE);
public static final BooleanSetting HIDE_VIDEO_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE);
public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE); public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE);
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE); public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
public static final BooleanSetting VIDEO_QUALITY_DIALOG_BUTTON = new BooleanSetting("revanced_video_quality_dialog_button", FALSE); public static final BooleanSetting VIDEO_QUALITY_DIALOG_BUTTON = new BooleanSetting("revanced_video_quality_dialog_button", FALSE);
@@ -356,6 +356,7 @@ public class Settings extends BaseSettings {
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false); public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false);
public static final BooleanSetting LOOP_VIDEO = new BooleanSetting("revanced_loop_video", FALSE); public static final BooleanSetting LOOP_VIDEO = new BooleanSetting("revanced_loop_video", FALSE);
public static final BooleanSetting LOOP_VIDEO_BUTTON = new BooleanSetting("revanced_loop_video_button", FALSE); public static final BooleanSetting LOOP_VIDEO_BUTTON = new BooleanSetting("revanced_loop_video_button", FALSE);
public static final BooleanSetting PAUSE_ON_AUDIO_INTERRUPT = new BooleanSetting("revanced_pause_on_audio_interrupt", FALSE, true);
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE); public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_CHAPTERS = new BooleanSetting("revanced_disable_haptic_feedback_chapters", FALSE); public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_CHAPTERS = new BooleanSetting("revanced_disable_haptic_feedback_chapters", FALSE);
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING = new BooleanSetting("revanced_disable_haptic_feedback_precise_seeking", FALSE); public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING = new BooleanSetting("revanced_disable_haptic_feedback_precise_seeking", FALSE);

View File

@@ -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.47.0-dev.15 version = 5.48.0-dev.9

View File

@@ -104,6 +104,10 @@ public final class app/revanced/patches/all/misc/packagename/ChangePackageNamePa
public static final fun setPackageNameOption (Lapp/revanced/patcher/patch/Option;)V public static final fun setPackageNameOption (Lapp/revanced/patcher/patch/Option;)V
} }
public final class app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrityKt {
public static final fun getDisablePlayIntegrityPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/all/misc/resources/AddResourcesPatchKt { public final class app/revanced/patches/all/misc/resources/AddResourcesPatchKt {
public static final fun addResource (Ljava/lang/String;Lapp/revanced/util/resource/BaseResource;)Z public static final fun addResource (Ljava/lang/String;Lapp/revanced/util/resource/BaseResource;)Z
public static final fun addResources (Lapp/revanced/patcher/patch/Patch;Lkotlin/jvm/functions/Function1;)Z public static final fun addResources (Lapp/revanced/patcher/patch/Patch;Lkotlin/jvm/functions/Function1;)Z
@@ -176,6 +180,10 @@ public final class app/revanced/patches/cieid/restrictions/root/BypassRootChecks
public static final fun getBypassRootChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getBypassRootChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/com/sbs/ondemand/tv/RemoveAdsPatchKt {
public static final fun getRemoveAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/cricbuzz/ads/DisableAdsPatchKt { public final class app/revanced/patches/cricbuzz/ads/DisableAdsPatchKt {
public static final fun getDisableAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getDisableAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -268,6 +276,10 @@ public final class app/revanced/patches/iconpackstudio/misc/pro/UnlockProPatchKt
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/idaustria/detection/deviceintegrity/RemoveDeviceIntegrityChecksPatchKt {
public static final fun getRemoveDeviceIntegrityChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/idaustria/detection/root/RootDetectionPatchKt { public final class app/revanced/patches/idaustria/detection/root/RootDetectionPatchKt {
public static final fun getRootDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getRootDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -344,6 +356,10 @@ public final class app/revanced/patches/letterboxd/ads/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/letterboxd/unlock/unlockAppIcons/UnlockAppIconsPatchKt {
public static final fun getUnlockAppIconsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/lightroom/misc/login/DisableMandatoryLoginPatchKt { public final class app/revanced/patches/lightroom/misc/login/DisableMandatoryLoginPatchKt {
public static final fun getDisableMandatoryLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getDisableMandatoryLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -352,6 +368,10 @@ public final class app/revanced/patches/lightroom/misc/premium/UnlockPremiumPatc
public static final fun getUnlockPremiumPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getUnlockPremiumPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/lightroom/misc/version/DisableVersionCheckPatchKt {
public static final fun getDisableVersionCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/memegenerator/detection/license/LicenseValidationPatchKt { public final class app/revanced/patches/memegenerator/detection/license/LicenseValidationPatchKt {
public static final fun getLicenseValidationPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getLicenseValidationPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -637,6 +657,10 @@ public final class app/revanced/patches/protonvpn/delay/RemoveDelayPatchKt {
public static final fun getRemoveDelayPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getRemoveDelayPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/protonvpn/splittunneling/UnlockSplitTunnelingKt {
public static final fun getUnlockSplitTunnelingPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/rar/misc/annoyances/purchasereminder/HidePurchaseReminderPatchKt { public final class app/revanced/patches/rar/misc/annoyances/purchasereminder/HidePurchaseReminderPatchKt {
public static final fun getHidePurchaseReminderPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getHidePurchaseReminderPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -920,6 +944,10 @@ public final class app/revanced/patches/shared/misc/pairip/license/DisableLicens
public static final fun getDisableLicenseCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getDisableLicenseCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/shared/misc/privacy/DisableSentryTelemetryKt {
public static final fun getDisableSentryTelemetryPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/shared/misc/settings/SettingsPatchKt { public final class app/revanced/patches/shared/misc/settings/SettingsPatchKt {
public static final fun overrideThemeColors (Ljava/lang/String;Ljava/lang/String;)V public static final fun overrideThemeColors (Ljava/lang/String;Ljava/lang/String;)V
public static final fun settingsPatch (Ljava/util/List;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch; public static final fun settingsPatch (Ljava/util/List;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch;
@@ -1172,6 +1200,30 @@ public final class app/revanced/patches/stocard/layout/HideStoryBubblesPatchKt {
public static final fun getHideStoryBubblesPatch ()Lapp/revanced/patcher/patch/ResourcePatch; public static final fun getHideStoryBubblesPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
} }
public final class app/revanced/patches/strava/media/download/AddMediaDownloadPatchKt {
public static final fun getAddMediaDownloadPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/strava/media/upload/OverwriteMediaUploadParametersPatchKt {
public static final fun getOverwriteMediaUploadParametersPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/strava/misc/extension/SharedExtensionPatchKt {
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/strava/password/EnablePasswordLoginPatchKt {
public static final fun getEnablePasswordLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/strava/privacy/BlockSnowplowTrackingPatchKt {
public static final fun getBlockSnowplowTrackingPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/strava/quickedit/DisableQuickEditPatchKt {
public static final fun getDisableQuickEditPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/strava/subscription/UnlockSubscriptionPatchKt { public final class app/revanced/patches/strava/subscription/UnlockSubscriptionPatchKt {
public static final fun getUnlockSubscriptionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getUnlockSubscriptionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -1652,6 +1704,10 @@ public final class app/revanced/patches/youtube/misc/announcements/Announcements
public static final fun getAnnouncementsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getAnnouncementsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatchKt {
public static final fun getPauseOnAudioInterruptPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/misc/autorepeat/AutoRepeatPatchKt { public final class app/revanced/patches/youtube/misc/autorepeat/AutoRepeatPatchKt {
public static final fun getAutoRepeatPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getAutoRepeatPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -1947,6 +2003,7 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun indexOfFirstResourceIdOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun indexOfFirstResourceIdOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V
public static final fun literal (Lapp/revanced/patcher/FingerprintBuilder;Lkotlin/jvm/functions/Function0;)V public static final fun literal (Lapp/revanced/patcher/FingerprintBuilder;Lkotlin/jvm/functions/Function0;)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V
@@ -1956,7 +2013,6 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V
public static synthetic fun returnEarly$default (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ZILjava/lang/Object;)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V

View File

@@ -50,12 +50,9 @@ kotlin {
publishing { publishing {
repositories { repositories {
maven { maven {
name = "GitHubPackages" name = "githubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-patches") url = uri("https://maven.pkg.github.com/revanced/revanced-patches")
credentials { credentials(PasswordCredentials::class)
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
} }
} }
} }

View File

@@ -0,0 +1,55 @@
package app.revanced.patches.all.misc.playintegrity
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/playintegrity/DisablePlayIntegrityPatch;"
private val CONTEXT_BIND_SERVICE_METHOD_REFERENCE = ImmutableMethodReference(
"Landroid/content/Context;",
"bindService",
listOf("Landroid/content/Intent;", "Landroid/content/ServiceConnection;", "I"),
"Z"
)
@Suppress("unused")
val disablePlayIntegrityPatch = bytecodePatch(
name = "Disable Play Integrity",
description = "Prevents apps from using Play Integrity by pretending it is not available.",
use = false,
) {
extendWith("extensions/all/misc/disable-play-integrity.rve")
dependsOn(
transformInstructionsPatch(
filterMap = filterMap@{ classDef, method, instruction, instructionIndex ->
val reference = instruction
.getReference<MethodReference>()
?.takeIf {
MethodUtil.methodSignaturesMatch(CONTEXT_BIND_SERVICE_METHOD_REFERENCE, it)
}
?: return@filterMap null
Triple(instruction as Instruction35c, instructionIndex, reference.parameterTypes)
},
transform = { method, entry ->
val (instruction, index, parameterTypes) = entry
val parameterString = parameterTypes.joinToString(separator = "")
val registerString = "v${instruction.registerC}, v${instruction.registerD}, v${instruction.registerE}, v${instruction.registerF}"
method.replaceInstruction(
index,
"invoke-static { $registerString }, $EXTENSION_CLASS_DESCRIPTOR->bindService(Landroid/content/Context;$parameterString)Z"
)
}
)
)
}

View File

@@ -0,0 +1,28 @@
package app.revanced.patches.com.sbs.ondemand.tv
import app.revanced.patcher.fingerprint
internal val shouldShowAdvertisingTVFingerprint = fingerprint {
returns("Z")
custom { method, classDef ->
method.name == "getShouldShowAdvertisingTV" &&
classDef.type == "Lcom/sbs/ondemand/common/InMemoryStorage;"
}
}
internal val shouldShowPauseAdFingerprint = fingerprint {
returns("Z")
custom { method, classDef ->
method.name == "shouldShowPauseAd" &&
classDef.type == "Lcom/sbs/ondemand/player/viewmodels/PauseAdController;"
}
}
internal val requestAdStreamFingerprint = fingerprint {
returns("V")
custom { method, classDef ->
method.name == "requestAdStream\$player_googleStoreTvRelease" &&
classDef.type == "Lcom/sbs/ondemand/player/viewmodels/AdsController;"
}
}

View File

@@ -0,0 +1,37 @@
package app.revanced.patches.com.sbs.ondemand.tv
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.misc.pairip.license.disableLicenseCheckPatch
import app.revanced.util.returnEarly
@Suppress("unused")
val removeAdsPatch = bytecodePatch(
name = "Remove ads",
description = "Removes pre-roll, pause and on-demand advertisements from SBS On Demand TV.",
) {
compatibleWith("com.sbs.ondemand.tv")
dependsOn(disableLicenseCheckPatch)
execute {
shouldShowAdvertisingTVFingerprint.method.returnEarly(true)
shouldShowPauseAdFingerprint.method.returnEarly(false)
// Remove on-demand pre-roll advertisements using exception handling.
// Exception handling is used instead of returnEarly() because:
// 1. returnEarly() causes black screen when the app waits for ad content that never comes.
// 2. SBS app has built-in exception handling in handleProviderFailure().
// 3. Exception triggers fallbackToAkamaiProvider() which loads actual content.
// 4. This preserves the intended app flow: first try ads, then fail gracefully, then load content.
requestAdStreamFingerprint.method.addInstructions(
0,
"""
new-instance v0, Ljava/lang/RuntimeException;
const-string v1, "Ad stream disabled"
invoke-direct {v0, v1}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
throw v0
"""
)
}
}

View File

@@ -0,0 +1,22 @@
package app.revanced.patches.idaustria.detection.deviceintegrity
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal val isDeviceBootloaderOpenFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
returns("Ljava/lang/Object;")
custom { method, classDef ->
method.name == "isDeviceBootloaderOpen" &&
classDef.endsWith("/DeviceIntegrityCheckProviderImpl;")
}
}
internal val isDeviceRootedFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
returns("Z")
custom { method, classDef ->
method.name == "isDeviceRooted" &&
classDef.endsWith("/DeviceIntegrityCheckProviderImpl;")
}
}

View File

@@ -0,0 +1,30 @@
package app.revanced.patches.idaustria.detection.deviceintegrity
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val removeDeviceIntegrityChecksPatch = bytecodePatch(
name = "Remove device integrity checks",
description = "Removes the check for root permissions and unlocked bootloader.",
) {
compatibleWith("at.gv.oe.app")
execute {
isDeviceRootedFingerprint.method.returnEarly(false)
isDeviceBootloaderOpenFingerprint.method.apply {
addInstructions(
0,
"""
const/4 v0, 0x0
invoke-static { v0 }, Lkotlin/coroutines/jvm/internal/Boxing;->boxBoolean(Z)Ljava/lang/Boolean;
move-result-object v0
return-object v0
"""
)
}
}
}

View File

@@ -1,31 +0,0 @@
package app.revanced.patches.idaustria.detection.root
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal val attestationSupportedCheckFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
returns("V")
custom { method, classDef ->
method.name == "attestationSupportCheck" &&
classDef.endsWith("/DeviceIntegrityCheck;")
}
}
internal val bootloaderCheckFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
returns("Z")
custom { method, classDef ->
method.name == "bootloaderCheck" &&
classDef.endsWith("/DeviceIntegrityCheck;")
}
}
internal val rootCheckFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
returns("V")
custom { method, classDef ->
method.name == "rootCheck" &&
classDef.endsWith("/DeviceIntegrityCheck;")
}
}

View File

@@ -1,22 +1,10 @@
package app.revanced.patches.idaustria.detection.root package app.revanced.patches.idaustria.detection.root
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.PATCH_DESCRIPTION_REMOVE_ROOT_DETECTION import app.revanced.patches.idaustria.detection.deviceintegrity.removeDeviceIntegrityChecksPatch
import app.revanced.patches.shared.PATCH_NAME_REMOVE_ROOT_DETECTION
import app.revanced.util.returnEarly
@Deprecated("Patch was superseded", ReplaceWith("removeDeviceIntegrityChecksPatch"))
@Suppress("unused") @Suppress("unused")
val rootDetectionPatch = bytecodePatch( val rootDetectionPatch = bytecodePatch {
name = PATCH_NAME_REMOVE_ROOT_DETECTION, dependsOn(removeDeviceIntegrityChecksPatch)
description = PATCH_DESCRIPTION_REMOVE_ROOT_DETECTION
) {
compatibleWith("at.gv.oe.app")
execute {
setOf(
attestationSupportedCheckFingerprint,
bootloaderCheckFingerprint,
rootCheckFingerprint,
).forEach { it.method.returnEarly(true) }
}
} }

View File

@@ -28,6 +28,13 @@ val hideNavigationButtonsPatch = bytecodePatch(
dependsOn(sharedExtensionPatch) dependsOn(sharedExtensionPatch)
val hideHome by booleanOption(
key = "hideHome",
default = false,
title = "Hide Home",
description = "Permanently hides the Home button. App starts at next available tab." // On the "homecoming" / current instagram layout.
)
val hideReels by booleanOption( val hideReels by booleanOption(
key = "hideReels", key = "hideReels",
default = true, default = true,
@@ -35,6 +42,27 @@ val hideNavigationButtonsPatch = bytecodePatch(
description = "Permanently hides the Reels button." description = "Permanently hides the Reels button."
) )
val hideDirect by booleanOption(
key = "hideDirect",
default = false,
title = "Hide Direct",
description = "Permanently hides the Direct button."
)
val hideSearch by booleanOption(
key = "hideSearch",
default = false,
title = "Hide Search",
description = "Permanently hides the Search button."
)
val hideProfile by booleanOption(
key = "hideProfile",
default = false,
title = "Hide Profile",
description = "Permanently hides the Profile button."
)
val hideCreate by booleanOption( val hideCreate by booleanOption(
key = "hideCreate", key = "hideCreate",
default = true, default = true,
@@ -43,7 +71,7 @@ val hideNavigationButtonsPatch = bytecodePatch(
) )
execute { execute {
if (!hideReels!! && !hideCreate!!) { if (!hideHome!! &&!hideReels!! && !hideDirect!! && !hideSearch!! && !hideProfile!! && !hideCreate!!) {
return@execute Logger.getLogger(this::class.java.name).warning( return@execute Logger.getLogger(this::class.java.name).warning(
"No hide navigation buttons options are enabled. No changes made." "No hide navigation buttons options are enabled. No changes made."
) )
@@ -76,6 +104,13 @@ val hideNavigationButtonsPatch = bytecodePatch(
""" """
} }
if (hideHome!!) {
addInstructionsAtControlFlowLabel(
returnIndex,
instructionsRemoveButtonByName("fragment_feed")
)
}
if (hideReels!!) { if (hideReels!!) {
addInstructionsAtControlFlowLabel( addInstructionsAtControlFlowLabel(
returnIndex, returnIndex,
@@ -83,12 +118,33 @@ val hideNavigationButtonsPatch = bytecodePatch(
) )
} }
if (hideDirect!!) {
addInstructionsAtControlFlowLabel(
returnIndex,
instructionsRemoveButtonByName("fragment_direct_tab")
)
}
if (hideSearch!!) {
addInstructionsAtControlFlowLabel(
returnIndex,
instructionsRemoveButtonByName("fragment_search")
)
}
if (hideCreate!!) { if (hideCreate!!) {
addInstructionsAtControlFlowLabel( addInstructionsAtControlFlowLabel(
returnIndex, returnIndex,
instructionsRemoveButtonByName("fragment_share") instructionsRemoveButtonByName("fragment_share")
) )
} }
if (hideProfile!!) {
addInstructionsAtControlFlowLabel(
returnIndex,
instructionsRemoveButtonByName("fragment_profile")
)
}
} }
} }
} }

View File

@@ -13,7 +13,7 @@ internal val storyUrlResponseJsonParserFingerprint = fingerprint {
} }
internal val profileUrlResponseJsonParserFingerprint = fingerprint { internal val profileUrlResponseJsonParserFingerprint = fingerprint {
strings("profile_to_share_url", "ProfileUrlResponse") strings("profile_to_share_url")
custom { method, _ -> method.name == "parseFromJson" } custom { method, _ -> method.name == "parseFromJson" }
} }

View File

@@ -9,7 +9,7 @@ val disableReelsScrollingPatch = bytecodePatch(
name = "Disable Reels scrolling", name = "Disable Reels scrolling",
description = "Disables the endless scrolling behavior in Instagram Reels, preventing swiping to the next Reel. " + description = "Disables the endless scrolling behavior in Instagram Reels, preventing swiping to the next Reel. " +
"Note: On a clean install, the 'Tip' animation may appear but will stop on its own after a few seconds.", "Note: On a clean install, the 'Tip' animation may appear but will stop on its own after a few seconds.",
use = true use = false
) { ) {
compatibleWith("com.instagram.android") compatibleWith("com.instagram.android")
@@ -31,4 +31,4 @@ val disableReelsScrollingPatch = bytecodePatch(
// Return false in onInterceptTouchEvent to disable pull-to-refresh. // Return false in onInterceptTouchEvent to disable pull-to-refresh.
clipsSwipeRefreshLayoutOnInterceptTouchEventFingerprint.method.returnEarly(false) clipsSwipeRefreshLayoutOnInterceptTouchEventFingerprint.method.returnEarly(false)
} }
} }

View File

@@ -0,0 +1,9 @@
package app.revanced.patches.letterboxd.unlock.unlockAppIcons
import app.revanced.patcher.fingerprint
internal val getCanChangeAppIconFingerprint = fingerprint {
custom { method, classDef ->
method.name == "getCanChangeAppIcon" && classDef.type.endsWith("SettingsAppIconFragment;")
}
}

View File

@@ -0,0 +1,16 @@
package app.revanced.patches.letterboxd.unlock.unlockAppIcons
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val unlockAppIconsPatch = bytecodePatch(
name = "Unlock app icons",
) {
compatibleWith("com.letterboxd.letterboxd")
execute {
getCanChangeAppIconFingerprint.method.returnEarly(true)
}
}

View File

@@ -0,0 +1,23 @@
package app.revanced.patches.lightroom.misc.version
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import com.android.tools.smali.dexlib2.Opcode
@Suppress("unused")
val disableVersionCheckPatch = bytecodePatch(
name = "Disable version check",
description = "Disables the server-side version check that prevents the app from starting.",
) {
compatibleWith("com.adobe.lrmobile"("9.3.0"))
execute {
refreshRemoteConfigurationFingerprint.method.apply {
val igetIndex = refreshRemoteConfigurationFingerprint.patternMatch!!.endIndex
// This value represents the server command to clear all version restrictions.
val statusForceReset = "-0x2";
replaceInstruction(igetIndex, "const/4 v1, $statusForceReset")
}
}
}

View File

@@ -0,0 +1,18 @@
package app.revanced.patches.lightroom.misc.version
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val refreshRemoteConfigurationFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
strings(
"com.adobe.lrmobile.denylisted_version_set_key",
"com.adobe.lrmobile.app_min_version_key"
)
opcodes(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IGET, // Overwrite this instruction to disable the check.
)
}

View File

@@ -0,0 +1,20 @@
package app.revanced.patches.protonvpn.splittunneling
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val enableSplitTunnelingUiFingerprint = fingerprint {
strings("currentModeAppNames")
opcodes(
Opcode.MOVE_OBJECT,
Opcode.MOVE_FROM16,
Opcode.INVOKE_DIRECT_RANGE
)
}
internal val initializeSplitTunnelingSettingsUIFingerprint = fingerprint {
custom { method, _ ->
method.name == "applyRestrictions"
}
}

View File

@@ -0,0 +1,34 @@
package app.revanced.patches.protonvpn.splittunneling
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Suppress("unused")
val unlockSplitTunnelingPatch =
bytecodePatch(
name = "Unlock split tunneling",
) {
compatibleWith("ch.protonvpn.android")
execute {
val registerIndex = enableSplitTunnelingUiFingerprint.patternMatch!!.endIndex - 1
enableSplitTunnelingUiFingerprint.method.apply {
val register = getInstruction<OneRegisterInstruction>(registerIndex).registerA
replaceInstruction(registerIndex, "const/4 v$register, 0x0")
}
initializeSplitTunnelingSettingsUIFingerprint.method.apply {
val initSettingsIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.name == "getSplitTunneling"
}
removeInstruction(initSettingsIndex - 1)
}
}
}

View File

@@ -3,12 +3,8 @@ package app.revanced.patches.reddit.ad.comments
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
internal val hideCommentAdsFingerprint = fingerprint { internal val hideCommentAdsFingerprint = fingerprint {
strings( custom { method, classDef ->
"link", method.name == "invokeSuspend" &&
// CommentPageRepository is not returning a link object classDef.contains("LoadAdsCombinedCall")
"is not returning a link object"
)
custom { _, classDef ->
classDef.sourceFile == "PostDetailPresenter.kt"
} }
} }

View File

@@ -1,6 +1,6 @@
package app.revanced.patches.reddit.ad.comments package app.revanced.patches.reddit.ad.comments
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
val hideCommentAdsPatch = bytecodePatch( val hideCommentAdsPatch = bytecodePatch(
@@ -8,13 +8,6 @@ val hideCommentAdsPatch = bytecodePatch(
) { ) {
execute { execute {
hideCommentAdsFingerprint.method.addInstructions( hideCommentAdsFingerprint.method.replaceInstructions(0, "return-object p1")
0,
"""
new-instance v0, Ljava/lang/Object;
invoke-direct {v0}, Ljava/lang/Object;-><init>()V
return-object v0
""",
)
} }
} }

View File

@@ -12,6 +12,5 @@ internal val adPostFingerprint = fingerprint {
internal val newAdPostFingerprint = fingerprint { internal val newAdPostFingerprint = fingerprint {
opcodes(Opcode.INVOKE_VIRTUAL) opcodes(Opcode.INVOKE_VIRTUAL)
strings("chain", "feedElement") strings("feedElement", "com.reddit.cookie")
custom { _, classDef -> classDef.sourceFile == "AdElementConverter.kt" }
} }

View File

@@ -3,7 +3,6 @@ package app.revanced.patches.reddit.ad.general
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.reddit.ad.banner.hideBannerPatch
import app.revanced.patches.reddit.ad.comments.hideCommentAdsPatch import app.revanced.patches.reddit.ad.comments.hideCommentAdsPatch
import app.revanced.patches.reddit.misc.extension.sharedExtensionPatch import app.revanced.patches.reddit.misc.extension.sharedExtensionPatch
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@@ -16,14 +15,9 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
val hideAdsPatch = bytecodePatch( val hideAdsPatch = bytecodePatch(
name = "Hide ads", name = "Hide ads",
) { ) {
dependsOn(hideBannerPatch, hideCommentAdsPatch, sharedExtensionPatch) dependsOn(hideCommentAdsPatch, sharedExtensionPatch)
// Note that for now, this patch and anything using it will only work on compatibleWith("com.reddit.frontpage")
// Reddit 2024.17.0 or older. Newer versions will crash during patching.
// See https://github.com/ReVanced/revanced-patches/issues/3099
// and https://github.com/iBotPeaches/Apktool/issues/3534.
// This constraint is necessary due to dependency on hideBannerPatch.
compatibleWith("com.reddit.frontpage"("2024.17.0"))
execute { execute {
// region Filter promoted ads (does not work in popular or latest feed) // region Filter promoted ads (does not work in popular or latest feed)

View File

@@ -0,0 +1,36 @@
package app.revanced.patches.shared.misc.privacy
import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.asSequence
import app.revanced.util.getNode
import org.w3c.dom.Element
@Suppress("unused")
val disableSentryTelemetryPatch = resourcePatch(
name = "Disable Sentry telemetry",
description = "Disables Sentry telemetry. See https://sentry.io/for/android/ for more information.",
use = false,
) {
execute {
fun Element.replaceOrCreate(tagName: String, attributeName: String, attributeValue: String) {
val childElements = getElementsByTagName(tagName).asSequence().filterIsInstance<Element>()
val targetChild = childElements.find { childElement ->
childElement.getAttribute("android:name") == attributeName
}
if (targetChild != null) {
targetChild.setAttribute("android:value", attributeValue)
} else {
appendChild(ownerDocument.createElement(tagName).apply {
setAttribute("android:name", attributeName)
setAttribute("android:value", attributeValue)
})
}
}
document("AndroidManifest.xml").use { document ->
val application = document.getNode("application") as Element
application.replaceOrCreate("meta-data", "io.sentry.enabled", "false")
application.replaceOrCreate("meta-data", "io.sentry.dsn", "")
}
}
}

View File

@@ -0,0 +1,116 @@
package app.revanced.patches.strava.media.download
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.misc.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.strava.misc.extension.sharedExtensionPatch
import app.revanced.util.getReference
import app.revanced.util.writeRegister
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction22c
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
private const val ACTION_CLASS_DESCRIPTOR = "Lcom/strava/bottomsheet/Action;"
private const val MEDIA_CLASS_DESCRIPTOR = "Lcom/strava/photos/data/Media;"
private const val MEDIA_DOWNLOAD_CLASS_DESCRIPTOR = "Lapp/revanced/extension/strava/AddMediaDownloadPatch;"
@Suppress("unused")
val addMediaDownloadPatch = bytecodePatch(
name = "Add media download",
description = "Extends the full-screen media viewer menu with items to copy or open their URLs or download them directly."
) {
compatibleWith("com.strava")
dependsOn(
resourceMappingPatch,
sharedExtensionPatch
)
execute {
val fragmentClass = classBy { it.endsWith("/FullscreenMediaFragment;") }!!.mutableClass
// region Extend menu of `FullscreenMediaFragment` with actions.
createAndShowFragmentFingerprint.match(fragmentClass).method.apply {
val setTrueIndex = instructions.indexOfFirst { instruction ->
instruction.opcode == Opcode.IPUT_BOOLEAN
}
val actionRegistrarRegister = getInstruction<BuilderInstruction22c>(setTrueIndex).registerB
val actionRegister = instructions.first { instruction ->
instruction.getReference<TypeReference>()?.type == ACTION_CLASS_DESCRIPTOR
}.writeRegister!!
fun addMenuItem(actionId: String, string: String, color: String, drawable: String) = addInstructions(
setTrueIndex + 1,
"""
new-instance v$actionRegister, $ACTION_CLASS_DESCRIPTOR
sget v${actionRegister + 1}, $MEDIA_DOWNLOAD_CLASS_DESCRIPTOR->$actionId:I
const v${actionRegister + 2}, 0x0
const v${actionRegister + 3}, ${resourceMappings["string", string]}
const v${actionRegister + 4}, ${resourceMappings["color", color]}
const v${actionRegister + 5}, ${resourceMappings["drawable", drawable]}
move/from16 v${actionRegister + 6}, v${actionRegister + 4}
invoke-direct/range { v$actionRegister .. v${actionRegister + 7} }, $ACTION_CLASS_DESCRIPTOR-><init>(ILjava/lang/String;IIIILjava/io/Serializable;)V
invoke-virtual { v$actionRegistrarRegister, v$actionRegister }, Lcom/strava/bottomsheet/a;->a(Lcom/strava/bottomsheet/BottomSheetItem;)V
"""
)
addMenuItem("ACTION_COPY_LINK", "copy_link", "core_o3", "actions_link_normal_xsmall")
addMenuItem("ACTION_OPEN_LINK", "fallback_menu_item_open_in_browser", "core_o3", "actions_link_external_normal_xsmall")
addMenuItem("ACTION_DOWNLOAD", "download", "core_o3", "actions_download_normal_xsmall")
// Move media to last parameter of `Action` constructor.
val getMediaInstruction = instructions.first { instruction ->
instruction.getReference<FieldReference>()?.type == MEDIA_CLASS_DESCRIPTOR
}
addInstruction(
getMediaInstruction.location.index + 1,
"move-object/from16 v${actionRegister + 7}, v${getMediaInstruction.writeRegister}"
)
}
// endregion
// region Handle new actions.
val actionClass = classes.first { clazz ->
clazz.type == ACTION_CLASS_DESCRIPTOR
}
val actionSerializableField = actionClass.instanceFields.first { field ->
field.type == "Ljava/io/Serializable;"
}
// Handle "copy link" & "open link" & "download" actions.
handleMediaActionFingerprint.match(fragmentClass).method.apply {
// Call handler if action ID < 0 (= custom).
val moveInstruction = instructions.first { instruction ->
instruction.opcode == Opcode.MOVE_RESULT
}
val indexAfterMoveInstruction = moveInstruction.location.index + 1
val actionIdRegister = moveInstruction.writeRegister
addInstructionsWithLabels(
indexAfterMoveInstruction,
"""
if-gez v$actionIdRegister, :move
check-cast p2, $ACTION_CLASS_DESCRIPTOR
iget-object v0, p2, $actionSerializableField
check-cast v0, $MEDIA_CLASS_DESCRIPTOR
invoke-static { v$actionIdRegister, v0 }, $MEDIA_DOWNLOAD_CLASS_DESCRIPTOR->handleAction(I$MEDIA_CLASS_DESCRIPTOR)Z
move-result v0
return v0
""",
ExternalLabel("move", instructions[indexAfterMoveInstruction])
)
}
// endregion
}
}

View File

@@ -0,0 +1,15 @@
package app.revanced.patches.strava.media.download
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal val createAndShowFragmentFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters("L")
strings("mediaType")
}
internal val handleMediaActionFingerprint = fingerprint {
parameters("Landroid/view/View;", "Lcom/strava/bottomsheet/BottomSheetItem;")
}

View File

@@ -0,0 +1,21 @@
package app.revanced.patches.strava.media.upload
import app.revanced.patcher.fingerprint
internal val getCompressionQualityFingerprint = fingerprint {
custom { method, _ ->
method.name == "getCompressionQuality"
}
}
internal val getMaxDurationFingerprint = fingerprint {
custom { method, _ ->
method.name == "getMaxDuration"
}
}
internal val getMaxSizeFingerprint = fingerprint {
custom { method, _ ->
method.name == "getMaxSize"
}
}

View File

@@ -0,0 +1,48 @@
package app.revanced.patches.strava.media.upload
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.intOption
import app.revanced.patcher.patch.longOption
import app.revanced.util.returnEarly
@Suppress("unused")
val overwriteMediaUploadParametersPatch = bytecodePatch(
name = "Overwrite media upload parameters",
description = "Overwrites the compression, resize and trim media (images and videos) parameters returned by Strava's server before upload.",
) {
compatibleWith("com.strava")
val compressionQuality by intOption(
key = "compressionQuality",
title = "Compression quality (percent)",
description = "This is used as the JPEG quality setting (≤ 100).",
) { it == null || it in 1..100 }
val maxDuration by longOption(
key = "maxDuration",
title = "Max duration (seconds)",
description = "The maximum length (≤ ${60 * 60}) of a video before it gets trimmed.",
) { it == null || it in 1..60 * 60 }
val maxSize by intOption(
key = "maxSize",
title = "Max size (pixels)",
description = "The image gets resized so that the smaller dimension (width/height) does not exceed this value (≤ 10000).",
) { it == null || it in 1..10000 }
execute {
val mediaUploadParametersClass = classes.single { it.endsWith("/MediaUploadParameters;") }
compressionQuality?.let { compressionQuality ->
getCompressionQualityFingerprint.match(mediaUploadParametersClass).method.returnEarly(compressionQuality / 100f)
}
maxDuration?.let { maxDuration ->
getMaxDurationFingerprint.match(mediaUploadParametersClass).method.returnEarly(maxDuration)
}
maxSize?.let {
getMaxSizeFingerprint.match(mediaUploadParametersClass).method.returnEarly(it)
}
}
}

View File

@@ -0,0 +1,9 @@
package app.revanced.patches.strava.misc.extension
import app.revanced.patches.shared.misc.extension.extensionHook
internal val applicationOnCreateHook = extensionHook {
custom { method, classDef ->
method.name == "onCreate" && classDef.endsWith("/StravaApplication;")
}
}

View File

@@ -0,0 +1,5 @@
package app.revanced.patches.strava.misc.extension
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
val sharedExtensionPatch = sharedExtensionPatch("strava", applicationOnCreateHook)

View File

@@ -0,0 +1,20 @@
package app.revanced.patches.strava.password
import app.revanced.patcher.Fingerprint
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val enablePasswordLoginPatch = bytecodePatch(
name = "Enable password login",
description = "Re-enables password login after having used an OTP code.",
) {
compatibleWith("com.strava")
execute {
fun Fingerprint.returnTrue() = method.returnEarly(true)
logInGetUsePasswordFingerprint.returnTrue()
emailChangeGetUsePasswordFingerprint.returnTrue()
}
}

View File

@@ -0,0 +1,15 @@
package app.revanced.patches.strava.password
import app.revanced.patcher.fingerprint
internal val logInGetUsePasswordFingerprint = fingerprint {
custom { method, classDef ->
method.name == "getUsePassword" && classDef.endsWith("/RequestOtpLogInNetworkResponse;")
}
}
internal val emailChangeGetUsePasswordFingerprint = fingerprint {
custom { method, classDef ->
method.name == "getUsePassword" && classDef.endsWith("/RequestEmailChangeWithOtpOrPasswordResponse;")
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.patches.strava.privacy
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val blockSnowplowTrackingPatch = bytecodePatch(
name = "Block Snowplow tracking",
description = "Blocks Snowplow analytics. See https://snowplow.io for more information.",
) {
compatibleWith("com.strava")
execute {
// Keep events list empty, otherwise sent to https://c.strava.com/com.snowplowanalytics.snowplow/tp2.
insertEventFingerprint.method.returnEarly()
}
}

View File

@@ -0,0 +1,9 @@
package app.revanced.patches.strava.privacy
import app.revanced.patcher.fingerprint
// https://github.com/snowplow/snowplow-android-tracker/blob/2.2.0/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/internal/emitter/storage/SQLiteEventStore.java#L130
// Not the exact same code (e.g. returns void instead of long), even though the version number matches.
internal val insertEventFingerprint = fingerprint {
strings("Added event to database: %s")
}

View File

@@ -0,0 +1,16 @@
package app.revanced.patches.strava.quickedit
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val disableQuickEditPatch = bytecodePatch(
name = "Disable Quick Edit",
description = "Prevents the Quick Edit prompt from popping up.",
) {
compatibleWith("com.strava")
execute {
getHasAccessToQuickEditFingerprint.method.returnEarly()
}
}

View File

@@ -0,0 +1,10 @@
package app.revanced.patches.strava.quickedit
import app.revanced.patcher.fingerprint
internal val getHasAccessToQuickEditFingerprint = fingerprint {
returns("Z")
custom { method, _ ->
method.name == "getHasAccessToQuickEdit"
}
}

View File

@@ -1,11 +1,9 @@
package app.revanced.patches.strava.subscription package app.revanced.patches.strava.subscription
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.Opcode
internal val getSubscribedFingerprint = fingerprint { internal val getSubscribedFingerprint = fingerprint {
opcodes(Opcode.IGET_BOOLEAN)
custom { method, classDef -> custom { method, classDef ->
classDef.endsWith("/SubscriptionDetailResponse;") && method.name == "getSubscribed" method.name == "getSubscribed" && classDef.endsWith("/SubscriptionDetailResponse;")
} }
} }

View File

@@ -1,7 +1,7 @@
package app.revanced.patches.strava.subscription package app.revanced.patches.strava.subscription
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused") @Suppress("unused")
val unlockSubscriptionPatch = bytecodePatch( val unlockSubscriptionPatch = bytecodePatch(
@@ -11,9 +11,6 @@ val unlockSubscriptionPatch = bytecodePatch(
compatibleWith("com.strava") compatibleWith("com.strava")
execute { execute {
getSubscribedFingerprint.method.replaceInstruction( getSubscribedFingerprint.method.returnEarly(true)
getSubscribedFingerprint.patternMatch!!.startIndex,
"const/4 v0, 0x1",
)
} }
} }

View File

@@ -174,6 +174,7 @@ val hideLayoutComponentsPatch = bytecodePatch(
), ),
SwitchPreference("revanced_hide_channel_bar"), SwitchPreference("revanced_hide_channel_bar"),
SwitchPreference("revanced_hide_channel_watermark"), SwitchPreference("revanced_hide_channel_watermark"),
SwitchPreference("revanced_hide_crowdfunding_box"),
SwitchPreference("revanced_hide_emergency_box"), SwitchPreference("revanced_hide_emergency_box"),
SwitchPreference("revanced_hide_info_panels"), SwitchPreference("revanced_hide_info_panels"),
SwitchPreference("revanced_hide_join_membership_button"), SwitchPreference("revanced_hide_join_membership_button"),
@@ -229,7 +230,6 @@ val hideLayoutComponentsPatch = bytecodePatch(
SwitchPreference("revanced_hide_chips_shelf"), SwitchPreference("revanced_hide_chips_shelf"),
SwitchPreference("revanced_hide_community_posts"), SwitchPreference("revanced_hide_community_posts"),
SwitchPreference("revanced_hide_compact_banner"), SwitchPreference("revanced_hide_compact_banner"),
SwitchPreference("revanced_hide_crowdfunding_box"),
SwitchPreference("revanced_hide_expandable_card"), SwitchPreference("revanced_hide_expandable_card"),
SwitchPreference("revanced_hide_floating_microphone_button"), SwitchPreference("revanced_hide_floating_microphone_button"),
SwitchPreference( SwitchPreference(

View File

@@ -0,0 +1,14 @@
package app.revanced.patches.youtube.misc.audiofocus
import app.revanced.patcher.fingerprint
internal val audioFocusChangeListenerFingerprint = fingerprint {
strings(
"AudioFocus DUCK",
"AudioFocus loss; Will lower volume",
)
}
internal val audioFocusRequestBuilderFingerprint = fingerprint {
strings("Can't build an AudioFocusRequestCompat instance without a listener")
}

View File

@@ -0,0 +1,66 @@
package app.revanced.patches.youtube.misc.audiofocus
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/PauseOnAudioInterruptPatch;"
val pauseOnAudioInterruptPatch = bytecodePatch(
name = "Pause on audio interrupt",
description = "Adds an option to pause playback instead of lowering volume when other audio plays.",
) {
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.youtube"(
"20.14.43",
)
)
execute {
addResources("youtube", "misc.audiofocus.pauseOnAudioInterruptPatch")
PreferenceScreen.MISC.addPreferences(
SwitchPreference("revanced_pause_on_audio_interrupt"),
)
// Hook the builder method that creates AudioFocusRequest.
// At the start, set the willPauseWhenDucked field (b) to true if setting is enabled.
val builderMethod = audioFocusRequestBuilderFingerprint.method
val builderClass = builderMethod.definingClass
builderMethod.addInstructionsWithLabels(
0,
"""
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->shouldPauseOnAudioInterrupt()Z
move-result v0
if-eqz v0, :skip_override
const/4 v0, 0x1
iput-boolean v0, p0, $builderClass->b:Z
""",
ExternalLabel("skip_override", builderMethod.getInstruction(0)),
)
// Also hook the audio focus change listener as a backup.
audioFocusChangeListenerFingerprint.method.addInstructions(
0,
"""
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->overrideAudioFocusChange(I)I
move-result p1
"""
)
}
}

View File

@@ -23,22 +23,19 @@ import app.revanced.util.InstructionUtils.Companion.writeOpcodes
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.Opcode.* import com.android.tools.smali.dexlib2.Opcode.*
import com.android.tools.smali.dexlib2.analysis.reflection.util.ReflectionUtils
import com.android.tools.smali.dexlib2.formatter.DexFormatter
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.*
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference 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.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.Reference import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.iface.reference.StringReference
import com.android.tools.smali.dexlib2.iface.value.*
import com.android.tools.smali.dexlib2.immutable.ImmutableField import com.android.tools.smali.dexlib2.immutable.ImmutableField
import com.android.tools.smali.dexlib2.immutable.value.*
import com.android.tools.smali.dexlib2.util.MethodUtil import com.android.tools.smali.dexlib2.util.MethodUtil
import java.util.EnumSet import java.util.*
/** /**
* Starting from and including the instruction at index [startIndex], * Starting from and including the instruction at index [startIndex],
@@ -180,7 +177,7 @@ internal val Instruction.isReturnInstruction: Boolean
* *
* @param fieldName The name of the field to find. Partial matches are allowed. * @param fieldName The name of the field to find. Partial matches are allowed.
*/ */
private fun Method.findInstructionIndexFromToString(fieldName: String) : Int { private fun Method.findInstructionIndexFromToString(fieldName: String): Int {
val stringIndex = indexOfFirstInstruction { val stringIndex = indexOfFirstInstruction {
val reference = getReference<StringReference>() val reference = getReference<StringReference>()
reference?.string?.contains(fieldName) == true reference?.string?.contains(fieldName) == true
@@ -233,7 +230,7 @@ private fun Method.findInstructionIndexFromToString(fieldName: String) : Int {
* @param fieldName The name of the field to find. Partial matches are allowed. * @param fieldName The name of the field to find. Partial matches are allowed.
*/ */
context(BytecodePatchContext) context(BytecodePatchContext)
internal fun Method.findMethodFromToString(fieldName: String) : MutableMethod { internal fun Method.findMethodFromToString(fieldName: String): MutableMethod {
val methodUsageIndex = findInstructionIndexFromToString(fieldName) val methodUsageIndex = findInstructionIndexFromToString(fieldName)
return navigate(this).to(methodUsageIndex).stop() return navigate(this).to(methodUsageIndex).stop()
} }
@@ -243,7 +240,7 @@ internal fun Method.findMethodFromToString(fieldName: String) : MutableMethod {
* *
* @param fieldName The name of the field to find. Partial matches are allowed. * @param fieldName The name of the field to find. Partial matches are allowed.
*/ */
internal fun Method.findFieldFromToString(fieldName: String) : FieldReference { internal fun Method.findFieldFromToString(fieldName: String): FieldReference {
val methodUsageIndex = findInstructionIndexFromToString(fieldName) val methodUsageIndex = findInstructionIndexFromToString(fieldName)
return getInstruction<ReferenceInstruction>(methodUsageIndex).getReference<FieldReference>()!! return getInstruction<ReferenceInstruction>(methodUsageIndex).getReference<FieldReference>()!!
} }
@@ -838,23 +835,59 @@ fun BytecodePatchContext.forEachLiteralValueInstruction(
} }
private const val RETURN_TYPE_MISMATCH = "Mismatch between override type and Method return type" private fun MutableMethod.checkReturnType(expectedTypes: Iterable<Class<*>>) {
val returnTypeJava = ReflectionUtils.dexToJavaName(returnType)
check(expectedTypes.any { returnTypeJava == it.name }) {
"Actual return type $returnTypeJava is not contained in expected types: $expectedTypes"
}
}
/** /**
* Overrides the first instruction of a method with a constant `Boolean` return value. * Overrides the first instruction of a method with returning the default value for the type (or `void`).
* None of the method code will ever execute. * None of the method code will ever execute.
* *
* For methods that return an object or any array type, calling this method with `false`
* will force the method to return a `null` value.
*
* @see returnLate * @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Boolean = false) { fun MutableMethod.returnEarly() {
val returnType = returnType.first() val value = when (returnType) {
check(returnType == 'Z' || (!value && (returnType == 'V' || returnType == 'L' || returnType != '['))) { "V" -> null
RETURN_TYPE_MISMATCH "Z" -> ImmutableBooleanEncodedValue.FALSE_VALUE
"B" -> ImmutableByteEncodedValue(0)
"S" -> ImmutableShortEncodedValue(0)
"C" -> ImmutableCharEncodedValue(Char.MIN_VALUE)
"I" -> ImmutableIntEncodedValue(0)
"F" -> ImmutableFloatEncodedValue(0f)
"J" -> ImmutableLongEncodedValue(0)
"D" -> ImmutableDoubleEncodedValue(0.0)
else -> ImmutableNullEncodedValue.INSTANCE
} }
overrideReturnValue(value.toHexString(), false) overrideReturnValue(value, false)
}
private fun MutableMethod.returnString(value: String, late: Boolean) {
checkReturnType(String::class.java.allAssignableTypes())
overrideReturnValue(ImmutableStringEncodedValue(value), late)
}
/**
* Overrides the first instruction of a method with a constant `String` return value.
* None of the method code will ever execute.
*
* @see returnLate
*/
fun MutableMethod.returnEarly(value: String) = returnString(value, false)
/**
* Overrides all return statements with a constant `String` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: String) = returnString(value, true)
private fun MutableMethod.returnByte(value: Byte, late: Boolean) {
checkReturnType(Byte::class.javaObjectType.allAssignableTypes() + Byte::class.javaPrimitiveType!!)
overrideReturnValue(ImmutableByteEncodedValue(value), late)
} }
/** /**
@@ -863,9 +896,40 @@ fun MutableMethod.returnEarly(value: Boolean = false) {
* *
* @see returnLate * @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Byte) { fun MutableMethod.returnEarly(value: Byte) = returnByte(value, false)
check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false) /**
* Overrides all return statements with a constant `Byte` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Byte) = returnByte(value, true)
private fun MutableMethod.returnBoolean(value: Boolean, late: Boolean) {
checkReturnType(Boolean::class.javaObjectType.allAssignableTypes() + Boolean::class.javaPrimitiveType!!)
overrideReturnValue(ImmutableBooleanEncodedValue.forBoolean(value), late)
}
/**
* Overrides the first instruction of a method with a constant `Boolean` return value.
* None of the method code will ever execute.
*
* @see returnLate
*/
fun MutableMethod.returnEarly(value: Boolean) = returnBoolean(value, false)
/**
* Overrides all return statements with a constant `Boolean` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Boolean) = returnBoolean(value, true)
private fun MutableMethod.returnShort(value: Short, late: Boolean) {
checkReturnType(Short::class.javaObjectType.allAssignableTypes() + Short::class.javaPrimitiveType!!)
overrideReturnValue(ImmutableShortEncodedValue(value), late)
} }
/** /**
@@ -874,9 +938,19 @@ fun MutableMethod.returnEarly(value: Byte) {
* *
* @see returnLate * @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Short) { fun MutableMethod.returnEarly(value: Short) = returnShort(value, false)
check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false) /**
* Overrides all return statements with a constant `Short` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Short) = returnShort(value, true)
private fun MutableMethod.returnChar(value: Char, late: Boolean) {
checkReturnType(Char::class.javaObjectType.allAssignableTypes() + Char::class.javaPrimitiveType!!)
overrideReturnValue(ImmutableCharEncodedValue(value), late)
} }
/** /**
@@ -885,9 +959,19 @@ fun MutableMethod.returnEarly(value: Short) {
* *
* @see returnLate * @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Char) { fun MutableMethod.returnEarly(value: Char) = returnChar(value, false)
check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.code.toString(), false) /**
* Overrides all return statements with a constant `Char` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Char) = returnChar(value, true)
private fun MutableMethod.returnInt(value: Int, late: Boolean) {
checkReturnType(Int::class.javaObjectType.allAssignableTypes() + Int::class.javaPrimitiveType!!)
overrideReturnValue(ImmutableIntEncodedValue(value), late)
} }
/** /**
@@ -896,20 +980,19 @@ fun MutableMethod.returnEarly(value: Char) {
* *
* @see returnLate * @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Int) { fun MutableMethod.returnEarly(value: Int) = returnInt(value, false)
check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
}
/** /**
* Overrides the first instruction of a method with a constant `Long` return value. * Overrides all return statements with a constant `Int` value.
* None of the method code will ever execute. * All method code is executed the same as unpatched.
* *
* @see returnLate * @see returnEarly
*/ */
fun MutableMethod.returnEarly(value: Long) { fun MutableMethod.returnLate(value: Int) = returnInt(value, true)
check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false) private fun MutableMethod.returnFloat(value: Float, late: Boolean) {
checkReturnType(Float::class.javaObjectType.allAssignableTypes() + Float::class.javaPrimitiveType!!)
overrideReturnValue(ImmutableFloatEncodedValue(value), late)
} }
/** /**
@@ -918,9 +1001,40 @@ fun MutableMethod.returnEarly(value: Long) {
* *
* @see returnLate * @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Float) { fun MutableMethod.returnEarly(value: Float) = returnFloat(value, false)
check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false) /**
* Overrides all return statements with a constant `Float` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Float) = returnFloat(value, true)
private fun MutableMethod.returnLong(value: Long, late: Boolean) {
checkReturnType(Long::class.javaObjectType.allAssignableTypes() + Long::class.javaPrimitiveType!!)
overrideReturnValue(ImmutableLongEncodedValue(value), late)
}
/**
* Overrides the first instruction of a method with a constant `Long` return value.
* None of the method code will ever execute.
*
* @see returnLate
*/
fun MutableMethod.returnEarly(value: Long) = returnLong(value, false)
/**
* Overrides all return statements with a constant `Long` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Long) = returnLong(value, true)
private fun MutableMethod.returnDouble(value: Double, late: Boolean) {
checkReturnType(Double::class.javaObjectType.allAssignableTypes() + Double::class.javaPrimitiveType!!)
overrideReturnValue(ImmutableDoubleEncodedValue(value), late)
} }
/** /**
@@ -929,113 +1043,7 @@ fun MutableMethod.returnEarly(value: Float) {
* *
* @see returnLate * @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Double) { fun MutableMethod.returnEarly(value: Double) = returnDouble(value, false)
check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
}
/**
* Overrides the first instruction of a method with a constant String return value.
* None of the method code will ever execute.
*
* Target method must have return type
* Ljava/lang/String; or Ljava/lang/CharSequence;
*
* @see returnLate
*/
fun MutableMethod.returnEarly(value: String) {
check(returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;") {
RETURN_TYPE_MISMATCH
}
overrideReturnValue(value, false)
}
/**
* Overrides all return statements with a constant `Boolean` value.
* All method code is executed the same as unpatched.
*
* For methods that return an object or any array type, calling this method with `false`
* will force the method to return a `null` value.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Boolean) {
val returnType = returnType.first()
if (returnType == 'V') {
error("Cannot return late for Method of void type")
}
check(returnType == 'Z' || (!value && (returnType == 'L' || returnType == '['))) {
RETURN_TYPE_MISMATCH
}
overrideReturnValue(value.toHexString(), true)
}
/**
* Overrides all return statements with a constant `Byte` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Byte) {
check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
/**
* Overrides all return statements with a constant `Short` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Short) {
check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
/**
* Overrides all return statements with a constant `Char` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Char) {
check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.code.toString(), true)
}
/**
* Overrides all return statements with a constant `Int` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Int) {
check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
/**
* Overrides all return statements with a constant `Long` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Long) {
check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
/**
* Overrides all return statements with a constant `Float` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Float) {
check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
/** /**
* Overrides all return statements with a constant `Double` value. * Overrides all return statements with a constant `Double` value.
@@ -1043,75 +1051,164 @@ fun MutableMethod.returnLate(value: Float) {
* *
* @see returnEarly * @see returnEarly
*/ */
fun MutableMethod.returnLate(value: Double) { fun MutableMethod.returnLate(value: Double) = returnDouble(value, true)
check(returnType.first() == 'D') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
/** private fun MutableMethod.overrideReturnValue(value: EncodedValue?, returnLate: Boolean) {
* Overrides all return statements with a constant String value. val instructions = if (value == null) {
* All method code is executed the same as unpatched. require(!returnLate) {
* "Cannot return late for method of void type"
* Target method must have return type }
* Ljava/lang/String; or Ljava/lang/CharSequence; "return-void"
* } else {
* @see returnEarly val encodedValue = DexFormatter.INSTANCE.getEncodedValue(value)
*/ when (value) {
fun MutableMethod.returnLate(value: String) { is NullEncodedValue -> {
check(returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;") { """
RETURN_TYPE_MISMATCH
}
overrideReturnValue(value, true)
}
private fun MutableMethod.overrideReturnValue(value: String, returnLate: Boolean) {
val instructions = if (returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;" ) {
"""
const-string v0, "$value"
return-object v0
"""
} else when (returnType.first()) {
// If return type is an object, always return null.
'L', '[' -> {
"""
const/4 v0, 0x0 const/4 v0, 0x0
return-object v0 return-object v0
""" """
} }
'V' -> { is StringEncodedValue -> {
"return-void" """
} const-string v0, $encodedValue
return-object v0
"""
}
'B', 'Z' -> { is ByteEncodedValue -> {
""" if (returnType == "B") {
const/4 v0, $value """
return v0 const/4 v0, $encodedValue
""" return v0
} """
} else {
"""
const/4 v0, $encodedValue
invoke-static { v0 }, Ljava/lang/Byte;->valueOf(B)Ljava/lang/Byte;
move-result-object v0
return-object v0
"""
}
}
'S', 'C' -> { is BooleanEncodedValue -> {
""" val encodedValue = value.value.toHexString()
const/16 v0, $value if (returnType == "Z") {
return v0 """
""" const/4 v0, $encodedValue
} return v0
"""
} else {
"""
const/4 v0, $encodedValue
invoke-static { v0 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;
move-result-object v0
return-object v0
"""
}
}
'I', 'F' -> { is ShortEncodedValue -> {
""" if (returnType == "S") {
const v0, $value """
return v0 const/16 v0, $encodedValue
""" return v0
} """
} else {
"""
const/16 v0, $encodedValue
invoke-static { v0 }, Ljava/lang/Short;->valueOf(S)Ljava/lang/Short;
move-result-object v0
return-object v0
"""
}
}
'J', 'D' -> { is CharEncodedValue -> {
""" if (returnType == "C") {
const-wide v0, $value """
return-wide v0 const/16 v0, $encodedValue
""" return v0
} """
} else {
"""
const/16 v0, $encodedValue
invoke-static { v0 }, Ljava/lang/Character;->valueOf(C)Ljava/lang/Character;
move-result-object v0
return-object v0
"""
}
}
else -> throw Exception("Return type is not supported: $this") is IntEncodedValue -> {
if (returnType == "I") {
"""
const v0, $encodedValue
return v0
"""
} else {
"""
const v0, $encodedValue
invoke-static { v0 }, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object v0
return-object v0
"""
}
}
is FloatEncodedValue -> {
val encodedValue = "${encodedValue}f"
if (returnType == "F") {
"""
const v0, $encodedValue
return v0
"""
} else {
"""
const v0, $encodedValue
invoke-static { v0 }, Ljava/lang/Float;->valueOf(F)Ljava/lang/Float;
move-result-object v0
return-object v0
"""
}
}
is LongEncodedValue -> {
val encodedValue = "${encodedValue}L"
if (returnType == "J") {
"""
const-wide v0, $encodedValue
return-wide v0
"""
} else {
"""
const-wide v0, $encodedValue
invoke-static { v0 }, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;
move-result-object v0
return-object v0
"""
}
}
is DoubleEncodedValue -> {
if (returnType == "D") {
"""
const-wide v0, $encodedValue
return-wide v0
"""
} else {
"""
const-wide v0, $encodedValue
invoke-static { v0 }, Ljava/lang/Double;->valueOf(D)Ljava/lang/Double;
move-result-object v0
return-object v0
"""
}
}
else -> throw IllegalArgumentException("Value $value cannot be returned from $this")
}
} }
if (returnLate) { if (returnLate) {

View File

@@ -7,4 +7,21 @@ internal object Utils {
.trimIndent() // Remove the leading newline. .trimIndent() // Remove the leading newline.
} }
internal fun Boolean.toHexString(): String = if (this) "0x1" else "0x0" internal fun Boolean.toHexString(): String = if (this) "0x1" else "0x0"
internal fun Class<*>.allAssignableTypes(): Set<Class<*>> {
val result = mutableSetOf<Class<*>>()
fun visit(child: Class<*>?) {
if (child == null || !result.add(child)) {
return
}
child.interfaces.forEach(::visit)
visit(child.superclass)
}
visit(this)
return result
}

View File

@@ -232,9 +232,6 @@ However, enabling this will also log some user data such as your IP address."</s
<string name="revanced_hide_compact_banner_title">Hide compact banners</string> <string name="revanced_hide_compact_banner_title">Hide compact banners</string>
<string name="revanced_hide_compact_banner_summary_on">Compact banners are hidden</string> <string name="revanced_hide_compact_banner_summary_on">Compact banners are hidden</string>
<string name="revanced_hide_compact_banner_summary_off">Compact banners are shown</string> <string name="revanced_hide_compact_banner_summary_off">Compact banners are shown</string>
<string name="revanced_hide_crowdfunding_box_title">Hide crowdfunding box</string>
<string name="revanced_hide_crowdfunding_box_summary_on">Crowdfunding box is hidden</string>
<string name="revanced_hide_crowdfunding_box_summary_off">Crowdfunding box is shown</string>
<string name="revanced_hide_expandable_card_title">Hide expandable card</string> <string name="revanced_hide_expandable_card_title">Hide expandable card</string>
<string name="revanced_hide_expandable_card_summary_on">Expandable card under videos is hidden</string> <string name="revanced_hide_expandable_card_summary_on">Expandable card under videos is hidden</string>
<string name="revanced_hide_expandable_card_summary_off">Expandable card under videos is shown</string> <string name="revanced_hide_expandable_card_summary_off">Expandable card under videos is shown</string>
@@ -300,8 +297,11 @@ If a Doodle is currently showing in your region and this hide setting is on, the
<string name="revanced_hide_channel_bar_summary_on">Channel bar is hidden</string> <string name="revanced_hide_channel_bar_summary_on">Channel bar is hidden</string>
<string name="revanced_hide_channel_bar_summary_off">Channel bar is shown</string> <string name="revanced_hide_channel_bar_summary_off">Channel bar is shown</string>
<string name="revanced_hide_channel_watermark_title">Hide channel watermark</string> <string name="revanced_hide_channel_watermark_title">Hide channel watermark</string>
<string name="revanced_hide_channel_watermark_summary_on">Watermark is hidden</string> <string name="revanced_hide_channel_watermark_summary_on">Channel watermark is hidden</string>
<string name="revanced_hide_channel_watermark_summary_off">Watermark is shown</string> <string name="revanced_hide_channel_watermark_summary_off">Channel watermark is shown</string>
<string name="revanced_hide_crowdfunding_box_title">Hide crowdfunding box</string>
<string name="revanced_hide_crowdfunding_box_summary_on">Crowdfunding box is hidden</string>
<string name="revanced_hide_crowdfunding_box_summary_off">Crowdfunding box is shown</string>
<string name="revanced_hide_emergency_box_title">Hide emergency boxes</string> <string name="revanced_hide_emergency_box_title">Hide emergency boxes</string>
<string name="revanced_hide_emergency_box_summary_on">Emergency boxes are hidden</string> <string name="revanced_hide_emergency_box_summary_on">Emergency boxes are hidden</string>
<string name="revanced_hide_emergency_box_summary_off">Emergency boxes are shown</string> <string name="revanced_hide_emergency_box_summary_off">Emergency boxes are shown</string>
@@ -1558,6 +1558,11 @@ Tap here to learn more about DeArrow"</string>
<string name="revanced_loop_video_button_toast_on">Loop video is on</string> <string name="revanced_loop_video_button_toast_on">Loop video is on</string>
<string name="revanced_loop_video_button_toast_off">Loop video is off</string> <string name="revanced_loop_video_button_toast_off">Loop video is off</string>
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
<string name="revanced_pause_on_audio_interrupt_title">Pause on audio interrupt</string>
<string name="revanced_pause_on_audio_interrupt_summary_on">Playback pauses when other audio plays (e.g. navigation)</string>
<string name="revanced_pause_on_audio_interrupt_summary_off">Volume lowers when other audio plays</string>
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
<string name="revanced_spoof_device_dimensions_title">Spoof device dimensions</string> <string name="revanced_spoof_device_dimensions_title">Spoof device dimensions</string>
<string name="revanced_spoof_device_dimensions_summary_on">"Device dimensions spoofed <string name="revanced_spoof_device_dimensions_summary_on">"Device dimensions spoofed

View File

@@ -5,12 +5,9 @@ pluginManagement {
gradlePluginPortal() gradlePluginPortal()
google() google()
maven { maven {
name = "GitHubPackages" name = "githubPackages"
url = uri("https://maven.pkg.github.com/revanced/registry") url = uri("https://maven.pkg.github.com/revanced/registry")
credentials { credentials(PasswordCredentials::class)
username = providers.gradleProperty("gpr.user").getOrElse(System.getenv("GITHUB_ACTOR"))
password = providers.gradleProperty("gpr.key").getOrElse(System.getenv("GITHUB_TOKEN"))
}
} }
} }
} }