Compare commits

..

6 Commits

Author SHA1 Message Date
semantic-release-bot
274b6959f7 chore(release): 2.169.1-dev.4 [skip ci]
## [2.169.1-dev.4](https://github.com/revanced/revanced-patches/compare/v2.169.1-dev.3...v2.169.1-dev.4) (2023-04-23)

### Bug Fixes

* **twitch/block-embedded-ads:** correct spelling mistake ([#1962](https://github.com/revanced/revanced-patches/issues/1962)) ([936d9f6](936d9f61a3))
2023-04-23 03:05:18 +00:00
parknich081
936d9f61a3 fix(twitch/block-embedded-ads): correct spelling mistake (#1962) 2023-04-23 05:03:33 +02:00
semantic-release-bot
6c62db8019 chore(release): 2.169.1-dev.3 [skip ci]
## [2.169.1-dev.3](https://github.com/revanced/revanced-patches/compare/v2.169.1-dev.2...v2.169.1-dev.3) (2023-04-23)

### Bug Fixes

* **youtube/hide-video-action-buttons:** fix hide action buttons not working for some users ([#1959](https://github.com/revanced/revanced-patches/issues/1959)) ([064557c](064557c0c8))
2023-04-23 02:17:52 +00:00
LisoUseInAIKyrios
064557c0c8 fix(youtube/hide-video-action-buttons): fix hide action buttons not working for some users (#1959)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-04-23 06:16:10 +04:00
semantic-release-bot
85a32e833e chore(release): 2.169.1-dev.2 [skip ci]
## [2.169.1-dev.2](https://github.com/revanced/revanced-patches/compare/v2.169.1-dev.1...v2.169.1-dev.2) (2023-04-21)

### Bug Fixes

* **"enable-android-debugging:** don't include by default ([5614651](5614651d34))
2023-04-21 16:50:56 +00:00
oSumAtrIX
5614651d34 fix("enable-android-debugging): don't include by default 2023-04-21 18:49:10 +02:00
1409 changed files with 20717 additions and 91388 deletions

View File

@@ -1,3 +0,0 @@
[*.{kt,kts}]
ktlint_code_style = intellij_idea
ktlint_standard_no-wildcard-imports = disabled

9
.gitattributes vendored
View File

@@ -1,9 +0,0 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf

73
.github/ISSUE_TEMPLATE/bug-issue.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
name: 🐞 Bug report
description: Report a very clearly broken issue.
title: 'bug: <title>'
labels: [bug]
body:
- type: markdown
attributes:
value: |
# ReVanced bug report
Important to note that your issue may have already been reported before. Please check for existing issues [here](https://github.com/revanced/revanced-patches/labels/bug).
- type: dropdown
attributes:
label: Type
options:
- Error while patching
- Error at runtime
- Cosmetic
- Other
validations:
required: true
- type: textarea
attributes:
label: Bug description
description: How did you find the bug? Any additional details that might help?
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: Add the steps to reproduce this bug including your environment.
placeholder: Step 1. Download some files. Step 2. ...
validations:
required: true
- type: textarea
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. Capture crash logs by running `logcat | grep AndroidRuntime`.
render: shell
validations:
required: true
- type: textarea
attributes:
label: Screenshots or videos
description: Add screenshots or videos that show the bug here.
placeholder: Drag and drop the screenshots/videos into this box.
validations:
required: false
- type: textarea
attributes:
label: Solution
description: If applicable, add a possible solution.
validations:
required: false
- type: textarea
attributes:
label: Additional context
description: Add additional context here.
validations:
required: false
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your issue will be closed if you haven't done these steps.
options:
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
required: true
- label: I have written a short but informative title.
required: true
- label: I filled out all of the requested information in this issue properly.
required: true

View File

@@ -1,110 +0,0 @@
name: 🐞 Bug report
description: Report a bug or an issue.
title: 'bug: '
labels: ['Bug report']
body:
- type: markdown
attributes:
value: |
<p align="center">
<picture>
<source
width="256px"
media="(prefers-color-scheme: dark)"
srcset="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
>
<img
width="256px"
src="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
>
</picture>
<br>
<a href="https://revanced.app/">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-logo/revanced-logo.svg" />
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-logo/revanced-logo.svg" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://github.com/ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="http://revanced.app/discord">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://reddit.com/r/revancedapp">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://t.me/app_revanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://x.com/revancedapp">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://www.youtube.com/@ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
</picture>
</a>
<br>
<br>
Continuing the legacy of Vanced
</p>
# ReVanced Patches bug report
Before creating a new bug report, please keep the following in mind:
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Bug+report%22).
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
attributes:
label: Bug description
description: |
- Describe your bug in detail
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
- Add images and videos if possible
- List used patches if applicable
validations:
required: true
- type: textarea
attributes:
label: Error logs
description: Exceptions can be captured by running `logcat | grep AndroidRuntime` in a shell.
render: shell
- type: textarea
attributes:
label: Solution
description: If applicable, add a possible solution to the bug.
- type: textarea
attributes:
label: Additional context
description: Add additional context here.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your bug report will be closed if you don't follow the checklist below.
options:
- label: I have checked all open and closed bug reports and this is not a duplicate.
required: true
- label: I have chosen an appropriate title.
required: true
- label: All requested information has been provided properly.
required: true

View File

@@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 📃 Documentation
url: https://github.com/revanced/revanced-documentation/
about: Don't know how or where to start? Check out our documentation!
- name: 🗨 Discussions
url: https://github.com/revanced/revanced-suggestions/discussions
about: Have something unspecific to ReVanced Patches in mind? Search for or start a new discussion!
about: Got something you think should change or be added? Search for or start a new discussion!

View File

@@ -1,106 +0,0 @@
name: ⭐ Feature request
description: Create a detailed request for a new feature.
title: 'feat: '
labels: ['Feature request']
body:
- type: markdown
attributes:
value: |
<p align="center">
<picture>
<source
width="256px"
media="(prefers-color-scheme: dark)"
srcset="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
>
<img
width="256px"
src="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
>
</picture>
<br>
<a href="https://revanced.app/">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-logo/revanced-logo.svg" />
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-logo/revanced-logo.svg" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://github.com/ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="http://revanced.app/discord">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://reddit.com/r/revancedapp">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://t.me/app_revanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://x.com/revancedapp">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://www.youtube.com/@ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
</picture>
</a>
<br>
<br>
Continuing the legacy of Vanced
</p>
# ReVanced Patches feature request
Before creating a new feature request, please keep the following in mind:
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Feature+request%22).
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
attributes:
label: Feature description
description: |
- Describe your feature in detail
- Add images, videos, links, examples, references, etc. if possible
- Add the target application name in case you request a new patch
- type: textarea
attributes:
label: Motivation
description: |
A strong motivation is necessary for a feature request to be considered.
- Why should this feature be implemented?
- What is the explicit use case?
- What are the benefits?
- What makes this feature important?
validations:
required: true
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your feature request will be closed if you don't follow the checklist below.
options:
- label: I have checked all open and closed feature requests and this is not a duplicate
required: true
- label: I have chosen an appropriate title.
required: true
- label: All requested information has been provided properly.
required: true

View File

@@ -0,0 +1,41 @@
name: ⭐ Patch Request
description: Create a detailed patch request.
title: 'feat(patch): '
labels: [patch-request]
body:
- type: textarea
attributes:
label: Application
description: Which application is this patch for?
validations:
required: true
- type: textarea
attributes:
label: Issue
description: What is the issue this patch should solve?
validations:
required: true
- type: textarea
attributes:
label: Patch
description: "Describe the patch you'd like to see in detail."
validations:
required: true
- type: textarea
attributes:
label: Motivation
description: Why should your patch request should be considered? What makes it valuable to the community?
validations:
required: true
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your issue will be closed if you haven't done these steps.
options:
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
required: true
- label: I have written a short but informative title.
required: true
- label: I filled out all of the requested information in this issue properly.
required: true

2
.github/config.yml vendored
View File

@@ -1,2 +0,0 @@
firstPRMergeComment: >
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) to receive a role for your contribution.

View File

@@ -1,22 +0,0 @@
version: 2
updates:
- package-ecosystem: github-actions
labels: []
directory: /
target-branch: dev
schedule:
interval: monthly
- package-ecosystem: npm
labels: []
directory: /
target-branch: dev
schedule:
interval: monthly
- package-ecosystem: gradle
labels: []
directory: /
target-branch: dev
schedule:
interval: monthly

View File

@@ -1,25 +0,0 @@
name: Build pull request
on:
workflow_dispatch:
pull_request:
branches:
- dev
jobs:
release:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build --no-daemon

View File

@@ -1,4 +1,4 @@
name: Open a PR to main
name: PR to main
on:
push:
@@ -7,7 +7,7 @@ on:
workflow_dispatch:
env:
MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main`
MESSAGE: merge branch `${{ github.head_ref || github.ref_name }}` to `main`
jobs:
pull-request:
@@ -15,16 +15,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Open pull request
uses: repo-sync/pull-request@v2
with:
destination_branch: "main"
pr_title: "chore: ${{ env.MESSAGE }}"
destination_branch: 'main'
pr_title: 'chore: ${{ env.MESSAGE }}'
pr_body: |
This pull request will ${{ env.MESSAGE }}.
## Dependencies before merge
- [ ] https://github.com/revanced/revanced-integrations

View File

@@ -1,35 +0,0 @@
name: Pull strings
on:
workflow_dispatch:
schedule:
- cron: 0 0 1 * *
jobs:
pull:
name: Pull strings
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: dev
- name: Pull strings
uses: crowdin/github-action@v2
with:
config: crowdin.yml
download_translations: true
localization_branch_name: feat/translations
create_pull_request: true
pull_request_title: "chore: Sync translations"
pull_request_body: "Sync translations from [crowdin.com/project/revanced](https://crowdin.com/project/revanced)"
pull_request_base_branch_name: "dev"
commit_message: "chore: Sync translations"
github_user_name: revanced-bot
github_user_email: github@revanced.app
env:
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

View File

@@ -1,29 +0,0 @@
name: Push strings
on:
workflow_dispatch:
push:
branches:
- dev
paths:
- src/main/resources/addresources/values/strings.xml
jobs:
push:
name: Push strings
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Push strings
uses: crowdin/github-action@v2
with:
config: crowdin.yml
upload_sources: true
env:
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

View File

@@ -6,6 +6,10 @@ on:
branches:
- main
- dev
pull_request:
branches:
- main
- dev
jobs:
release:
@@ -13,37 +17,35 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
# Make sure the release step uses its own credentials:
# https://github.com/cycjimmy/semantic-release-action#private-packages
persist-credentials: false
fetch-depth: 0
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
- name: Build
- name: Setup JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'zulu'
cache: gradle
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Setup Android SDK
uses: amyu/setup-android@v2
with:
cache-disabled: false
sdk-version: '33'
build-tools-version: '33.0.1'
- name: Build with Gradle
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew generateMeta clean
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
cache: 'npm'
- name: Install dependencies
run: ./gradlew generateMeta clean --no-daemon
- name: Setup semantic-release
run: npm install
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
fingerprint: ${{ vars.GPG_FINGERPRINT }}
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}

View File

@@ -1,18 +0,0 @@
name: Update Gradle wrapper
on:
schedule:
- cron: "0 0 1 * *"
workflow_dispatch:
jobs:
update:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Update Gradle Wrapper
uses: gradle-update/update-gradle-wrapper-action@v1
with:
target-branch: dev

2
.idea/misc.xml generated
View File

@@ -4,5 +4,5 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="azul-17" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="azul-17" project-jdk-type="JavaSDK" />
</project>

View File

@@ -7,13 +7,7 @@
}
],
"plugins": [
[
"@semantic-release/commit-analyzer", {
"releaseRules": [
{ "type": "build", "scope": "Needs bump", "release": "patch" }
]
}
],
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"gradle-semantic-release-plugin",
@@ -33,7 +27,7 @@
{
"assets": [
{
"path": "build/libs/revanced-patches*"
"path": "build/libs/*.jar"
},
{
"path": "patches.json"

File diff suppressed because it is too large Load Diff

View File

@@ -1,128 +0,0 @@
<p align="center">
<picture>
<source
width="256px"
media="(prefers-color-scheme: dark)"
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
>
<img
width="256px"
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
>
</picture>
<br>
<a href="https://revanced.app/">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo.svg" />
<img height="24px" src="assets/revanced-logo/revanced-logo.svg" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://github.com/ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="http://revanced.app/discord">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://reddit.com/r/revancedapp">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://t.me/app_revanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://x.com/revancedapp">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://www.youtube.com/@ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
</picture>
</a>
<br>
<br>
Continuing the legacy of Vanced
</p>
# 👋 Contribution guidelines
This document describes how to contribute to ReVanced Patches.
## 📖 Resources to help you get started
* The [documentation](https://github.com/ReVanced/revanced-patcher/tree/main/docs) contains the fundamentals
of ReVanced Patcher and how to use ReVanced Patcher to create patches
* [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
* [Issues](https://github.com/ReVanced/revanced-patches/issues) are where we keep track of bugs and feature requests
## 🙏 Submitting a feature request
Features can be requested by opening an issue using the
[Feature request issue template](https://github.com/ReVanced/revanced-patches/issues/new?assignees=&labels=Feature+request&projects=&template=feature_request.yml&title=feat%3A+).
> **Note**
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Patches.
> Good motivation has to be provided for a request to be accepted.
## 🐞 Submitting a bug report
If you encounter a bug while using ReVanced Patches, open an issue using the
[Bug report issue template](https://github.com/ReVanced/revanced-patches/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
## 🌐 Submitting translations
You can contribute translations at [translate.revanced.app](https://translate.revanced.app).
## 🧑‍⚖️ Guidelines for requesting or contributing patches
To maintain a high-quality and ethical collection of patches, the following guidelines for requesting
or contributing patches are effective as of September 14, 2023. Any patches present prior to this date
are unaffected by this change.
> **Note**
> We generally adhere to the guidelines outlined below. However, we may make exceptions
> in specific cases based on our discretion. Pull requests for patches that deviate from the guidelines
> will be evaluated individually. While a patch may not align with our general guidelines,
> we will consider its acceptance on a case-by-case basis, taking into account its impact on user experience
> and ethical considerations. We reserve the right to make exceptions for patches that provide significant value.
✅ Examples for acceptable patches include:
* Customizations: Feel free to contribute patches that allow users to personalize their experience
* Ad-Blocking: Patches aimed at enhancing user privacy and blocking intrusive advertisements are appreciated
* Feature additions: Patches that add new features or change behaviour to the app are welcome
❌ Examples for unacceptable patches include:
* Payment circumvention: We do not accept patches that exist solely to bypass payment for the app or any of its features
* Malicious patches: Patches that are malicious in nature are not allowed
## 📝 How to contribute
1. Before contributing, it is recommended to open an issue to discuss your change
with the maintainers of ReVanced Patches. This will help you determine whether your change is acceptable
and whether it is worth your time to implement it
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
3. Commit your changes. In case you are contributing a new patch, make sure to follow the conventions for patches
described in the [ReVanced Patcher documentation](https://github.com/ReVanced/revanced-patcher/tree/main/docs)
4. Submit a pull request to the `dev` branch of the repository and reference issues
that your pull request closes in the description of your pull request
5. Our team will review your pull request and provide feedback. Once your pull request is approved,
it will be merged into the `dev` branch and will be included in the next release of ReVanced Patches
❤️ Thank you for considering contributing to ReVanced Patches,
ReVanced

122
README-template.md Normal file
View File

@@ -0,0 +1,122 @@
## 🧩 Patches
The official Patch bundle provided by ReVanced and the community.
> Looking for the JSON variant of this? [Click here](patches.json).
{{ table }}
## 📝 JSON Format
This section explains the JSON format for the [patches.json](patches.json) file.
The file contains an array of objects, each object representing a patch. The object contains the following properties:
| key | description |
|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name` | The name of the patch. |
| `description` | The description of the patch. |
| `version` | The version of the patch. |
| `excluded` | Whether the patch is excluded by default. If `true`, the patch must never be included by default. |
| `options` | An array of options for this patch. |
| `options.key` | The key of the option. |
| `options.title` | The title of the option. |
| `options.description` | The description of the option. |
| `options.required` | Whether the option is required. |
| `options.choices?` | An array of choices of the option. This may be `null` if this option has no choices. The element type of this array may be any type. It can be a `String`, `Int` or something else. |
| `dependencies` | An array of dependencies, which are patch names. |
| `compatiblePackages` | An array of packages compatible with this patch. |
| `compatiblePackages.name` | The name of the package. |
| `compatiblePackages.versions` | An array of versions of the package compatible with this patch. If empty, all versions are seemingly compatible. |
Example:
```json
[
{
"name": "remember-video-quality",
"description": "Adds the ability to remember the video quality you chose in the video quality flyout.",
"version": "0.0.1",
"excluded": false,
"options": [],
"dependencies": [
"integrations",
"video-id-hook"
],
"compatiblePackages": [
{
"name": "com.google.android.youtube",
"versions": [
"17.22.36",
"17.24.35",
"17.26.35",
"17.27.39",
"17.28.34",
"17.29.34",
"17.32.35",
"17.33.42"
]
}
]
},
{
"name": "theme",
"description": "Enables a custom theme.",
"version": "0.0.1",
"excluded": false,
"deprecated": false,
"options": [
{
"key": "theme",
"title": "Theme",
"description": "Select a theme.",
"required": true,
"choices": [
"Amoled"
]
}
],
"dependencies": [
"locale-config-fix"
],
"compatiblePackages": [
{
"name": "com.google.android.youtube",
"versions": []
}
]
},
{
"name": "custom-branding",
"description": "Changes the YouTube launcher icon and name to your choice (defaults to ReVanced).",
"version": "0.0.1",
"excluded": false,
"deprecated": false,
"options": [
{
"key": "appName",
"title": "Application Name",
"description": "The name of the application it will show on your home screen.",
"required": true,
"choices": null
},
{
"key": "appIconPath",
"title": "Application Icon Path",
"description": "A path to the icon of the application.",
"required": false,
"choices": null
}
],
"dependencies": [
"locale-config-fix"
],
"compatiblePackages": [
{
"name": "com.google.android.youtube",
"versions": []
}
]
}
]
```

557
README.md
View File

@@ -1,105 +1,488 @@
<p align="center">
<picture>
<source
width="256px"
media="(prefers-color-scheme: dark)"
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
>
<img
width="256px"
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
>
</picture>
<br>
<a href="https://revanced.app/">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo.svg" />
<img height="24px" src="assets/revanced-logo/revanced-logo.svg" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://github.com/ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="http://revanced.app/discord">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://reddit.com/r/revancedapp">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://t.me/app_revanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://x.com/revancedapp">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://www.youtube.com/@ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
</picture>
</a>
<br>
<br>
Continuing the legacy of Vanced
</p>
## 🧩 Patches
# 🧩 ReVanced Patches
The official Patch bundle provided by ReVanced and the community.
![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/ReVanced/revanced-patches/release.yml)
![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg)
> Looking for the JSON variant of this? [Click here](patches.json).
This repository contains a collection of ReVanced Patches.
### [📦 `com.google.android.youtube`](https://play.google.com/store/apps/details?id=com.google.android.youtube)
<details>
## ❓ About
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `always-autorepeat` | Always repeats the playing video again. | 18.08.37 |
| `client-spoof` | Spoofs a patched client to allow playback. | all |
| `comments` | Hides components related to comments. | 18.08.37 |
| `copy-video-url` | Adds buttons in player to copy video links. | 18.08.37 |
| `custom-branding` | Changes the YouTube launcher icon and name to your choice (defaults to ReVanced). | all |
| `custom-video-buffer` | Lets you change the buffers of videos. | 18.08.37 |
| `custom-video-speed` | Adds more video speed options. | 18.08.37 |
| `disable-auto-captions` | Disable forced captions from being automatically enabled. | 18.08.37 |
| `disable-fullscreen-panels` | Disables video description and comments panel in fullscreen view. | 18.08.37 |
| `disable-player-popup-panels` | Disables panels from appearing automatically when going into fullscreen (playlist or live chat). | 18.08.37 |
| `disable-shorts-on-startup` | Disables playing YouTube Shorts when launching YouTube. | 18.08.37 |
| `disable-zoom-haptics` | Disables haptics when zooming. | all |
| `downloads` | Enables downloading music and videos from YouTube. | 18.08.37 |
| `enable-debugging` | Adds debugging options. | all |
| `enable-wide-searchbar` | Replaces the search icon with a wide search bar. This will hide the YouTube logo when active. | 18.08.37 |
| `general-ads` | Removes general ads. | 18.08.37 |
| `hdr-auto-brightness` | Makes the brightness of HDR videos follow the system default. | 18.08.37 |
| `hide-album-cards` | Hides the album cards below the artist description. | 18.08.37 |
| `hide-artist-card` | Hides the artist card below the searchbar. | 18.08.37 |
| `hide-autoplay-button` | Hides the autoplay button in the video player. | 18.08.37 |
| `hide-breaking-news-shelf` | Hides the breaking news shelf on the homepage tab. | 18.08.37 |
| `hide-captions-button` | Hides the captions button on video player. | 18.08.37 |
| `hide-cast-button` | Hides the cast button in the video player. | all |
| `hide-create-button` | Hides the create button in the navigation bar. | 18.08.37 |
| `hide-crowdfunding-box` | Hides the crowdfunding box between the player and video description. | 18.08.37 |
| `hide-email-address` | Hides the email address in the account switcher. | 18.08.37 |
| `hide-endscreen-cards` | Hides the suggested video cards at the end of a video in fullscreen. | 18.08.37 |
| `hide-floating-microphone-button` | Hides the floating microphone button which appears in search. | 18.08.37 |
| `hide-info-cards` | Hides info cards in videos. | 18.08.37 |
| `hide-my-mix` | Hides mix playlists. | 18.08.37 |
| `hide-player-buttons` | Adds the option to hide video player previous and next buttons. | all |
| `hide-seekbar` | Hides the seekbar. | 18.08.37 |
| `hide-shorts-button` | Hides the shorts button on the navigation bar. | 18.08.37 |
| `hide-timestamp` | Hides timestamp in video player. | 18.08.37 |
| `hide-video-action-buttons` | Adds the options to hide action buttons under a video. | 18.08.37 |
| `hide-watch-in-vr` | Hides the option to watch in VR from the player settings flyout panel. | 18.08.37 |
| `hide-watermark` | Hides creator's watermarks on videos. | 18.08.37 |
| `minimized-playback` | Enables minimized and background playback. | 18.08.37 |
| `old-quality-layout` | Enables the original video quality flyout in the video player settings | 18.08.37 |
| `open-links-externally` | Open links outside of the app directly in your browser. | 18.08.37 |
| `premium-heading` | Shows premium branding on the home screen. | all |
| `remember-playback-speed` | Adds the ability to remember the playback speed you chose in the video playback speed flyout. | 18.08.37 |
| `remember-video-quality` | Adds the ability to remember the video quality you chose in the video quality flyout. | 18.08.37 |
| `remove-player-button-background` | Removes the background from the video player buttons. | 18.08.37 |
| `return-youtube-dislike` | Shows the dislike count of videos using the Return YouTube Dislike API. | 18.08.37 |
| `seekbar-tapping` | Enables tap-to-seek on the seekbar of the video player. | 18.08.37 |
| `sponsorblock` | Integrates SponsorBlock which allows skipping video segments such as sponsored content. | 18.08.37 |
| `spoof-app-version` | Tricks YouTube into thinking, you are running an older version of the app. One of the side effects also includes restoring the old UI. | 18.08.37 |
| `spoof-signature-verification` | Spoofs a patched client to prevent playback issues. | 18.08.37 |
| `swipe-controls` | Adds volume and brightness swipe controls. | 18.08.37 |
| `tablet-mini-player` | Enables the tablet mini player layout. | 18.08.37 |
| `theme` | Applies a custom theme. | all |
| `vanced-microg-support` | Allows YouTube ReVanced to run without root and under a different package name with Vanced MicroG. | 18.08.37 |
| `video-ads` | Removes ads in the video player. | 18.08.37 |
</details>
Patches are small modifications to Android apps that allow you to change the behavior of or add new features,
block ads, customize the appearance, and much more.
### [📦 `com.google.android.apps.youtube.music`](https://play.google.com/store/apps/details?id=com.google.android.apps.youtube.music)
<details>
## 💪 Features
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `background-play` | Enables playing music in the background. | all |
| `bypass-certificate-checks` | Bypasses certificate checks which prevent YouTube Music from working on Android Auto. | 5.39.52 |
| `codecs-unlock` | Adds more audio codec options. The new audio codecs usually result in better audio quality. | all |
| `compact-header` | Hides the music category bar at the top of the homepage. | all |
| `exclusive-audio-playback` | Enables the option to play music without video. | all |
| `hide-get-premium` | Removes all "Get Premium" evidences from the avatar menu. | 5.39.52 |
| `minimized-playback-music` | Enables minimized playback on Kids music. | all |
| `music-microg-support` | Allows YouTube Music ReVanced to run without root and under a different package name. | all |
| `music-video-ads` | Removes ads in the music player. | all |
| `tasteBuilder-remover` | Removes the "Tell us which artists you like" card from the home screen. | all |
| `upgrade-button-remover` | Removes the upgrade tab from the pivot bar. | all |
</details>
Some of the features the patches provide are:
### [📦 `com.ss.android.ugc.trill`](https://play.google.com/store/apps/details?id=com.ss.android.ugc.trill)
<details>
* 🚫 **Block ads**: Say goodbye to ads
***Customize your app**: Personalize the appearance of apps with various layouts and themes
* 🪄 **Add new features**: Extend the functionality of apps with lots of new features
* ⚙️ **Miscellaneous and general purpose**: Rename packages, enable debugging, disable screen capture restrictions,
export activities, etc.
***And much more!**
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `disable-login-requirement` | Do not force login. | all |
| `downloads` | Removes download restrictions and changes the default path to download to. | 27.8.3 |
| `feed-filter` | Filters tiktok videos: removing ads, removing livestreams. | 27.8.3 |
| `fix-google-login` | Allows logging in with a Google account. | all |
| `hide-ads` | Removes ads from TikTok. | all |
| `playback-speed` | Enables the playback speed option for all videos. | all |
| `settings` | Adds ReVanced settings to TikTok. | 27.8.3 |
| `show-seekbar` | Shows progress bar for all video. | all |
| `sim-spoof` | Spoofs the information which is retrieved from the sim-card. | 27.8.3 |
</details>
For a complete list of all available patches, visit [revanced.app/patches](https://revanced.app/patches).
### [📦 `com.zhiliaoapp.musically`](https://play.google.com/store/apps/details?id=com.zhiliaoapp.musically)
<details>
## 🚀 How to get started
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `disable-login-requirement` | Do not force login. | all |
| `downloads` | Removes download restrictions and changes the default path to download to. | 27.8.3 |
| `feed-filter` | Filters tiktok videos: removing ads, removing livestreams. | 27.8.3 |
| `fix-google-login` | Allows logging in with a Google account. | all |
| `hide-ads` | Removes ads from TikTok. | all |
| `playback-speed` | Enables the playback speed option for all videos. | all |
| `settings` | Adds ReVanced settings to TikTok. | 27.8.3 |
| `show-seekbar` | Shows progress bar for all video. | all |
| `sim-spoof` | Spoofs the information which is retrieved from the sim-card. | 27.8.3 |
</details>
You can use [ReVanced CLI](https://github.com/ReVanced/revanced-cli) or [ReVanced Manager](https://github.com/ReVanced/revanced-manager) to use ReVanced Patches.
### [📦 `tv.twitch.android.app`](https://play.google.com/store/apps/details?id=tv.twitch.android.app)
<details>
## 📚 Everything else
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `block-audio-ads` | Blocks audio ads in streams and VODs. | 14.6.1 |
| `block-embedded-ads` | Blocks embedded stream ads using services like TTV.lol or PurpleAdBlocker. | 14.6.1 |
| `block-video-ads` | Blocks video ads in streams and VODs. | 14.6.1 |
| `debug-mode` | Enables Twitch's internal debugging mode. | all |
| `settings` | Adds settings menu to Twitch. | all |
| `show-deleted-messages` | Shows deleted chat messages behind a clickable spoiler. | all |
</details>
### 📙 Contributing
### [📦 `com.twitter.android`](https://play.google.com/store/apps/details?id=com.twitter.android)
<details>
Thank you for considering contributing to ReVanced Patches. You can find the contribution guidelines [here](CONTRIBUTING.md).
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `dynamic-color` | Replaces the default Twitter Blue with the users Material You palette. | all |
| `hide-ads` | Hides ads. | all |
| `hide-recommended-users` | Hides recommended users. | all |
| `hide-views-stats` | Hides the view stats under tweets. | 9.71.0-release.0 |
</details>
### 🛠️ Building
### [📦 `com.spotify.music`](https://play.google.com/store/apps/details?id=com.spotify.music)
<details>
To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `disable-capture-restriction` | Allows capturing Spotify's audio output while screen sharing or screen recording. | all |
| `hide-premium-navbar` | Removes the premium tab from the navbar. | all |
| `spotify-theme` | Applies a custom theme. | all |
</details>
## 📜 Licence
### [📦 `com.reddit.frontpage`](https://play.google.com/store/apps/details?id=com.reddit.frontpage)
<details>
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.
Any modifications to ReVanced Patches must also be made available under the GPL,
along with build & install instructions.
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `general-reddit-ads` | Removes general ads from the Reddit frontpage and subreddits. | 2023.12.0 |
| `hide-subreddit-banner` | Hides banner ads from comments on subreddits. | 2023.12.0 |
| `premium-icon-reddit` | Unlocks premium Reddit app icons. | all |
</details>
### [📦 `com.vanced.android.youtube`](https://play.google.com/store/apps/details?id=com.vanced.android.youtube)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `client-spoof` | Spoofs a patched client to allow playback. | all |
| `hide-ads` | Removes general ads. | all |
</details>
### [📦 `at.gv.bmf.bmf2go`](https://play.google.com/store/apps/details?id=at.gv.bmf.bmf2go)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `remove-bootloader-detection` | Removes the check for an unlocked bootloader. | 2.2.0 |
| `remove-root-detection` | Removes the check for root permissions. | 2.2.0 |
</details>
### [📦 `at.gv.oe.app`](https://play.google.com/store/apps/details?id=at.gv.oe.app)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `remove-root-detection` | Removes the check for root permissions and unlocked bootloader. | 2.6.0 |
| `spoof-signature` | Spoofs the signature of the app. | 2.6.0 |
</details>
### [📦 `com.myprog.hexedit`](https://play.google.com/store/apps/details?id=com.myprog.hexedit)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `disable-ads` | Disables ads in HexEditor. | all |
</details>
### [📦 `com.spotify.lite`](https://play.google.com/store/apps/details?id=com.spotify.lite)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `enable-on-demand` | Enables listening to songs on-demand, allowing to play any song from playlists, albums or artists without limitations. This does not remove ads. | all |
</details>
### [📦 `com.nis.app`](https://play.google.com/store/apps/details?id=com.nis.app)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `hide-ads` | Removes ads from Inshorts. | all |
</details>
### [📦 `com.facebook.orca`](https://play.google.com/store/apps/details?id=com.facebook.orca)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `hide-inbox-ads` | Hides ads in inbox. | all |
</details>
### [📦 `com.instagram.android`](https://play.google.com/store/apps/details?id=com.instagram.android)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `hide-timeline-ads` | Removes ads from the timeline. | all |
</details>
### [📦 `org.citra.citra_emu`](https://play.google.com/store/apps/details?id=org.citra.citra_emu)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `premium-unlock` | Unlocks premium functions. | all |
</details>
### [📦 `org.citra.citra_emu.canary`](https://play.google.com/store/apps/details?id=org.citra.citra_emu.canary)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `premium-unlock` | Unlocks premium functions. | all |
</details>
### [📦 `com.backdrops.wallpapers`](https://play.google.com/store/apps/details?id=com.backdrops.wallpapers)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `pro-unlock` | Unlocks pro-only functions. | 4.52 |
</details>
### [📦 `de.dwd.warnapp`](https://play.google.com/store/apps/details?id=de.dwd.warnapp)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `promo-code-unlock` | Disables the validation of promo code. Any code will work to unlock all features. | all |
</details>
### [📦 `net.binarymode.android.irplus`](https://play.google.com/store/apps/details?id=net.binarymode.android.irplus)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `remove-ads` | Removes all ads from the app. | all |
</details>
### [📦 `eu.faircode.netguard`](https://play.google.com/store/apps/details?id=eu.faircode.netguard)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `remove-broadcasts-restriction` | Enables starting/stopping NetGuard via broadcasts. | all |
</details>
### [📦 `com.dci.dev.androidtwelvewidgets`](https://play.google.com/store/apps/details?id=com.dci.dev.androidtwelvewidgets)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-paid-widgets` | Unlocks paid widgets of the app | all |
</details>
### [📦 `com.microblink.photomath`](https://play.google.com/store/apps/details?id=com.microblink.photomath)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-plus` | Unlocks plus features. | 8.9.0 |
</details>
### [📦 `io.yuka.android`](https://play.google.com/store/apps/details?id=io.yuka.android)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-premium` | Unlocks premium features. | all |
</details>
### [📦 `com.teslacoilsw.launcher`](https://play.google.com/store/apps/details?id=com.teslacoilsw.launcher)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-prime` | Unlocks Nova Prime and all functions of the app. | all |
</details>
### [📦 `ginlemon.iconpackstudio`](https://play.google.com/store/apps/details?id=ginlemon.iconpackstudio)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-pro` | Unlocks all pro features. | all |
</details>
### [📦 `co.windyapp.android`](https://play.google.com/store/apps/details?id=co.windyapp.android)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-pro` | Unlocks all pro features. | all |
</details>
### [📦 `com.ithebk.expensemanager`](https://play.google.com/store/apps/details?id=com.ithebk.expensemanager)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-pro` | Unlocks pro features. | all |
</details>
### [📦 `org.totschnig.myexpenses`](https://play.google.com/store/apps/details?id=org.totschnig.myexpenses)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-pro` | Unlocks all professional features. | 3.4.9 |
</details>
### [📦 `com.zombodroid.MemeGenerator`](https://play.google.com/store/apps/details?id=com.zombodroid.MemeGenerator)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-pro` | Unlocks pro features. | 4.6364 |
</details>
### [📦 `com.awedea.nyx`](https://play.google.com/store/apps/details?id=com.awedea.nyx)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-pro` | Unlocks all pro features. | all |
</details>
### [📦 `com.ticktick.task`](https://play.google.com/store/apps/details?id=com.ticktick.task)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-themes` | Unlocks all themes that are inaccessible until a certain level is reached. | all |
</details>
### [📦 `net.dinglisch.android.taskerm`](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm)
<details>
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `unlock-trial` | Unlocks the trial version. | all |
</details>
## 📝 JSON Format
This section explains the JSON format for the [patches.json](patches.json) file.
The file contains an array of objects, each object representing a patch. The object contains the following properties:
| key | description |
|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name` | The name of the patch. |
| `description` | The description of the patch. |
| `version` | The version of the patch. |
| `excluded` | Whether the patch is excluded by default. If `true`, the patch must never be included by default. |
| `options` | An array of options for this patch. |
| `options.key` | The key of the option. |
| `options.title` | The title of the option. |
| `options.description` | The description of the option. |
| `options.required` | Whether the option is required. |
| `options.choices?` | An array of choices of the option. This may be `null` if this option has no choices. The element type of this array may be any type. It can be a `String`, `Int` or something else. |
| `dependencies` | An array of dependencies, which are patch names. |
| `compatiblePackages` | An array of packages compatible with this patch. |
| `compatiblePackages.name` | The name of the package. |
| `compatiblePackages.versions` | An array of versions of the package compatible with this patch. If empty, all versions are seemingly compatible. |
Example:
```json
[
{
"name": "remember-video-quality",
"description": "Adds the ability to remember the video quality you chose in the video quality flyout.",
"version": "0.0.1",
"excluded": false,
"options": [],
"dependencies": [
"integrations",
"video-id-hook"
],
"compatiblePackages": [
{
"name": "com.google.android.youtube",
"versions": [
"17.22.36",
"17.24.35",
"17.26.35",
"17.27.39",
"17.28.34",
"17.29.34",
"17.32.35",
"17.33.42"
]
}
]
},
{
"name": "theme",
"description": "Enables a custom theme.",
"version": "0.0.1",
"excluded": false,
"deprecated": false,
"options": [
{
"key": "theme",
"title": "Theme",
"description": "Select a theme.",
"required": true,
"choices": [
"Amoled"
]
}
],
"dependencies": [
"locale-config-fix"
],
"compatiblePackages": [
{
"name": "com.google.android.youtube",
"versions": []
}
]
},
{
"name": "custom-branding",
"description": "Changes the YouTube launcher icon and name to your choice (defaults to ReVanced).",
"version": "0.0.1",
"excluded": false,
"deprecated": false,
"options": [
{
"key": "appName",
"title": "Application Name",
"description": "The name of the application it will show on your home screen.",
"required": true,
"choices": null
},
{
"key": "appIconPath",
"title": "Application Icon Path",
"description": "A path to the icon of the application.",
"required": false,
"choices": null
}
],
"dependencies": [
"locale-config-fix"
],
"compatiblePackages": [
{
"name": "com.google.android.youtube",
"versions": []
}
]
}
]
```

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Logo"><g id="Ring"><circle id="Ring-Background" serif:id="Ring Background" cx="400" cy="400" r="400" style="fill:#1b1b1b;"/><path id="Ring1" serif:id="Ring" d="M400,0c220.766,0 400,179.234 400,400c-0,220.766 -179.234,400 -400,400c-220.766,-0 -400,-179.234 -400,-400c0,-220.766 179.234,-400 400,-400Zm-0,36c200.897,-0 364,163.103 364,364c0,200.897 -163.103,364 -364,364c-200.897,0 -364,-163.103 -364,-364c-0,-200.897 163.103,-364 364,-364Z" style="fill:url(#_Linear1);"/></g><g id="Shape"><path id="V-Shape" serif:id="V Shape" d="M538.74,269.872c1.481,-3.382 1.157,-7.283 -0.863,-10.373c-2.021,-3.091 -5.464,-4.954 -9.156,-4.954c-5.148,0 -10.435,0 -14.165,0c-3.1,0 -5.907,1.834 -7.153,4.672c-12.468,28.396 -78.273,178.273 -100.25,228.328c-1.246,2.838 -4.053,4.671 -7.154,4.671c-3.1,0 -5.907,-1.833 -7.153,-4.671c-21.977,-50.055 -87.782,-199.932 -100.25,-228.328c-1.246,-2.838 -4.053,-4.672 -7.153,-4.672c-3.73,0 -9.017,0 -14.164,0c-3.693,0 -7.135,1.863 -9.156,4.954c-2.02,3.09 -2.344,6.991 -0.863,10.373c23.557,53.766 101.872,232.519 117.871,269.034c1.743,3.979 5.674,6.549 10.018,6.549c6.293,-0 15.408,-0 21.701,-0c4.344,-0 8.275,-2.57 10.018,-6.549c15.999,-36.515 94.315,-215.268 117.872,-269.034Z" style="fill:#fff;"/><path id="Diamond" d="M408.119,395.312c-1.675,2.901 -4.77,4.688 -8.119,4.688c-3.349,-0 -6.444,-1.787 -8.119,-4.688c-16.997,-29.44 -56.156,-97.264 -73.153,-126.704c-1.675,-2.901 -1.675,-6.474 0,-9.375c1.675,-2.901 4.77,-4.688 8.119,-4.688c33.995,0 112.311,0 146.306,0c3.349,0 6.444,1.787 8.119,4.688c1.675,2.901 1.675,6.474 -0,9.375c-16.997,29.44 -56.156,97.264 -73.153,126.704Z" style="fill:url(#_Linear2);"/></g></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(4.89859e-14,800,-800,4.89859e-14,400.001,3.31681e-10)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.77155e-14,289.317,-282.535,1.73003e-14,400,254.545)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,153 +1,66 @@
import org.gradle.kotlin.dsl.support.listFilesOrdered
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlin)
alias(libs.plugins.binary.compatibility.validator)
`maven-publish`
signing
kotlin("jvm") version "1.8.10"
}
group = "app.revanced"
val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
repositories {
mavenCentral()
mavenLocal()
google()
maven {
// A repository must be specified for some reason. "registry" is a dummy.
url = uri("https://maven.pkg.github.com/revanced/registry")
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
username = githubUsername
password = githubPassword
}
}
}
dependencies {
implementation(libs.revanced.patcher)
implementation(libs.smali)
// TODO: Required because build fails without it. Find a way to remove this dependency.
implementation(libs.guava)
// Used in JsonGenerator.
implementation(libs.gson)
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
java {
targetCompatibility = JavaVersion.VERSION_11
implementation("app.revanced:revanced-patcher:7.0.0")
implementation("app.revanced:multidexlib2:2.5.3-a3836654")
// Required for meta
implementation("com.google.code.gson:gson:2.10.1")
}
tasks {
withType(Jar::class) {
exclude("app/revanced/meta")
manifest {
attributes["Name"] = "ReVanced Patches"
attributes["Description"] = "Patches for ReVanced."
attributes["Version"] = version
attributes["Timestamp"] = System.currentTimeMillis().toString()
attributes["Source"] = "git@github.com:revanced/revanced-patches.git"
attributes["Author"] = "ReVanced"
attributes["Contact"] = "contact@revanced.app"
attributes["Origin"] = "https://revanced.app"
attributes["License"] = "GNU General Public License v3.0"
}
}
register("buildDexJar") {
description = "Build and add a DEX to the JAR file"
group = "build"
register<DefaultTask>("generateBundle") {
description = "Generate dex files from build and bundle them in the jar file"
dependsOn(build)
doLast {
val d8 = File(System.getenv("ANDROID_HOME")).resolve("build-tools")
.listFilesOrdered().last().resolve("d8").absolutePath
val patchesJar = configurations.archives.get().allArtifacts.files.files.first().absolutePath
val workingDirectory = layout.buildDirectory.dir("libs").get().asFile
val androidHome = System.getenv("ANDROID_HOME") ?: throw GradleException("ANDROID_HOME not found")
val d8 = "${androidHome}/build-tools/33.0.1/d8"
val input = configurations.archives.get().allArtifacts.files.files.first().absolutePath
val work = File("${buildDir}/libs")
exec {
workingDir = workingDirectory
commandLine = listOf(d8, "--release", patchesJar)
workingDir = work
commandLine = listOf(d8, input)
}
exec {
workingDir = workingDirectory
commandLine = listOf("zip", "-u", patchesJar, "classes.dex")
workingDir = work
commandLine = listOf("zip", "-u", input, "classes.dex")
}
}
}
register<JavaExec>("generatePatchesFiles") {
description = "Generate patches files"
register<JavaExec>("generateMeta") {
description = "Generate metadata for this bundle"
dependsOn(build)
classpath = sourceSets["main"].runtimeClasspath
mainClass.set("app.revanced.generator.MainKt")
mainClass.set("app.revanced.meta.PatchesFileGenerator")
}
// Needed by gradle-semantic-release-plugin.
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
publish {
dependsOn("buildDexJar")
dependsOn("generatePatchesFiles")
// Dummy task to fix the Gradle semantic-release plugin.
// Remove this if you forked it to support building only.
// Tracking issue: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
register<DefaultTask>("publish") {
group = "publish"
description = "Dummy task"
dependsOn(named("generateBundle"), named("generateMeta"))
}
}
publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-patches")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
publications {
create<MavenPublication>("revanced-patches-publication") {
from(components["java"])
pom {
name = "ReVanced Patches"
description = "Patches for ReVanced."
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-patches.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-patches.git"
url = "https://github.com/revanced/revanced-patches"
}
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications["revanced-patches-publication"])
}

View File

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

View File

@@ -1,4 +1,2 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
version = 4.14.0-dev.2
version = 2.169.1-dev.4

View File

@@ -1,18 +0,0 @@
[versions]
revanced-patcher = "19.3.1"
#noinspection GradleDependency
smali = "3.0.5" # 3.0.7 breaks binary compatibility. Tracking https://github.com/google/smali/issues/58.
guava = "33.2.1-jre"
gson = "2.11.0"
binary-compatibility-validator = "0.15.1"
kotlin = "2.0.0"
[libraries]
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
[plugins]
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

Binary file not shown.

View File

@@ -1,8 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

29
gradlew vendored
View File

@@ -83,8 +83,10 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -131,13 +133,10 @@ location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -145,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -153,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -198,15 +197,11 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

20
gradlew.bat vendored
View File

@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail

6848
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
{
"devDependencies": {
"@saithodev/semantic-release-backmerge": "^4.0.1",
"@semantic-release/changelog": "^6.0.3",
"@saithodev/semantic-release-backmerge": "^3.1.0",
"@semantic-release/changelog": "^6.0.2",
"@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.9.2",
"semantic-release": "^24.0.0"
"gradle-semantic-release-plugin": "^1.7.6",
"semantic-release": "^20.1.0"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1 @@
rootProject.name = "revanced-patches"
buildCache {
local {
isEnabled = "CI" !in System.getenv()
}
}

View File

@@ -0,0 +1,44 @@
package app.revanced.extensions
import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.patch.PatchResultError
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.util.MethodUtil
import org.w3c.dom.Node
// TODO: populate this to all patches
/**
* Convert a [MethodFingerprint] to a [PatchResultError].
*
* @return A [PatchResultError] for the [MethodFingerprint].
*/
fun MethodFingerprint.toErrorResult() = PatchResultError("Failed to resolve $name")
/**
* Find the [MutableMethod] from a given [Method] in a [MutableClass].
*
* @param method The [Method] to find.
* @return The [MutableMethod].
*/
fun MutableClass.findMutableMethodOf(method: Method) = this.methods.first {
MethodUtil.methodSignaturesMatch(it, method)
}
/**
* apply a transform to all methods of the class
*
* @param transform the transformation function. original method goes in, transformed method goes out
*/
fun MutableClass.transformMethods(transform: MutableMethod.() -> MutableMethod) {
val transformedMethods = methods.map { it.transform() }
methods.clear()
methods.addAll(transformedMethods)
}
internal fun Node.doRecursively(action: (Node) -> Unit) {
action(this)
for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action)
}

View File

@@ -1,49 +0,0 @@
package app.revanced.generator
import app.revanced.patcher.PatchSet
import app.revanced.patcher.patch.Patch
import com.google.gson.GsonBuilder
import java.io.File
internal class JsonPatchesFileGenerator : PatchesFileGenerator {
override fun generate(patches: PatchSet) = patches.map {
JsonPatch(
it.name!!,
it.description,
it.compatiblePackages,
it.use,
it.requiresIntegrations,
it.options.values.map { option ->
JsonPatch.Option(
option.key,
option.default,
option.values,
option.title,
option.description,
option.required,
)
},
)
}.let {
File("patches.json").writeText(GsonBuilder().serializeNulls().create().toJson(it))
}
@Suppress("unused")
private class JsonPatch(
val name: String? = null,
val description: String? = null,
val compatiblePackages: Set<Patch.CompatiblePackage>? = null,
val use: Boolean = true,
val requiresIntegrations: Boolean = false,
val options: List<Option>,
) {
class Option(
val key: String,
val default: Any?,
val values: Map<String, Any?>?,
val title: String?,
val description: String?,
val required: Boolean,
)
}
}

View File

@@ -1,12 +0,0 @@
package app.revanced.generator
import app.revanced.patcher.PatchBundleLoader
import java.io.File
internal fun main() = PatchBundleLoader.Jar(
File("build/libs/").listFiles { it -> it.name.endsWith(".jar") }!!.first(),
).also { loader ->
if (loader.isEmpty()) throw IllegalStateException("No patches found")
}.let { bundle ->
arrayOf(JsonPatchesFileGenerator()).forEach { generator -> generator.generate(bundle) }
}

View File

@@ -1,7 +0,0 @@
package app.revanced.generator
import app.revanced.patcher.PatchSet
internal interface PatchesFileGenerator {
fun generate(patches: PatchSet)
}

View File

@@ -0,0 +1,70 @@
package app.revanced.meta
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.dependencies
import app.revanced.patcher.extensions.PatchExtensions.description
import app.revanced.patcher.extensions.PatchExtensions.include
import app.revanced.patcher.extensions.PatchExtensions.options
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.extensions.PatchExtensions.version
import app.revanced.patcher.patch.PatchOption
import com.google.gson.GsonBuilder
import java.io.File
internal class JsonGenerator : PatchesFileGenerator {
override fun generate(bundle: PatchBundlePatches) {
val patches = bundle.map {
JsonPatch(
it.patchName,
it.description ?: "This patch has no description.",
it.version ?: "0.0.0",
!it.include,
it.options?.map { option ->
JsonPatch.Option(
option.key,
option.title,
option.description,
option.required,
option.let { listOption ->
if (listOption is PatchOption.ListOption<*>) {
listOption.options.toMutableList().toTypedArray()
} else null
}
)
}?.toTypedArray() ?: emptyArray(),
it.dependencies?.map { dep ->
dep.java.patchName
}?.toTypedArray() ?: emptyArray(),
it.compatiblePackages?.map { pkg ->
JsonPatch.CompatiblePackage(pkg.name, pkg.versions)
}?.toTypedArray() ?: emptyArray()
)
}
val json = File("patches.json")
json.writeText(GsonBuilder().serializeNulls().create().toJson(patches))
}
private class JsonPatch(
val name: String,
val description: String,
val version: String,
val excluded: Boolean,
val options: Array<Option>,
val dependencies: Array<String>,
val compatiblePackages: Array<CompatiblePackage>,
) {
class CompatiblePackage(
val name: String,
val versions: Array<String>,
)
class Option(
val key: String,
val title: String,
val description: String,
val required: Boolean,
val choices: Array<*>?,
)
}
}

View File

@@ -0,0 +1,25 @@
package app.revanced.meta
import app.revanced.patcher.data.Context
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.util.patch.PatchBundle
import java.io.File
typealias PatchBundlePatches = List<Class<out Patch<Context>>>
internal interface PatchesFileGenerator {
fun generate(bundle: PatchBundlePatches)
private companion object {
@JvmStatic
fun main(args: Array<String>) = PatchBundle.Jar(
File("build/libs/").listFiles()!!.first {
it.name.startsWith("revanced-patches-") && it.name.endsWith(".jar")
}.absolutePath
).loadPatches().also {
if (it.isEmpty()) throw IllegalStateException("No patches found")
}.let { bundle ->
arrayOf(JsonGenerator(), ReadmeGenerator()).forEach { it.generate(bundle) }
}
}
}

View File

@@ -0,0 +1,69 @@
package app.revanced.meta
import app.revanced.patcher.data.Context
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.description
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.Patch
import java.io.File
internal class ReadmeGenerator : PatchesFileGenerator {
private companion object {
private const val TABLE_HEADER =
"| \uD83D\uDC8A Patch | \uD83D\uDCDC Description | \uD83C\uDFF9 Target Version |\n" +
"|:--------:|:--------------:|:-----------------:|"
}
override fun generate(bundle: PatchBundlePatches) {
val output = StringBuilder()
mutableMapOf<String, MutableList<Class<out Patch<Context>>>>()
.apply {
for (patch in bundle) {
patch.compatiblePackages?.forEach { pkg ->
if (!contains(pkg.name)) put(pkg.name, mutableListOf())
this[pkg.name]!!.add(patch)
}
}
}
.entries
.sortedByDescending { it.value.size }
.forEach { (`package`, patches) ->
val mostCommonVersion = buildMap {
patches.forEach { patch ->
patch.compatiblePackages?.single { compatiblePackage -> compatiblePackage.name == `package` }?.versions?.let {
it.forEach { version -> merge(version, 1, Integer::sum) }
}
}
}.let { commonMap ->
commonMap.maxByOrNull { it.value }?.value?.let {
// This is not foolproof, because for example v1.0.0-dev.0 will be returned instead of v1.0.0-release.
// Unfortunately this can not be solved easily because versioning can be complex.
commonMap.entries.filter { mostCommon -> mostCommon.value == it }.maxBy { it.key }.key
} ?: "all"
}
output.apply {
appendLine("### [\uD83D\uDCE6 `${`package`}`](https://play.google.com/store/apps/details?id=${`package`})")
appendLine("<details>\n")
appendLine(TABLE_HEADER)
patches.forEach { patch ->
val recommendedPatchVersion = if (
patch.compatiblePackages?.single { it.name == `package` }?.versions?.isNotEmpty() == true
) mostCommonVersion else "all"
appendLine(
"| `${patch.patchName}` " +
"| ${patch.description} " +
"| $recommendedPatchVersion |"
)
}
appendLine("</details>\n")
}
}
StringBuilder(File("README-template.md").readText())
.replace(Regex("\\{\\{\\s?table\\s?}}"), output.toString())
.let(File("README.md")::writeText)
}
}

View File

@@ -0,0 +1,45 @@
package app.revanced.patches.all.activity.exportAll.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotations.Patch
@Patch(false)
@Name("export-all-activities")
@Description("Makes all app activities exportable.")
@Version("0.0.1")
class ExportAllActivitiesPatch : ResourcePatch {
override fun execute(context: ResourceContext): PatchResult {
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file
val activities = document.getElementsByTagName("activity")
for(i in 0..activities.length) {
activities.item(i)?.apply {
val exportedAttribute = attributes.getNamedItem(EXPORTED_FLAG)
if (exportedAttribute != null) {
if (exportedAttribute.nodeValue != "true")
exportedAttribute.nodeValue = "true"
}
// Reason why the attribute is added in the case it does not exist:
// https://github.com/revanced/revanced-patches/pull/1751/files#r1141481604
else document.createAttribute(EXPORTED_FLAG)
.apply { value = "true" }
.let(attributes::setNamedItem)
}
}
}
return PatchResultSuccess()
}
private companion object {
const val EXPORTED_FLAG = "android:exported"
}
}

View File

@@ -1,42 +0,0 @@
package app.revanced.patches.all.activity.exportall
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
@Patch(
name = "Export all activities",
description = "Makes all app activities exportable.",
use = false,
)
@Suppress("unused")
object ExportAllActivitiesPatch : ResourcePatch() {
private const val EXPORTED_FLAG = "android:exported"
override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file
val activities = document.getElementsByTagName("activity")
for (i in 0..activities.length) {
activities.item(i)?.apply {
val exportedAttribute = attributes.getNamedItem(EXPORTED_FLAG)
if (exportedAttribute != null) {
if (exportedAttribute.nodeValue != "true") {
exportedAttribute.nodeValue = "true"
}
}
// Reason why the attribute is added in the case it does not exist:
// https://github.com/revanced/revanced-patches/pull/1751/files#r1141481604
else {
document.createAttribute(EXPORTED_FLAG)
.apply { value = "true" }
.let(attributes::setNamedItem)
}
}
}
}
}
}

View File

@@ -1,46 +1,31 @@
package app.revanced.patches.all.connectivity.wifi.spoof
package app.revanced.patches.all.connectivity.wifi.spoof.patch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.patch.annotations.RequiresIntegrations
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch
import app.revanced.patches.all.misc.transformation.IMethodCall
import app.revanced.patches.all.misc.transformation.Instruction35cInfo
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import app.revanced.util.patch.*
import org.jf.dexlib2.iface.ClassDef
import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.iface.instruction.Instruction
import java.util.*
@Patch(
name = "Spoof Wi-Fi connection",
description = "Spoofs an existing Wi-Fi connection.",
use = false,
requiresIntegrations = true
)
@Suppress("unused")
object SpoofWifiPatch : BaseTransformInstructionsPatch<Instruction35cInfo>() {
private const val INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX = "Lapp/revanced/integrations/all/connectivity/wifi/spoof/SpoofWifiPatch"
private const val INTEGRATIONS_CLASS_DESCRIPTOR = "$INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX;"
@Patch(false)
@Name("spoof-wifi-connection")
@Description("Spoofs an existing Wi-Fi connection.")
@Version("0.0.1")
@RequiresIntegrations
internal class SpoofWifiPatch : AbstractTransformInstructionsPatch<Instruction35cInfo>() {
override fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
) = filterMapInstruction35c<MethodCall>(
INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX,
classDef,
instruction,
instructionIndex
)
override fun transform(mutableMethod: MutableMethod, entry: Instruction35cInfo) {
val (methodType, instruction, instructionIndex) = entry
methodType.replaceInvokeVirtualWithIntegrations(INTEGRATIONS_CLASS_DESCRIPTOR, mutableMethod, instruction, instructionIndex)
private companion object {
const val INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX = "Lapp/revanced/all/connectivity/wifi/spoof/SpoofWifiPatch"
const val INTEGRATIONS_CLASS_DESCRIPTOR = "${INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX};"
}
// Information about method calls we want to replace
private enum class MethodCall(
enum class MethodCall(
override val definedClassName: String,
override val methodName: String,
override val methodParams: Array<String>,
@@ -203,4 +188,21 @@ object SpoofWifiPatch : BaseTransformInstructionsPatch<Instruction35cInfo>() {
"V",
);
}
override fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
) = filterMapInstruction35c<MethodCall>(
INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX,
classDef,
instruction,
instructionIndex
)
override fun transform(mutableMethod: MutableMethod, entry: Instruction35cInfo) {
val (methodType, instruction, instructionIndex) = entry
methodType.replaceInvokeVirtualWithIntegrations(INTEGRATIONS_CLASS_DESCRIPTOR, mutableMethod, instruction, instructionIndex)
}
}

View File

@@ -1,29 +0,0 @@
package app.revanced.patches.all.interaction.gestures
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
@Patch(
name = "Predictive back gesture",
description = "Enables the predictive back gesture introduced on Android 13.",
use = false,
)
@Suppress("unused")
object PredictiveBackGesturePatch : ResourcePatch() {
private const val FLAG = "android:enableOnBackInvokedCallback"
override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file
with(document.getElementsByTagName("application").item(0)) {
if (attributes.getNamedItem(FLAG) != null) return@with
document.createAttribute(FLAG)
.apply { value = "true" }
.let(attributes::setNamedItem)
}
}
}
}

View File

@@ -0,0 +1,37 @@
package app.revanced.patches.all.interaction.gestures.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotations.Patch
@Patch(false)
@Name("predictive-back-gesture")
@Description("Enables the predictive back gesture introduced on Android 13.")
@Version("0.0.1")
class PredictiveBackGesturePatch : ResourcePatch {
override fun execute(context: ResourceContext): PatchResult {
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file
with(document.getElementsByTagName("application").item(0)) {
if (attributes.getNamedItem(FLAG) != null) return@with
document.createAttribute(FLAG)
.apply { value = "true" }
.let(attributes::setNamedItem)
}
}
return PatchResultSuccess()
}
private companion object {
const val FLAG = "android:enableOnBackInvokedCallback"
}
}

View File

@@ -1,56 +0,0 @@
@file:Suppress("unused")
package app.revanced.patches.all.location.hide
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch
import app.revanced.patches.all.misc.transformation.IMethodCall
import app.revanced.patches.all.misc.transformation.fromMethodReference
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.iface.ClassDef
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.Instruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Patch(
name = "Hide mock location",
description = "Prevents the app from knowing the device location is being mocked by a third party app.",
use = false
)
object HideMockLocationPatch : BaseTransformInstructionsPatch<Pair<Instruction, Int>>() {
override fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
): Pair<Instruction, Int>? {
val reference = instruction.getReference<MethodReference>() ?: return null
if (fromMethodReference<MethodCall>(reference) == null) return null
return instruction to instructionIndex
}
override fun transform(mutableMethod: MutableMethod, entry: Pair<Instruction, Int>) {
val (instruction, index) = entry
instruction as FiveRegisterInstruction
// Replace return value with a constant `false` boolean.
mutableMethod.replaceInstruction(
index + 1,
"const/4 v${instruction.registerC}, 0x0"
)
}
}
private enum class MethodCall(
override val definedClassName: String,
override val methodName: String,
override val methodParams: Array<String>,
override val returnType: String
) : IMethodCall {
IsMock("Landroid/location/Location;", "isMock", emptyArray(), "Z"),
IsFromMockProvider("Landroid/location/Location;", "isFromMockProvider", emptyArray(), "Z")
}

View File

@@ -1,120 +0,0 @@
package app.revanced.patches.all.misc.build
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
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.reference.FieldReference
abstract class BaseSpoofBuildInfoPatch : BaseTransformInstructionsPatch<Pair<Int, Pair<String, String>>>() {
// The build information supported32BitAbis, supported64BitAbis, and supportedAbis are not supported for now,
// because initializing an array in transform is a bit more complex.
protected open val board: String? = null
protected open val bootloader: String? = null
protected open val brand: String? = null
protected open val cpuAbi: String? = null
protected open val cpuAbi2: String? = null
protected open val device: String? = null
protected open val display: String? = null
protected open val fingerprint: String? = null
protected open val hardware: String? = null
protected open val host: String? = null
protected open val id: String? = null
protected open val manufacturer: String? = null
protected open val model: String? = null
protected open val odmSku: String? = null
protected open val product: String? = null
protected open val radio: String? = null
protected open val serial: String? = null
protected open val sku: String? = null
protected open val socManufacturer: String? = null
protected open val socModel: String? = null
protected open val tags: String? = null
protected open val time: Long? = null
protected open val type: String? = null
protected open val user: String? = null
// Lazy, so that patch options above are initialized before they are accessed.
private val replacements: Map<String, Pair<String, String>> by lazy {
buildMap {
if (board != null) put("BOARD", "const-string" to "\"$board\"")
if (bootloader != null) put("BOOTLOADER", "const-string" to "\"$bootloader\"")
if (brand != null) put("BRAND", "const-string" to "\"$brand\"")
if (cpuAbi != null) put("CPU_ABI", "const-string" to "\"$cpuAbi\"")
if (cpuAbi2 != null) put("CPU_ABI2", "const-string" to "\"$cpuAbi2\"")
if (device != null) put("DEVICE", "const-string" to "\"$device\"")
if (display != null) put("DISPLAY", "const-string" to "\"$display\"")
if (fingerprint != null) put("FINGERPRINT", "const-string" to "\"$fingerprint\"")
if (hardware != null) put("HARDWARE", "const-string" to "\"$hardware\"")
if (host != null) put("HOST", "const-string" to "\"$host\"")
if (id != null) put("ID", "const-string" to "\"$id\"")
if (manufacturer != null) put("MANUFACTURER", "const-string" to "\"$manufacturer\"")
if (model != null) put("MODEL", "const-string" to "\"$model\"")
if (odmSku != null) put("ODM_SKU", "const-string" to "\"$odmSku\"")
if (product != null) put("PRODUCT", "const-string" to "\"$product\"")
if (radio != null) put("RADIO", "const-string" to "\"$radio\"")
if (serial != null) put("SERIAL", "const-string" to "\"$serial\"")
if (sku != null) put("SKU", "const-string" to "\"$sku\"")
if (socManufacturer != null) put("SOC_MANUFACTURER", "const-string" to "\"$socManufacturer\"")
if (socModel != null) put("SOC_MODEL", "const-string" to "\"$socModel\"")
if (tags != null) put("TAGS", "const-string" to "\"$tags\"")
if (time != null) put("TIME", "const-wide" to "$time")
if (type != null) put("TYPE", "const-string" to "\"$type\"")
if (user != null) put("USER", "const-string" to "\"$user\"")
}
}
override fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
): Pair<Int, Pair<String, String>>? {
val reference = instruction.getReference<FieldReference>() ?: return null
if (reference.definingClass != BUILD_CLASS_DESCRIPTOR) return null
return replacements[reference.name]?.let { instructionIndex to it }
}
override fun transform(mutableMethod: MutableMethod, entry: Pair<Int, Pair<String, String>>) {
val (index, replacement) = entry
val (opcode, operand) = replacement
val register = mutableMethod.getInstruction<OneRegisterInstruction>(index).registerA
mutableMethod.replaceInstruction(index, "$opcode v$register, $operand")
}
private companion object {
private const val BUILD_CLASS_DESCRIPTOR = "Landroid/os/Build;"
}
}

View File

@@ -1,183 +0,0 @@
package app.revanced.patches.all.misc.build
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.longPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
@Patch(
name = "Spoof build info",
description = "Spoof the information about the current build.",
use = false
)
@Suppress("unused")
class SpoofBuildInfoPatch : BaseSpoofBuildInfoPatch() {
override val board by stringPatchOption(
key = "board",
default = null,
title = "Board",
description = "The name of the underlying board, like \"goldfish\"."
)
override val bootloader by stringPatchOption(
key = "bootloader",
default = null,
title = "Bootloader",
description = "The system bootloader version number."
)
override val brand by stringPatchOption(
key = "brand",
default = null,
title = "Brand",
description = "The consumer-visible brand with which the product/hardware will be associated, if any."
)
override val cpuAbi by stringPatchOption(
key = "cpu-abi",
default = null,
title = "CPU ABI",
description = "This field was deprecated in API level 21. Use SUPPORTED_ABIS instead."
)
override val cpuAbi2 by stringPatchOption(
key = "cpu-abi-2",
default = null,
title = "CPU ABI 2",
description = "This field was deprecated in API level 21. Use SUPPORTED_ABIS instead."
)
override val device by stringPatchOption(
key = "device",
default = null,
title = "Device",
description = "The name of the industrial design."
)
override val display by stringPatchOption(
key = "display",
default = null,
title = "Display",
description = "A build ID string meant for displaying to the user."
)
override val fingerprint by stringPatchOption(
key = "fingerprint",
default = null,
title = "Fingerprint",
description = "A string that uniquely identifies this build."
)
override val hardware by stringPatchOption(
key = "hardware",
default = null,
title = "Hardware",
description = "The name of the hardware (from the kernel command line or /proc)."
)
override val host by stringPatchOption(
key = "host",
default = null,
title = "Host",
description = "The host."
)
override val id by stringPatchOption(
key = "id",
default = null,
title = "ID",
description = "Either a changelist number, or a label like \"M4-rc20\"."
)
override val manufacturer by stringPatchOption(
key = "manufacturer",
default = null,
title = "Manufacturer",
description = "The manufacturer of the product/hardware."
)
override val model by stringPatchOption(
key = "model",
default = null,
title = "Model",
description = "The end-user-visible name for the end product."
)
override val odmSku by stringPatchOption(
key = "odm-sku",
default = null,
title = "ODM SKU",
description = "The SKU of the device as set by the original design manufacturer (ODM)."
)
override val product by stringPatchOption(
key = "product",
default = null,
title = "Product",
description = "The name of the overall product."
)
override val radio by stringPatchOption(
key = "radio",
default = null,
title = "Radio",
description = "This field was deprecated in API level 15. " +
"The radio firmware version is frequently not available when this class is initialized, " +
"leading to a blank or \"unknown\" value for this string. Use getRadioVersion() instead."
)
override val serial by stringPatchOption(
key = "serial",
default = null,
title = "Serial",
description = "This field was deprecated in API level 26. Use getSerial() instead."
)
override val sku by stringPatchOption(
key = "sku",
default = null,
title = "SKU",
description = "The SKU of the hardware (from the kernel command line)."
)
override val socManufacturer by stringPatchOption(
key = "soc-manufacturer",
default = null,
title = "SOC Manufacturer",
description = "The manufacturer of the device's primary system-on-chip."
)
override val socModel by stringPatchOption(
key = "soc-model",
default = null,
title = "SOC Model",
description = "The model name of the device's primary system-on-chip."
)
override val tags by stringPatchOption(
key = "tags",
default = null,
title = "Tags",
description = "Comma-separated tags describing the build, like \"unsigned,debug\"."
)
override val time by longPatchOption(
key = "time",
default = null,
title = "Time",
description = "The time at which the build was produced, given in milliseconds since the UNIX epoch."
)
override val type by stringPatchOption(
key = "type",
default = null,
title = "Type",
description = "The type of build, like \"user\" or \"eng\"."
)
override val user by stringPatchOption(
key = "user",
default = null,
title = "User",
description = "The user."
)
}

View File

@@ -1,28 +0,0 @@
package app.revanced.patches.all.misc.debugging
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import org.w3c.dom.Element
@Patch(
name = "Enable Android debugging",
description = "Enables Android debugging capabilities. This can slow down the app.",
use = false,
)
@Suppress("unused")
object EnableAndroidDebuggingPatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file
val applicationNode =
document
.getElementsByTagName("application")
.item(0) as Element
// set application as debuggable
applicationNode.setAttribute("android:debuggable", "true")
}
}
}

View File

@@ -0,0 +1,43 @@
package app.revanced.patches.all.misc.debugging.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.*
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch
import org.w3c.dom.Element
@Patch(false)
@Name("enable-android-debugging")
@Description("Enables Android debugging capabilities.")
@Version("0.0.1")
class EnableAndroidDebuggingPatch : ResourcePatch {
override fun execute(context: ResourceContext): PatchResult {
if (debuggable == true) {
context.xmlEditor["AndroidManifest.xml"].use { dom ->
val applicationNode = dom
.file
.getElementsByTagName("application")
.item(0) as Element
// set application as debuggable
applicationNode.setAttribute("android:debuggable", "true")
}
}
return PatchResultSuccess()
}
companion object : OptionsContainer() {
var debuggable: Boolean? by option(
PatchOption.BooleanOption(
key = "debuggable",
default = true,
title = "App debugging",
description = "Whether to make the app debuggable on Android.",
)
)
}
}

View File

@@ -1,55 +0,0 @@
package app.revanced.patches.all.misc.hex
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.registerNewPatchOption
import app.revanced.patches.shared.misc.hex.BaseHexPatch
import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.patcher.patch.Patch as PatchClass
@Patch(
name = "Hex",
description = "Replaces a hexadecimal patterns of bytes of files in an APK.",
use = false,
)
@Suppress("unused")
class HexPatch : BaseHexPatch() {
// TODO: Instead of stringArrayOption, use a custom option type to work around
// https://github.com/ReVanced/revanced-library/issues/48.
// Replace the custom option type with a stringArrayOption once the issue is resolved.
private val replacementsOption by registerNewPatchOption<PatchClass<*>, List<String>>(
key = "replacements",
title = "Replacements",
description = """
Hexadecimal patterns to search for and replace with another in a target file.
A pattern is a sequence of case insensitive strings, each representing hexadecimal bytes, separated by spaces.
An example pattern is 'aa 01 02 FF'.
Every pattern must be followed by a pipe ('|'), the replacement pattern,
another pipe ('|'), and the path to the file to make the changes in relative to the APK root.
The replacement pattern must have the same length as the original pattern.
Full example of a valid input:
'aa 01 02 FF|00 00 00 00|path/to/file'
""".trimIndentMultiline(),
required = true,
valueType = "StringArray",
)
override val replacements
get() = replacementsOption!!.map { from ->
val (pattern, replacementPattern, targetFilePath) = try {
from.split("|", limit = 3)
} catch (e: Exception) {
throw PatchException(
"Invalid input: $from.\n" +
"Every pattern must be followed by a pipe ('|'), " +
"the replacement pattern, another pipe ('|'), " +
"and the path to the file to make the changes in relative to the APK root. ",
)
}
Replacement(pattern, replacementPattern, targetFilePath)
}
}

View File

@@ -1,61 +0,0 @@
package app.revanced.patches.all.misc.network
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.all.misc.debugging.EnableAndroidDebuggingPatch
import app.revanced.util.Utils.trimIndentMultiline
import org.w3c.dom.Element
import java.io.File
@Patch(
name = "Override certificate pinning",
description = "Overrides certificate pinning, allowing to inspect traffic via a proxy.",
dependencies = [EnableAndroidDebuggingPatch::class],
use = false,
)
@Suppress("unused")
object OverrideCertificatePinningPatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
val resXmlDirectory = context.get("res/xml")
// Add android:networkSecurityConfig="@xml/network_security_config" and the "networkSecurityConfig" attribute if it does not exist.
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file
val applicationNode = document.getElementsByTagName("application").item(0) as Element
if (!applicationNode.hasAttribute("networkSecurityConfig")) {
document.createAttribute("android:networkSecurityConfig")
.apply { value = "@xml/network_security_config" }.let(applicationNode.attributes::setNamedItem)
}
}
// In case the file does not exist create the "network_security_config.xml" file.
File(resXmlDirectory, "network_security_config.xml").apply {
writeText(
"""
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates
src="user"
overridePins="true" />
</trust-anchors>
</base-config>
<debug-overrides>
<trust-anchors>
<certificates src="system" />
<certificates
src="user"
overridePins="true" />
</trust-anchors>
</debug-overrides>
</network-security-config>
""".trimIndentMultiline(),
)
}
}
}

View File

@@ -1,70 +0,0 @@
package app.revanced.patches.all.misc.packagename
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patcher.patch.options.PatchOptionException
import org.w3c.dom.Element
import java.io.Closeable
@Patch(
name = "Change package name",
description = "Appends \".revanced\" to the package name by default. Changing the package name of the app can lead to unexpected issues.",
use = false,
)
@Suppress("unused")
object ChangePackageNamePatch : ResourcePatch(), Closeable {
private val packageNameOption =
stringPatchOption(
key = "packageName",
default = "Default",
values = mapOf("Default" to "Default"),
title = "Package name",
description = "The name of the package to rename the app to.",
required = true,
) {
it == "Default" || it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$"))
}
private lateinit var context: ResourceContext
override fun execute(context: ResourceContext) {
this.context = context
}
/**
* Set the package name to use.
* If this is called multiple times, the first call will set the package name.
*
* @param fallbackPackageName The package name to use if the user has not already specified a package name.
* @return The package name that was set.
* @throws PatchOptionException.ValueValidationException If the package name is invalid.
*/
fun setOrGetFallbackPackageName(fallbackPackageName: String): String {
val packageName = packageNameOption.value!!
return if (packageName == packageNameOption.default) {
fallbackPackageName.also { packageNameOption.value = it }
} else {
packageName
}
}
override fun close() =
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file
val replacementPackageName = packageNameOption.value
val manifest = document.getElementsByTagName("manifest").item(0) as Element
manifest.setAttribute(
"package",
if (replacementPackageName != packageNameOption.default) {
replacementPackageName
} else {
"${manifest.getAttribute("package")}.revanced"
},
)
}
}

View File

@@ -0,0 +1,50 @@
package app.revanced.patches.all.misc.packagename.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.*
import app.revanced.patcher.patch.annotations.Patch
import org.w3c.dom.Element
@Patch(false)
@Name("change-package-name")
@Description("Changes the package name.")
@Version("0.0.1")
class ChangePackageNamePatch : ResourcePatch {
override fun execute(context: ResourceContext): PatchResult {
packageName?.let { packageName ->
val packageNameRegex = Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$")
if (!packageName.matches(packageNameRegex))
return PatchResultError("Invalid package name")
var originalPackageName = ""
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val manifest = editor.file.getElementsByTagName("manifest").item(0) as Element
originalPackageName = manifest.getAttribute("package")
}
if (!originalPackageName.matches(packageNameRegex))
return PatchResultError("Failed to get the original package name")
context["AndroidManifest.xml"].apply {
readText().replace(originalPackageName, packageName).let(::writeText)
}
} ?: return PatchResultError("No package name provided")
return PatchResultSuccess()
}
companion object : OptionsContainer() {
var packageName: String? by option(
PatchOption.StringOption(
key = "packageName",
default = "",
title = "Package name",
description = "The name of the package to rename of the app.",
)
)
}
}

View File

@@ -1,383 +0,0 @@
package app.revanced.patches.all.misc.resources
import app.revanced.patcher.PatchClass
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.DomFileEditor
import app.revanced.patches.all.misc.resources.AddResourcesPatch.resources
import app.revanced.util.*
import app.revanced.util.resource.ArrayResource
import app.revanced.util.resource.BaseResource
import app.revanced.util.resource.StringResource
import org.w3c.dom.Node
import java.io.Closeable
/**
* An identifier of an app. For example, `youtube`.
*/
private typealias AppId = String
/**
* An identifier of a patch. For example, `ad.general.HideAdsPatch`.
*/
private typealias PatchId = String
/**
* A set of resources of a patch.
*/
private typealias PatchResources = MutableSet<BaseResource>
/**
* A map of resources belonging to a patch.
*/
private typealias AppResources = MutableMap<PatchId, PatchResources>
/**
* A map of resources belonging to an app.
*/
private typealias Resources = MutableMap<AppId, AppResources>
/**
* The value of a resource.
* For example, `values` or `values-de`.
*/
private typealias Value = String
@Patch(description = "Add resources such as strings or arrays to the app.")
object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseResource>> by mutableMapOf(), Closeable {
private lateinit var context: ResourceContext
/**
* A map of all resources associated by their value staged by [execute].
*/
private lateinit var resources: Map<Value, Resources>
/**
* Map of Crowdin locales to Android resource locale names.
*
* Fixme: Instead this patch should detect what locale regions are present in both patches and the target app,
* and automatically merge into the appropriate existing target file.
* So if a target app has only 'es', then the Crowdin file of 'es-rES' should merge into that.
* But if a target app has specific regions (such as 'pt-rBR'),
* then the Crowdin region specific file should merged into that.
*/
private val locales = mapOf(
"af-rZA" to "af",
"am-rET" to "am",
"ar-rSA" to "ar",
"as-rIN" to "as",
"az-rAZ" to "az",
"be-rBY" to "be",
"bg-rBG" to "bg",
"bn-rBD" to "bn",
"bs-rBA" to "bs",
"ca-rES" to "ca",
"cs-rCZ" to "cs",
"da-rDK" to "da",
"de-rDE" to "de",
"el-rGR" to "el",
"es-rES" to "es",
"et-rEE" to "et",
"eu-rES" to "eu",
"fa-rIR" to "fa",
"fi-rFI" to "fi",
"fil-rPH" to "tl",
"fr-rFR" to "fr",
"gl-rES" to "gl",
"gu-rIN" to "gu",
"hi-rIN" to "hi",
"hr-rHR" to "hr",
"hu-rHU" to "hu",
"hy-rAM" to "hy",
"in-rID" to "in",
"is-rIS" to "is",
"it-rIT" to "it",
"iw-rIL" to "iw",
"ja-rJP" to "ja",
"ka-rGE" to "ka",
"kk-rKZ" to "kk",
"km-rKH" to "km",
"kn-rIN" to "kn",
"ko-rKR" to "ko",
"ky-rKG" to "ky",
"lo-rLA" to "lo",
"lt-rLT" to "lt",
"lv-rLV" to "lv",
"mk-rMK" to "mk",
"ml-rIN" to "ml",
"mn-rMN" to "mn",
"mr-rIN" to "mr",
"ms-rMY" to "ms",
"my-rMM" to "my",
"nb-rNO" to "nb",
"ne-rIN" to "ne",
"nl-rNL" to "nl",
"or-rIN" to "or",
"pa-rIN" to "pa",
"pl-rPL" to "pl",
"pt-rBR" to "pt-rBR",
"pt-rPT" to "pt-rPT",
"ro-rRO" to "ro",
"ru-rRU" to "ru",
"si-rLK" to "si",
"sk-rSK" to "sk",
"sl-rSI" to "sl",
"sq-rAL" to "sq",
"sr-rCS" to "b+sr+Latn",
"sr-rSP" to "sr",
"sv-rSE" to "sv",
"sw-rKE" to "sw",
"ta-rIN" to "ta",
"te-rIN" to "te",
"th-rTH" to "th",
"tl-rPH" to "tl",
"tr-rTR" to "tr",
"uk-rUA" to "uk",
"ur-rIN" to "ur",
"uz-rUZ" to "uz",
"vi-rVN" to "vi",
"zh-rCN" to "zh-rCN",
"zh-rTW" to "zh-rTW",
"zu-rZA" to "zu",
)
/*
The strategy of this patch is to stage resources present in `/resources/addresources`.
These resources are organized by their respective value and patch.
On AddResourcesPatch#execute, all resources are staged in a temporary map.
After that, other patches that depend on AddResourcesPatch can call
AddResourcesPatch#invoke(PatchClass) to stage resources belonging to that patch
from the temporary map to AddResourcesPatch.
After all patches that depend on AddResourcesPatch have been executed,
AddResourcesPatch#close is finally called to add all staged resources to the app.
*/
override fun execute(context: ResourceContext) {
this.context = context
resources =
buildMap {
/**
* Puts resources under `/resources/addresources/<value>/<resourceKind>.xml` into the map.
*
* @param sourceValue The source value of the resource. For example, `values` or `values-de-rDE`.
* @param destValue The destination value of the resource. For example, 'values' or 'values-de'.
* @param resourceKind The kind of the resource. For example, `strings` or `arrays`.
* @param transform A function that transforms the [Node]s from the XML files to a [BaseResource].
*/
fun addResources(
sourceValue: Value,
destValue: Value = sourceValue,
resourceKind: String,
transform: (Node) -> BaseResource,
) {
inputStreamFromBundledResource(
"addresources",
"$sourceValue/$resourceKind.xml",
)?.let { stream ->
// Add the resources associated with the given value to the map,
// instead of overwriting it.
// This covers the example case such as adding strings and arrays of the same value.
getOrPut(destValue, ::mutableMapOf).apply {
context.xmlEditor[stream].use { editor ->
val document = editor.file
document.getElementsByTagName("app").asSequence().forEach { app ->
val appId = app.attributes.getNamedItem("id").textContent
getOrPut(appId, ::mutableMapOf).apply {
app.forEachChildElement { patch ->
val patchId = patch.attributes.getNamedItem("id").textContent
getOrPut(patchId, ::mutableSetOf).apply {
patch.forEachChildElement { resourceNode ->
val resource = transform(resourceNode)
add(resource)
}
}
}
}
}
}
}
}
}
// Stage all resources to a temporary map.
// Staged resources consumed by AddResourcesPatch#invoke(PatchClass)
// are later used in AddResourcesPatch#close.
try {
val addStringResources = { source: Value, dest: Value ->
addResources(source, dest, "strings", StringResource::fromNode)
}
locales.forEach { (source, dest) -> addStringResources("values-$source", "values-$dest") }
addStringResources("values", "values")
addResources("values", "values", "arrays", ArrayResource::fromNode)
} catch (e: Exception) {
throw PatchException("Failed to read resources", e)
}
}
}
/**
* Adds a [BaseResource] to the map using [MutableMap.getOrPut].
*
* @param value The value of the resource. For example, `values` or `values-de`.
* @param resource The resource to add.
*
* @return True if the resource was added, false if it already existed.
*/
operator fun invoke(
value: Value,
resource: BaseResource,
) = getOrPut(value, ::mutableSetOf).add(resource)
/**
* Adds a list of [BaseResource]s to the map using [MutableMap.getOrPut].
*
* @param value The value of the resource. For example, `values` or `values-de`.
* @param resources The resources to add.
*
* @return True if the resources were added, false if they already existed.
*/
operator fun invoke(
value: Value,
resources: Iterable<BaseResource>,
) = getOrPut(value, ::mutableSetOf).addAll(resources)
/**
* Adds a [StringResource].
*
* @param name The name of the string resource.
* @param value The value of the string resource.
* @param formatted Whether the string resource is formatted. Defaults to `true`.
* @param resourceValue The value of the resource. For example, `values` or `values-de`.
*
* @return True if the resource was added, false if it already existed.
*/
operator fun invoke(
name: String,
value: String,
formatted: Boolean = true,
resourceValue: Value = "values",
) = invoke(resourceValue, StringResource(name, value, formatted))
/**
* Adds an [ArrayResource].
*
* @param name The name of the array resource.
* @param items The items of the array resource.
*
* @return True if the resource was added, false if it already existed.
*/
operator fun invoke(
name: String,
items: List<String>,
) = invoke("values", ArrayResource(name, items))
/**
* Puts all resources of any [Value] staged in [resources] for the given [PatchClass] to [AddResourcesPatch].
*
* @param patch The class of the patch to add resources for.
* @param parseIds A function that parses the [AppId] and [PatchId] from the given [PatchClass].
* This is used to access the resources in [resources] to stage them in [AddResourcesPatch].
* The default implementation assumes that the [PatchClass] qualified name has the following format:
* `<any>.<any>.<any>.<app id>.<patch id>`.
*
* @return True if any resources were added, false if none were added.
*
* @see AddResourcesPatch.close
*/
operator fun invoke(
patch: PatchClass,
parseIds: PatchClass.() -> Pair<AppId, PatchId> = {
val qualifiedName = qualifiedName ?: throw PatchException("Patch qualified name is null")
// This requires qualifiedName to have the following format:
// `<any>.<any>.<any>.<app id>.<patch id>`
with(qualifiedName.split(".")) {
if (size < 5) throw PatchException("Patch qualified name has invalid format")
val appId = this[3]
val patchId = subList(4, size).joinToString(".")
appId to patchId
}
},
): Boolean {
val (appId, patchId) = patch.parseIds()
var result = false
// Stage resources for the given patch to AddResourcesPatch associated with their value.
resources.forEach { (value, resources) ->
resources[appId]?.get(patchId)?.let { patchResources ->
if (invoke(value, patchResources)) result = true
}
}
return result
}
/**
* Adds all resources staged in [AddResourcesPatch] to the app.
* This is called after all patches that depend on [AddResourcesPatch] have been executed.
*/
override fun close() {
operator fun MutableMap<String, Pair<DomFileEditor, Node>>.invoke(
value: Value,
resource: BaseResource,
) {
// TODO: Fix open-closed principle violation by modifying BaseResource#serialize so that it accepts
// a Value and the map of documents. It will then get or put the document suitable for its resource type
// to serialize itself to it.
val resourceFileName =
when (resource) {
is StringResource -> "strings"
is ArrayResource -> "arrays"
else -> throw NotImplementedError("Unsupported resource type")
}
getOrPut(resourceFileName) {
val targetFile =
context.get("res/$value/$resourceFileName.xml").also {
it.parentFile?.mkdirs()
if(it.createNewFile()) {
it.writeText("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>")
}
}
context.xmlEditor[targetFile.path].let { editor ->
val document = editor.file
// Save the target node here as well
// in order to avoid having to call document.getNode("resources")
// but also save the document so that it can be closed later.
editor to document.getNode("resources")
}
}.let { (_, targetNode) ->
targetNode.addResource(resource) { invoke(value, it) }
}
}
forEach { (value, resources) ->
// A map of document associated by their kind (e.g. strings, arrays).
// Each document is accompanied by the target node to which resources are added.
// A map is used because Map#getOrPut allows opening a new document for the duration of a resource value.
// This is done to prevent having to open the files for every resource that is added.
// Instead, it is cached once and reused for resources of the same value.
// This map is later accessed to close all documents for the current resource value.
val documents = mutableMapOf<String, Pair<DomFileEditor, Node>>()
resources.forEach { resource -> documents(value, resource) }
documents.values.forEach { (document, _) -> document.close() }
}
}
}

View File

@@ -1,58 +0,0 @@
package app.revanced.patches.all.misc.transformation
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.util.findMutableMethodOf
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
@Suppress("MemberVisibilityCanBePrivate")
abstract class BaseTransformInstructionsPatch<T> : BytecodePatch(emptySet()) {
abstract fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int,
): T?
abstract fun transform(mutableMethod: MutableMethod, entry: T)
// Returns the patch indices as a Sequence, which will execute lazily.
fun findPatchIndices(classDef: ClassDef, method: Method): Sequence<T>? {
return method.implementation?.instructions?.asSequence()?.withIndex()?.mapNotNull { (index, instruction) ->
filterMap(classDef, method, instruction, index)
}
}
override fun execute(context: BytecodeContext) {
// Find all methods to patch
buildMap {
context.classes.forEach { classDef ->
val methods = buildList {
classDef.methods.forEach { method ->
// Since the Sequence executes lazily,
// using any() results in only calling
// filterMap until the first index has been found.
if (findPatchIndices(classDef, method)?.any() == true) add(method)
}
}
if (methods.isNotEmpty()) {
put(classDef, methods)
}
}
}.forEach { (classDef, methods) ->
// And finally transform the methods...
val mutableClass = context.proxy(classDef).mutableClass
methods.map(mutableClass::findMutableMethodOf).forEach methods@{ mutableMethod ->
val patchIndices = findPatchIndices(mutableClass, mutableMethod)?.toCollection(ArrayDeque())
?: return@methods
while (!patchIndices.isEmpty()) transform(mutableMethod, patchIndices.removeLast())
}
}
}
}

View File

@@ -1,39 +0,0 @@
package app.revanced.patches.all.misc.versioncode
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.intPatchOption
import app.revanced.util.getNode
import org.w3c.dom.Element
@Patch(
name = "Change version code",
description = "Changes the version code of the app. By default the highest version code is set. " +
"This allows older versions of an app to be installed " +
"if their version code is set to the same or a higher value and can stop app stores to update the app.",
use = false,
)
@Suppress("unused")
object ChangeVersionCodePatch : ResourcePatch() {
private val versionCode by intPatchOption(
key = "versionCode",
default = Int.MAX_VALUE,
values = mapOf(
"Lowest" to 1,
"Highest" to Int.MAX_VALUE,
),
title = "Version code",
description = "The version code to use",
required = true,
) {
it!! >= 1
}
override fun execute(context: ResourceContext) {
context.document["AndroidManifest.xml"].use { document ->
val manifestElement = document.getNode("manifest") as Element
manifestElement.setAttribute("android:versionCode", "$versionCode")
}
}
}

View File

@@ -1,62 +0,0 @@
package app.revanced.patches.all.screencapture.removerestriction
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch
import app.revanced.patches.all.misc.transformation.IMethodCall
import app.revanced.patches.all.misc.transformation.Instruction35cInfo
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
@Patch(
name = "Remove screen capture restriction",
description = "Removes the restriction of capturing audio from apps that normally wouldn't allow it.",
dependencies = [RemoveCaptureRestrictionResourcePatch::class],
use = false,
requiresIntegrations = true
)
@Suppress("unused")
object RemoveCaptureRestrictionPatch : BaseTransformInstructionsPatch<Instruction35cInfo>() {
private const val INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX =
"Lapp/revanced/integrations/all/screencapture/removerestriction/RemoveScreencaptureRestrictionPatch"
private const val INTEGRATIONS_CLASS_DESCRIPTOR = "$INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX;"
// Information about method calls we want to replace
enum class MethodCall(
override val definedClassName: String,
override val methodName: String,
override val methodParams: Array<String>,
override val returnType: String
): IMethodCall {
SetAllowedCapturePolicySingle(
"Landroid/media/AudioAttributes\$Builder;",
"setAllowedCapturePolicy",
arrayOf("I"),
"Landroid/media/AudioAttributes\$Builder;",
),
SetAllowedCapturePolicyGlobal(
"Landroid/media/AudioManager;",
"setAllowedCapturePolicy",
arrayOf("I"),
"V",
);
}
override fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
) = filterMapInstruction35c<MethodCall>(
INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX,
classDef,
instruction,
instructionIndex
)
override fun transform(mutableMethod: MutableMethod, entry: Instruction35cInfo) {
val (methodType, instruction, instructionIndex) = entry
methodType.replaceInvokeVirtualWithIntegrations(INTEGRATIONS_CLASS_DESCRIPTOR, mutableMethod, instruction, instructionIndex)
}
}

View File

@@ -1,24 +0,0 @@
package app.revanced.patches.all.screencapture.removerestriction
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import org.w3c.dom.Element
@Patch(description = "Sets allowAudioPlaybackCapture in manifest to true.")
internal object RemoveCaptureRestrictionResourcePatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file
// get the application node
val applicationNode =
document
.getElementsByTagName("application")
.item(0) as Element
// set allowAudioPlaybackCapture attribute to true
applicationNode.setAttribute("android:allowAudioPlaybackCapture", "true")
}
}
}

View File

@@ -1,106 +0,0 @@
package app.revanced.patches.all.screenshot.removerestriction
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch
import app.revanced.patches.all.misc.transformation.IMethodCall
import app.revanced.patches.all.misc.transformation.Instruction35cInfo
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction22c
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
@Patch(
name = "Remove screenshot restriction",
description = "Removes the restriction of taking screenshots in apps that normally wouldn't allow it.",
use = false,
requiresIntegrations = true,
)
@Suppress("unused")
object RemoveScreenshotRestrictionPatch : BaseTransformInstructionsPatch<Instruction35cInfo>() {
private const val INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX =
"Lapp/revanced/integrations/all/screenshot/removerestriction/RemoveScreenshotRestrictionPatch"
private const val INTEGRATIONS_CLASS_DESCRIPTOR = "$INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX;"
override fun execute(context: BytecodeContext) {
super.execute(context)
ModifyLayoutParamsFlags().execute(context)
}
override fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
) = filterMapInstruction35c<MethodCall>(
INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX,
classDef,
instruction,
instructionIndex
)
override fun transform(mutableMethod: MutableMethod, entry: Instruction35cInfo) {
val (methodType, instruction, instructionIndex) = entry
methodType.replaceInvokeVirtualWithIntegrations(INTEGRATIONS_CLASS_DESCRIPTOR, mutableMethod, instruction, instructionIndex)
}
// Information about method calls we want to replace
enum class MethodCall(
override val definedClassName: String,
override val methodName: String,
override val methodParams: Array<String>,
override val returnType: String
): IMethodCall {
AddFlags(
"Landroid/view/Window;",
"addFlags",
arrayOf("I"),
"V",
),
SetFlags(
"Landroid/view/Window;",
"setFlags",
arrayOf("I", "I"),
"V",
);
}
}
private class ModifyLayoutParamsFlags : BaseTransformInstructionsPatch<Pair<Instruction22c, Int>>() {
override fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
): Pair<Instruction22c, Int>? {
if (instruction.opcode != Opcode.IPUT) {
return null
}
val instruction22c = instruction as Instruction22c
val fieldReference = instruction22c.reference as FieldReference
if (fieldReference.definingClass != "Landroid/view/WindowManager\$LayoutParams;"
|| fieldReference.name != "flags"
|| fieldReference.type != "I") {
return null
}
return Pair(instruction22c, instructionIndex)
}
override fun transform(mutableMethod: MutableMethod, entry: Pair<Instruction22c, Int>) {
val (instruction, index) = entry
val register = instruction.registerA
mutableMethod.addInstructions(
index,
"and-int/lit16 v$register, v$register, -0x2001"
)
}
}

View File

@@ -0,0 +1,59 @@
package app.revanced.patches.all.screenshot.removerestriction.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.patch.annotations.RequiresIntegrations
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.util.patch.*
import org.jf.dexlib2.iface.ClassDef
import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.iface.instruction.Instruction
import java.util.*
@Patch(false)
@Name("remove-screenshot-restriction")
@Description("Removes the restriction of taking screenshots in apps that normally wouldn't allow it.")
@Version("0.0.1")
@RequiresIntegrations
internal class RemoveScreenshotRestrictionPatch : AbstractTransformInstructionsPatch<Instruction35cInfo>() {
private companion object {
const val INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX =
"Lapp/revanced/all/screenshot/removerestriction/RemoveScreenshotRestrictionPatch"
const val INTEGRATIONS_CLASS_DESCRIPTOR = "$INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX;"
}
// Information about method calls we want to replace
enum class MethodCall(
override val definedClassName: String,
override val methodName: String,
override val methodParams: Array<String>,
override val returnType: String
): IMethodCall {
SetFlags(
"Landroid/view/Window;",
"setFlags",
arrayOf("I", "I"),
"V",
);
}
override fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
) = filterMapInstruction35c<MethodCall>(
INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX,
classDef,
instruction,
instructionIndex
)
override fun transform(mutableMethod: MutableMethod, entry: Instruction35cInfo) {
val (methodType, instruction, instructionIndex) = entry
methodType.replaceInvokeVirtualWithIntegrations(INTEGRATIONS_CLASS_DESCRIPTOR, mutableMethod, instruction, instructionIndex)
}
}

View File

@@ -1,32 +0,0 @@
package app.revanced.patches.all.shortcut.sharetargets
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.util.asSequence
import app.revanced.util.getNode
import org.w3c.dom.Element
import java.io.FileNotFoundException
import java.util.logging.Logger
@Patch(
name = "Remove share targets",
description = "Removes share targets like directly sharing to a frequent contact.",
use = false,
)
@Suppress("unused")
object RemoveShareTargetsPatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
try {
context.document["res/xml/shortcuts.xml"]
} catch (_: FileNotFoundException) {
return Logger.getLogger(this::class.java.name).warning("The app has no shortcuts")
}.use { document ->
val rootNode = document.getNode("shortcuts") as? Element ?: return@use
document.getElementsByTagName("share-target").asSequence().forEach {
rootNode.removeChild(it)
}
}
}
}

View File

@@ -1,113 +0,0 @@
package app.revanced.patches.all.telephony.sim.spoof
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil
import java.util.*
@Patch(
name = "Spoof SIM country",
description = "Spoofs country information returned by the SIM card provider.",
use = false,
)
@Suppress("unused")
object SpoofSimCountryPatch : BaseTransformInstructionsPatch<Pair<Int, String>>() {
private val countries = Locale.getISOCountries().associateBy { Locale("", it).displayCountry }
private val networkCountryIso by isoCountryPatchOption(
"networkCountryIso",
"Network ISO Country Code",
)
private val simCountryIso by isoCountryPatchOption(
"simCountryIso",
"Sim ISO Country Code",
)
private fun isoCountryPatchOption(
key: String,
title: String,
) = stringPatchOption(
key,
null,
countries,
title,
"ISO-3166-1 alpha-2 country code equivalent for the SIM provider's country code.",
false,
validator = { it: String? -> it == null || it.uppercase() in countries.values }
)
override fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
): Pair<Int, String>? {
if (instruction !is ReferenceInstruction) return null
val reference = instruction.reference as? MethodReference ?: return null
val match = MethodCall.entries.firstOrNull { search ->
MethodUtil.methodSignaturesMatch(reference, search.reference)
} ?: return null
val iso = when (match) {
MethodCall.NetworkCountryIso -> networkCountryIso
MethodCall.SimCountryIso -> simCountryIso
}?.lowercase()
return iso?.let { instructionIndex to it }
}
override fun transform(
mutableMethod: MutableMethod,
entry: Pair<Int, String>
) = transformMethodCall(entry, mutableMethod)
private fun transformMethodCall(
entry: Pair<Int, String>,
mutableMethod: MutableMethod
) {
val (instructionIndex, methodCallValue) = entry
val register = mutableMethod.getInstruction<OneRegisterInstruction>(instructionIndex + 1).registerA
mutableMethod.replaceInstruction(
instructionIndex + 1,
"const-string v$register, \"$methodCallValue\""
)
}
private enum class MethodCall(
val reference: MethodReference
) {
NetworkCountryIso(
ImmutableMethodReference(
"Landroid/telephony/TelephonyManager;",
"getNetworkCountryIso",
emptyList(),
"Ljava/lang/String;"
)
),
SimCountryIso(
ImmutableMethodReference(
"Landroid/telephony/TelephonyManager;",
"getSimCountryIso",
emptyList(),
"Ljava/lang/String;"
)
)
}
}

View File

@@ -1,11 +0,0 @@
package app.revanced.patches.amazon.deeplinking
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object DeepLinkingFingerprint : MethodFingerprint(
"Z",
parameters = listOf("L"),
accessFlags = AccessFlags.PRIVATE.value,
strings = listOf("https://www.", "android.intent.action.VIEW")
)

View File

@@ -1,28 +0,0 @@
package app.revanced.patches.amazon.deeplinking
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.util.exception
@Patch(
name = "Always allow deep-linking",
description = "Open Amazon links, even if the app is not set to handle Amazon links.",
compatiblePackages = [CompatiblePackage("com.amazon.mShop.android.shopping")]
)
@Suppress("unused")
object DeepLinkingPatch : BytecodePatch(
setOf(DeepLinkingFingerprint)
) {
override fun execute(context: BytecodeContext) {
DeepLinkingFingerprint.result?.mutableMethod?.addInstructions(
0,
"""
const/4 v0, 0x1
return v0
"""
) ?: throw DeepLinkingFingerprint.exception
}
}

View File

@@ -1,37 +0,0 @@
package app.revanced.patches.backdrops.misc.pro
import app.revanced.util.exception
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.backdrops.misc.pro.fingerprints.ProUnlockFingerprint
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Patch(
name = "Pro unlock",
compatiblePackages = [CompatiblePackage("com.backdrops.wallpapers", ["4.52"])]
)
@Suppress("unused")
object ProUnlockPatch : BytecodePatch(
setOf(ProUnlockFingerprint)
) {
override fun execute(context: BytecodeContext) {
ProUnlockFingerprint.result?.let { result ->
val registerIndex = result.scanResult.patternScanResult!!.endIndex - 1
result.mutableMethod.apply {
val register = getInstruction<OneRegisterInstruction>(registerIndex).registerA
addInstruction(
result.scanResult.patternScanResult!!.endIndex,
"""
const/4 v$register, 0x1
"""
)
}
} ?: throw ProUnlockFingerprint.exception
}
}

View File

@@ -0,0 +1,7 @@
package app.revanced.patches.backdrops.misc.pro.annotations
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@Compatibility([Package("com.backdrops.wallpapers", arrayOf("4.52"))])
internal annotation class ProUnlockCompatibility

View File

@@ -1,9 +1,9 @@
package app.revanced.patches.backdrops.misc.pro.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.Opcode
internal object ProUnlockFingerprint : MethodFingerprint(
object ProUnlockFingerprint : MethodFingerprint(
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
@@ -11,8 +11,5 @@ internal object ProUnlockFingerprint : MethodFingerprint(
Opcode.MOVE_RESULT,
Opcode.IF_EQZ
),
customFingerprint = { methodDef, _ ->
methodDef.definingClass == "Lcom/backdrops/wallpapers/data/local/DatabaseHandlerIAB;"
&& methodDef.name == "lambda\$existPurchase\$0"
}
customFingerprint = { it.definingClass == "Lcom/backdrops/wallpapers/data/local/DatabaseHandlerIAB;" && it.name == "lambda\$existPurchase\$0" }
)

View File

@@ -0,0 +1,42 @@
package app.revanced.patches.backdrops.misc.pro.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.instruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultError
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patches.backdrops.misc.pro.annotations.ProUnlockCompatibility
import app.revanced.patches.backdrops.misc.pro.fingerprints.ProUnlockFingerprint
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
@Patch
@Name("pro-unlock")
@Description("Unlocks pro-only functions.")
@ProUnlockCompatibility
@Version("0.0.1")
class ProUnlockPatch : BytecodePatch(
listOf(ProUnlockFingerprint)
) {
override fun execute(context: BytecodeContext): PatchResult {
val result = ProUnlockFingerprint.result ?: return PatchResultError("${ProUnlockFingerprint.name} not found")
val moveRegisterInstruction = result.mutableMethod.instruction(result.scanResult.patternScanResult!!.endIndex - 1)
val register = (moveRegisterInstruction as OneRegisterInstruction).registerA
result.mutableMethod.addInstructions(
result.scanResult.patternScanResult!!.endIndex,
"""
const/4 v$register, 0x1
"""
)
return PatchResultSuccess()
}
}

View File

@@ -1,23 +0,0 @@
package app.revanced.patches.bandcamp.limitations
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.bandcamp.limitations.fingerprints.HandlePlaybackLimitsPatch
import app.revanced.util.exception
@Patch(
name = "Remove play limits",
description = "Disables purchase nagging and playback limits of not purchased tracks.",
compatiblePackages = [CompatiblePackage("com.bandcamp.android")],
)
@Suppress("unused")
object RemovePlayLimitsPatch : BytecodePatch(
setOf(HandlePlaybackLimitsPatch),
) {
override fun execute(context: BytecodeContext) =
HandlePlaybackLimitsPatch.result?.mutableMethod?.addInstructions(0, "return-void")
?: throw HandlePlaybackLimitsPatch.exception
}

View File

@@ -1,7 +0,0 @@
package app.revanced.patches.bandcamp.limitations.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object HandlePlaybackLimitsPatch : MethodFingerprint(
strings = listOf("play limits processing track", "found play_count"),
)

View File

@@ -1,31 +0,0 @@
package app.revanced.patches.candylinkvpn
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.candylinkvpn.fingerprints.IsPremiumPurchasedFingerprint
import app.revanced.util.exception
@Patch(
compatiblePackages = [CompatiblePackage("com.candylink.openvpn")],
)
@Deprecated(
"This patch does not work anymore and will be removed in the future, " +
"because the servers now check the purchase status.",
)
@Suppress("unused")
object UnlockProPatch : BytecodePatch(
setOf(IsPremiumPurchasedFingerprint),
) {
override fun execute(context: BytecodeContext) {
IsPremiumPurchasedFingerprint.result?.mutableMethod?.addInstructions(
0,
"""
const/4 v0, 0x1
return v0
""",
) ?: throw IsPremiumPurchasedFingerprint.exception
}
}

View File

@@ -1,10 +0,0 @@
package app.revanced.patches.candylinkvpn.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object IsPremiumPurchasedFingerprint : MethodFingerprint(
customFingerprint = { methodDef, _ ->
methodDef.definingClass.endsWith("PreferenceProvider;") &&
methodDef.name == "isPremiumPurchased"
}
)

View File

@@ -1,24 +0,0 @@
package app.revanced.patches.cieid.restrictions.root
import app.revanced.util.exception
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.cieid.restrictions.root.fingerprints.CheckRootFingerprint
@Patch(
name = "Bypass root checks",
description = "Removes the restriction to use the app with root permissions or on a custom ROM.",
compatiblePackages = [CompatiblePackage("it.ipzs.cieid")]
)
@Suppress("unused")
object BypassRootChecksPatch : BytecodePatch(
setOf(CheckRootFingerprint)
) {
override fun execute(context: BytecodeContext) {
CheckRootFingerprint.result?.mutableMethod?.addInstruction(1, "return-void")
?: throw CheckRootFingerprint.exception
}
}

View File

@@ -1,9 +0,0 @@
package app.revanced.patches.cieid.restrictions.root.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object CheckRootFingerprint : MethodFingerprint(
customFingerprint = { methodDef, _ ->
methodDef.definingClass == "Lit/ipzs/cieid/BaseActivity;" && methodDef.name == "onResume"
}
)

View File

@@ -0,0 +1,7 @@
package app.revanced.patches.citra.misc.premium.annotations
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@Compatibility([Package("org.citra.citra_emu"), Package("org.citra.citra_emu.canary")])
internal annotation class PremiumUnlockCompatbility

View File

@@ -0,0 +1,7 @@
package app.revanced.patches.citra.misc.premium.fingerprints
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
object PremiumUnlockFingerprint : MethodFingerprint(
customFingerprint = { it.definingClass == "Lorg/citra/citra_emu/ui/main/MainActivity;" && it.name == "isPremiumActive" }
)

View File

@@ -0,0 +1,37 @@
package app.revanced.patches.citra.misc.premium.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultError
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patches.citra.misc.premium.annotations.PremiumUnlockCompatbility
import app.revanced.patches.citra.misc.premium.fingerprints.PremiumUnlockFingerprint
@Patch
@Name("premium-unlock")
@Description("Unlocks premium functions.")
@PremiumUnlockCompatbility
@Version("0.0.1")
class PremiumUnlockPatch : BytecodePatch(
listOf(PremiumUnlockFingerprint)
) {
override fun execute(context: BytecodeContext): PatchResult {
val result = PremiumUnlockFingerprint.result ?: return PatchResultError("${PremiumUnlockFingerprint.name} not found")
result.mutableMethod.addInstructions(
0,
"""
const v0, 0x1
return v0
"""
)
return PatchResultSuccess()
}
}

View File

@@ -1,41 +0,0 @@
package app.revanced.patches.duolingo.ad
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.duolingo.ad.fingerprints.InitializeMonetizationDebugSettingsFingerprint
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
@Patch(
name = "Disable ads",
compatiblePackages = [CompatiblePackage("com.duolingo")]
)
@Suppress("unused")
object DisableAdsPatch : BytecodePatch(
setOf(InitializeMonetizationDebugSettingsFingerprint)
) {
override fun execute(context: BytecodeContext) {
// Couple approaches to remove ads exist:
//
// MonetizationDebugSettings has a boolean value for "disableAds".
// OnboardingState has a getter to check if the user has any "adFreeSessions".
// SharedPreferences has a debug boolean value with key "disable_ads", which maps to "DebugCategory.DISABLE_ADS".
//
// MonetizationDebugSettings seems to be the most general setting to work fine.
InitializeMonetizationDebugSettingsFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
val insertIndex = it.scanResult.patternScanResult!!.startIndex
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
addInstructions(
insertIndex,
"const/4 v$register, 0x1"
)
}
}
}
}

View File

@@ -1,21 +0,0 @@
package app.revanced.patches.duolingo.ad.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal object InitializeMonetizationDebugSettingsFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
parameters = listOf(
"Z", // disableAds
"Z", // useDebugBilling
"Z", // showManageSubscriptions
"Z", // alwaysShowSuperAds
"Lcom/duolingo/debug/FamilyQuestOverride;",
),
opcodes = listOf(
Opcode.IPUT_BOOLEAN
)
)

View File

@@ -1,35 +0,0 @@
package app.revanced.patches.duolingo.debug
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.duolingo.debug.fingerprints.InitializeBuildConfigProviderFingerprint
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
@Patch(
name = "Enable debug menu",
compatiblePackages = [CompatiblePackage("com.duolingo", ["5.158.4"])],
use = false
)
@Suppress("unused")
object EnableDebugMenuPatch : BytecodePatch(
setOf(InitializeBuildConfigProviderFingerprint)
) {
override fun execute(context: BytecodeContext) {
InitializeBuildConfigProviderFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
val insertIndex = it.scanResult.patternScanResult!!.startIndex
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
addInstructions(
insertIndex,
"const/4 v$register, 0x1"
)
}
}
}
}

View File

@@ -1,25 +0,0 @@
package app.revanced.patches.duolingo.debug.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
/**
* The `BuildConfigProvider` class has two booleans:
*
* - `isChina`: (usually) compares "play" with "china"...except for builds in China
* - `isDebug`: compares "release" with "debug" <-- we want to force this to `true`
*/
internal object InitializeBuildConfigProviderFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
strings = listOf(
"debug",
"release",
"china",
),
opcodes = listOf(
Opcode.IPUT_BOOLEAN
)
)

View File

@@ -1,26 +0,0 @@
package app.revanced.patches.facebook.ads.story
import app.revanced.util.exception
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.facebook.ads.story.fingerprints.AdsInsertionFingerprint
import app.revanced.patches.facebook.ads.story.fingerprints.FetchMoreAdsFingerprint
@Patch(
name = "Hide story ads",
description = "Hides the ads in the Facebook app stories.",
compatiblePackages = [CompatiblePackage("com.facebook.katana")]
)
@Suppress("unused")
object HideStoryAdsPatch : BytecodePatch(
setOf(FetchMoreAdsFingerprint, AdsInsertionFingerprint)
) {
override fun execute(context: BytecodeContext) =
setOf(FetchMoreAdsFingerprint, AdsInsertionFingerprint).forEach { fingerprint ->
fingerprint.result?.mutableMethod?.replaceInstruction(0, "return-void")
?: throw fingerprint.exception
}
}

View File

@@ -1,3 +0,0 @@
package app.revanced.patches.facebook.ads.story.fingerprints
internal object AdsInsertionFingerprint : FieldMethodFingerprint(fieldValue = "AdBucketDataSourceUtil\$attemptAdsInsertion\$1")

View File

@@ -1,3 +0,0 @@
package app.revanced.patches.facebook.ads.story.fingerprints
internal object FetchMoreAdsFingerprint : FieldMethodFingerprint(fieldValue = "AdBucketDataSourceUtil\$attemptFetchMoreAds\$1")

View File

@@ -1,15 +0,0 @@
package app.revanced.patches.facebook.ads.story.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue
internal abstract class FieldMethodFingerprint(fieldValue: String) : MethodFingerprint(
returnType = "V",
parameters = listOf(),
customFingerprint = { methodDef, classDef ->
methodDef.name == "run" && classDef.fields.any any@{ field ->
if (field.name != "__redex_internal_original_name") return@any false
(field.initialValue as? StringEncodedValue)?.value == fieldValue
}
}
)

View File

@@ -1,33 +0,0 @@
package app.revanced.patches.finanzonline.detection.bootloader
import app.revanced.util.exception
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.finanzonline.detection.bootloader.fingerprints.BootStateFingerprint
import app.revanced.patches.finanzonline.detection.bootloader.fingerprints.CreateKeyFingerprint
@Patch(
name = "Remove bootloader detection",
description = "Removes the check for an unlocked bootloader.",
compatiblePackages = [CompatiblePackage("at.gv.bmf.bmf2go")]
)
@Suppress("unused")
object BootloaderDetectionPatch : BytecodePatch(
setOf(CreateKeyFingerprint, BootStateFingerprint)
) {
override fun execute(context: BytecodeContext) {
arrayOf(CreateKeyFingerprint, BootStateFingerprint).forEach { fingerprint ->
fingerprint.result?.mutableMethod?.addInstructions(
0,
"""
const/4 v0, 0x1
return v0
"""
) ?: throw fingerprint.exception
}
}
}

View File

@@ -1,30 +1,13 @@
package app.revanced.patches.finanzonline.detection.bootloader.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.AccessFlags
// Located @ at.gv.bmf.bmf2go.taxequalization.tools.utils.AttestationHelper#isBootStateOk (3.0.1)
internal object BootStateFingerprint : MethodFingerprint(
object BootStateFingerprint : MethodFingerprint(
"Z",
accessFlags = AccessFlags.PUBLIC.value,
opcodes = listOf(
Opcode.INVOKE_DIRECT,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CONST_4,
Opcode.NEW_ARRAY,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CONST_4,
Opcode.APUT_OBJECT,
Opcode.CONST_STRING,
Opcode.INVOKE_STATIC,
Opcode.SGET_OBJECT,
Opcode.IF_EQ,
Opcode.SGET_OBJECT,
Opcode.IF_NE,
Opcode.GOTO,
Opcode.MOVE,
Opcode.RETURN
)
access = AccessFlags.PUBLIC.value,
strings = listOf("Boot state of device: %s"),
customFingerprint = { methodDef ->
methodDef.definingClass.endsWith("/AttestationHelper;")
}
)

View File

@@ -0,0 +1,13 @@
package app.revanced.patches.finanzonline.detection.bootloader.fingerprints
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.AccessFlags
object BootloaderDetectionFingerprint : MethodFingerprint(
"Z",
access = AccessFlags.PUBLIC.value,
strings = listOf("Creation of attestation key succeeded", "Creation of attestation key failed"),
customFingerprint = { methodDef ->
methodDef.definingClass.endsWith("/AttestationHelper;")
}
)

View File

@@ -1,11 +0,0 @@
package app.revanced.patches.finanzonline.detection.bootloader.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
// Located @ at.gv.bmf.bmf2go.taxequalization.tools.utils.AttestationHelper#createKey (3.0.1)
internal object CreateKeyFingerprint : MethodFingerprint(
"Z",
accessFlags = AccessFlags.PUBLIC.value,
strings = listOf("attestation", "SHA-256", "random", "EC", "AndroidKeyStore")
)

View File

@@ -0,0 +1,38 @@
package app.revanced.patches.finanzonline.detection.bootloader.patch
import app.revanced.extensions.toErrorResult
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patches.finanzonline.detection.bootloader.fingerprints.BootStateFingerprint
import app.revanced.patches.finanzonline.detection.bootloader.fingerprints.BootloaderDetectionFingerprint
import app.revanced.patches.finanzonline.detection.shared.annotations.DetectionCompatibility
@Patch
@Name("remove-bootloader-detection")
@Description("Removes the check for an unlocked bootloader.")
@DetectionCompatibility
@Version("0.0.1")
class BootloaderDetectionPatch : BytecodePatch(
listOf(BootloaderDetectionFingerprint, BootStateFingerprint)
) {
override fun execute(context: BytecodeContext): PatchResult {
arrayOf(BootloaderDetectionFingerprint, BootStateFingerprint).forEach { fingerprint ->
fingerprint.result?.mutableMethod?.addInstruction(
0,
"""
const/4 v0, 0x1
return v0
"""
) ?: return fingerprint.toErrorResult()
}
return PatchResultSuccess()
}
}

View File

@@ -1,29 +0,0 @@
package app.revanced.patches.finanzonline.detection.root
import app.revanced.util.exception
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.finanzonline.detection.root.fingerprints.RootDetectionFingerprint
@Patch(
name = "Remove root detection",
description = "Removes the check for root permissions.",
compatiblePackages = [CompatiblePackage("at.gv.bmf.bmf2go")]
)
@Suppress("unused")
object RootDetectionPatch : BytecodePatch(
setOf(RootDetectionFingerprint)
) {
override fun execute(context: BytecodeContext) {
RootDetectionFingerprint.result?.mutableMethod?.addInstructions(
0,
"""
sget-object v0, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;
return-object v0
"""
) ?: throw RootDetectionFingerprint.exception
}
}

View File

@@ -1,22 +1,10 @@
package app.revanced.patches.finanzonline.detection.root.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
// Located @ at.gv.bmf.bmf2go.taxequalization.tools.utils.RootDetection#isRooted (3.0.1)
internal object RootDetectionFingerprint : MethodFingerprint(
object RootDetectionFingerprint : MethodFingerprint(
"L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("L"),
opcodes = listOf(
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.RETURN_OBJECT
)
customFingerprint = { methodDef ->
methodDef.definingClass == "Lat/gv/bmf/bmf2go/tools/utils/z;"
}
)

View File

@@ -0,0 +1,34 @@
package app.revanced.patches.finanzonline.detection.root.patch
import app.revanced.extensions.toErrorResult
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patches.finanzonline.detection.root.fingerprints.RootDetectionFingerprint
import app.revanced.patches.finanzonline.detection.shared.annotations.DetectionCompatibility
@Patch
@Name("remove-root-detection")
@Description("Removes the check for root permissions.")
@DetectionCompatibility
@Version("0.0.1")
class RootDetectionPatch : BytecodePatch(
listOf(RootDetectionFingerprint)
) {
override fun execute(context: BytecodeContext): PatchResult {
RootDetectionFingerprint.result?.mutableMethod?.addInstructions(
0,
"""
sget-object v0, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;
return-object v0
"""
) ?: return RootDetectionFingerprint.toErrorResult()
return PatchResultSuccess()
}
}

View File

@@ -0,0 +1,8 @@
package app.revanced.patches.finanzonline.detection.shared.annotations
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@Compatibility([Package("at.gv.bmf.bmf2go", arrayOf("2.2.0"))])
@Target(AnnotationTarget.CLASS)
internal annotation class DetectionCompatibility

View File

@@ -1,32 +0,0 @@
package app.revanced.patches.googlenews.customtabs
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.googlenews.customtabs.fingerprints.LaunchCustomTabFingerprint
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Patch(
name = "Enable CustomTabs",
description = "Enables CustomTabs to open articles in your default browser.",
compatiblePackages = [CompatiblePackage("com.google.android.apps.magazines")],
)
@Suppress("unused")
object EnableCustomTabs : BytecodePatch(
setOf(LaunchCustomTabFingerprint)
) {
override fun execute(context: BytecodeContext) {
LaunchCustomTabFingerprint.resultOrThrow().let { result ->
result.mutableMethod.apply {
val checkIndex = result.scanResult.patternScanResult!!.endIndex + 1
val register = getInstruction<OneRegisterInstruction>(checkIndex).registerA
replaceInstruction(checkIndex, "const/4 v$register, 0x1")
}
}
}
}

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