Compare commits
30 Commits
compose-de
...
v1.9.5
Author | SHA1 | Date | |
---|---|---|---|
c0446eac35 | |||
3af2f5b032 | |||
8f54b226b4 | |||
9f64011b26 | |||
c5fc54e721 | |||
fc8a4fc5b6 | |||
6f9ab232ae | |||
8cb96f1e45 | |||
5733acb77a | |||
e49bcb2a69 | |||
42e41c399f | |||
166a3180d3 | |||
3bf4982f23 | |||
f4e1cccfac | |||
7911a8f49e | |||
64a96fc3ce | |||
8e2cfbddc5 | |||
45fae3f0fd | |||
e45a7824c1 | |||
5d72c48a76 | |||
d6169c6fa2 | |||
9df6d52e2d | |||
239de8e923 | |||
7d553a87f3 | |||
557b42bc56 | |||
8423914748 | |||
07dce23794 | |||
18fd0552db | |||
d537d48f8e | |||
b456512bbb |
61
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
name: 🐞 Bug report
|
||||||
|
description: Create a new bug report.
|
||||||
|
title: 'bug: <title>'
|
||||||
|
labels: [bug]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
# ReVanced Manager bug report
|
||||||
|
|
||||||
|
Please check for existing issues [here](https://github.com/revanced/revanced-manager/labels/bug) before creating a new one.
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Bug description
|
||||||
|
description:
|
||||||
|
- Describe your bug in detail
|
||||||
|
- Add steps to reproduce the bug if possible (Step 1. Download some files. Step 2. ...)
|
||||||
|
- Add images and videos if possible
|
||||||
|
- List selected patches if applicable
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Version of ReVanced Manager and version & name of application you tried to patch
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Installation type
|
||||||
|
options:
|
||||||
|
- Non-root
|
||||||
|
- Root
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Device logs
|
||||||
|
description: Export logs in ReVanced Manager settings.
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Patcher logs
|
||||||
|
description: Export logs in "Patcher" screen.
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Acknowledgements
|
||||||
|
description: Your issue will be closed if you don't follow the checklist below!
|
||||||
|
options:
|
||||||
|
- label: This request is not a duplicate of an existing issue.
|
||||||
|
required: true
|
||||||
|
- label: I have chosen an appropriate title.
|
||||||
|
required: true
|
||||||
|
- label: All requested information has been provided properly.
|
||||||
|
required: true
|
||||||
|
- label: The issue is solely related to the ReVanced Manager
|
||||||
|
required: true
|
109
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,109 +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-manager/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
width="256px"
|
|
||||||
src="https://raw.githubusercontent.com/revanced/revanced-manager/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-manager/main/assets/revanced-logo/revanced-logo.svg" />
|
|
||||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager/main/assets/revanced-logo/revanced-logo.svg" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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 Manager 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-manager/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-manager/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, downloader and settings if applicable
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Patch logs
|
|
||||||
description: Patch logs can be exported by clicking on the "Logs" button in the "Patcher" screen, when patching finishes.
|
|
||||||
render: shell
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Debug logs
|
|
||||||
description: Debug logs can be exported by clicking on "Export debug logs" in "Settings" > "Advanced".
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
|
||||||
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
|
|
||||||
- label: The bug is only related to ReVanced Manager.
|
|
||||||
required: true
|
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
|
||||||
- name: 🗨 Discussions
|
|
||||||
url: https://github.com/revanced/revanced-suggestions/discussions
|
|
||||||
about: Have something unspecific to ReVanced Manager in mind? Search for or start a new discussion!
|
|
42
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
name: ⭐ Feature request
|
||||||
|
description: Create a new feature request.
|
||||||
|
title: 'feat: <title>'
|
||||||
|
labels: [feature-request]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
# ReVanced Manager feature request
|
||||||
|
|
||||||
|
Please check for existing feature requests [here](https://github.com/revanced/revanced-manager/labels/bug) before creating a new one.
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Feature description
|
||||||
|
description: Describe your feature in detail.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Motivation
|
||||||
|
description: Explain why the lack of it is a problem.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: In case there is something else you want to add.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Acknowledgements
|
||||||
|
description: Your issue will be closed if you don't follow the checklist below!
|
||||||
|
options:
|
||||||
|
- label: This request is not a duplicate of an existing issue.
|
||||||
|
required: true
|
||||||
|
- label: I have chosen an appropriate title.
|
||||||
|
required: true
|
||||||
|
- label: All requested information has been provided properly.
|
||||||
|
required: true
|
||||||
|
- label: The issue is solely related to the ReVanced Manager
|
||||||
|
required: true
|
103
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,103 +0,0 @@
|
|||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
<p align="center">
|
|
||||||
<picture>
|
|
||||||
<source
|
|
||||||
width="256px"
|
|
||||||
media="(prefers-color-scheme: dark)"
|
|
||||||
srcset="https://raw.githubusercontent.com/revanced/revanced-manager/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
width="256px"
|
|
||||||
src="https://raw.githubusercontent.com/revanced/revanced-manager/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-manager/main/assets/revanced-logo/revanced-logo.svg" />
|
|
||||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager/main/assets/revanced-logo/revanced-logo.svg" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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 Manager 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-manager/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-manager/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
|
|
||||||
- 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
|
|
||||||
- label: The feature request is only related to ReVanced Manager.
|
|
||||||
required: true
|
|
2
.github/config.yaml
vendored
@ -1,2 +1,2 @@
|
|||||||
firstPRMergeComment: >
|
firstPRMergeComment: >
|
||||||
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) to receive a role for your contribution.
|
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.
|
38
.github/workflows/analyze.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
name: Analyze Code
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "dev" ]
|
||||||
|
paths:
|
||||||
|
- "**.dart"
|
||||||
|
- ".github/workflows/analyze.yml"
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main", "dev" ]
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
- synchronize
|
||||||
|
- ready_for_review
|
||||||
|
paths:
|
||||||
|
- "**.dart"
|
||||||
|
- ".github/workflows/analyze.yml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: "Static analysis & format check"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: 'stable'
|
||||||
|
cache: true
|
||||||
|
- name: Install Flutter dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
- name: Generate files with Builder
|
||||||
|
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
|
- name: Analyze code
|
||||||
|
uses: ValentinVignal/action-dart-analyze@v0.15
|
||||||
|
with:
|
||||||
|
fail-on: warning
|
25
.github/workflows/build_pull_request.yml
vendored
@ -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
|
|
26
.github/workflows/open_pull_request.yml
vendored
@ -1,26 +0,0 @@
|
|||||||
name: Open a PR to main
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main`
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
pull-request:
|
|
||||||
name: Open pull request
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Open pull request
|
|
||||||
uses: repo-sync/pull-request@v2
|
|
||||||
with:
|
|
||||||
destination_branch: 'main'
|
|
||||||
pr_title: 'chore: ${{ env.MESSAGE }}'
|
|
||||||
pr_body: 'This pull request will ${{ env.MESSAGE }}.'
|
|
||||||
pr_draft: true
|
|
45
.github/workflows/pr-build.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
name: PR Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/pr-build.yml"
|
||||||
|
- "android/**"
|
||||||
|
- "assets/**"
|
||||||
|
- "lib/**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
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: Setup JDK
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: '11'
|
||||||
|
distribution: 'zulu'
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: 'stable'
|
||||||
|
cache: true
|
||||||
|
- name: Install Flutter dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
- name: Generate files with Builder
|
||||||
|
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
|
- name: Build with Flutter
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: flutter build apk --debug
|
||||||
|
- name: Upload build
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: revanced-manager
|
||||||
|
path: build/app/outputs/flutter-apk/app-debug.apk
|
50
.github/workflows/release-build.yml
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
name: "Release Build"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set env
|
||||||
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: "11"
|
||||||
|
distribution: "zulu"
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
- name: Set up Flutter
|
||||||
|
run: flutter pub get
|
||||||
|
- name: Generate files with Builder
|
||||||
|
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
|
- name: Build with Flutter
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||||
|
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||||
|
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
||||||
|
run: flutter build apk
|
||||||
|
- name: Sign APK
|
||||||
|
id: sign_apk
|
||||||
|
uses: ilharp/sign-android-release@v1
|
||||||
|
with:
|
||||||
|
releaseDir: build/app/outputs/apk/release
|
||||||
|
signingKey: ${{ secrets.SIGNING_KEYSTORE }}
|
||||||
|
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
||||||
|
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||||
|
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||||
|
- name: Add version to APK
|
||||||
|
run: mv ${{steps.sign_apk.outputs.signedFile}} revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||||
|
- name: Publish release APK
|
||||||
|
uses: "marvinpinto/action-automatic-releases@latest"
|
||||||
|
with:
|
||||||
|
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
prerelease: false
|
||||||
|
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
64
.github/workflows/release.yml
vendored
@ -1,64 +0,0 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- dev
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Release
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
id-token: write
|
|
||||||
attestations: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Java
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: '17'
|
|
||||||
|
|
||||||
- name: Cache Gradle
|
|
||||||
uses: burrunan/gradle-cache-action@v1
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: ./gradlew assembleRelease
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "lts/*"
|
|
||||||
cache: 'npm'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Setup keystore
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.KEYSTORE }}" | base64 --decode > "app/keystore.jks"
|
|
||||||
|
|
||||||
- name: Semantic Release
|
|
||||||
uses: cycjimmy/semantic-release-action@v4
|
|
||||||
id: semantic
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
|
||||||
KEYSTORE_ENTRY_ALIAS: ${{ secrets.KEYSTORE_ENTRY_ALIAS }}
|
|
||||||
KEYSTORE_ENTRY_PASSWORD: ${{ secrets.KEYSTORE_ENTRY_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Attest
|
|
||||||
if: steps.semantic.outputs.new_release_published == 'true'
|
|
||||||
uses: actions/attest-build-provenance@v2
|
|
||||||
with:
|
|
||||||
subject-path: build/app/outputs/apk/release/revanced-manager-*.apk
|
|
@ -11,7 +11,7 @@ jobs:
|
|||||||
name: Dispatch event to documentation repository
|
name: Dispatch event to documentation repository
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
steps:
|
steps:
|
||||||
- uses: peter-evans/repository-dispatch@v3
|
- uses: peter-evans/repository-dispatch@v2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
||||||
repository: revanced/revanced-documentation
|
repository: revanced/revanced-documentation
|
153
.gitignore
vendored
@ -1,12 +1,145 @@
|
|||||||
*.iml
|
# Miscellaneous
|
||||||
.gradle
|
*.class
|
||||||
/local.properties
|
*.lock
|
||||||
/.idea
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
.atom/
|
||||||
/captures
|
.buildlog/
|
||||||
.externalNativeBuild
|
.history
|
||||||
.cxx
|
.svn/
|
||||||
local.properties
|
|
||||||
|
|
||||||
.kotlin/
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Visual Studio Code related
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Flutter repo-specific
|
||||||
|
/bin/cache/
|
||||||
|
/bin/mingit/
|
||||||
|
/dev/benchmarks/mega_gallery/
|
||||||
|
/dev/bots/.recipe_deps
|
||||||
|
/dev/bots/android_tools/
|
||||||
|
/dev/docs/doc/
|
||||||
|
/dev/docs/flutter.docs.zip
|
||||||
|
/dev/docs/lib/
|
||||||
|
/dev/docs/pubspec.yaml
|
||||||
|
/dev/integration_tests/**/xcuserdata
|
||||||
|
/dev/integration_tests/**/Pods
|
||||||
|
/packages/flutter/coverage/
|
||||||
|
version
|
||||||
|
|
||||||
|
# packages file containing multi-root paths
|
||||||
|
.packages.generated
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/*.g.dart
|
||||||
|
**/*.locator.dart
|
||||||
|
**/*.router.dart
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
**/generated_plugin_registrant.dart
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
build/
|
||||||
|
flutter_*.png
|
||||||
|
linked_*.ds
|
||||||
|
unlinked.ds
|
||||||
|
unlinked_spec.ds
|
||||||
|
|
||||||
|
# Android related
|
||||||
|
.gradle/
|
||||||
|
**/android/**/gradle-wrapper.jar
|
||||||
|
**/android/.gradle
|
||||||
|
**/android/captures/
|
||||||
|
**/android/gradlew
|
||||||
|
**/android/gradlew.bat
|
||||||
|
**/android/local.properties
|
||||||
|
**/android/**/GeneratedPluginRegistrant.java
|
||||||
|
**/android/key.properties
|
||||||
|
*.jks
|
||||||
|
|
||||||
|
# iOS/XCode related
|
||||||
|
**/ios/**/*.mode1v3
|
||||||
|
**/ios/**/*.mode2v3
|
||||||
|
**/ios/**/*.moved-aside
|
||||||
|
**/ios/**/*.pbxuser
|
||||||
|
**/ios/**/*.perspectivev3
|
||||||
|
**/ios/**/*sync/
|
||||||
|
**/ios/**/.sconsign.dblite
|
||||||
|
**/ios/**/.tags*
|
||||||
|
**/ios/**/.vagrant/
|
||||||
|
**/ios/**/DerivedData/
|
||||||
|
**/ios/**/Icon?
|
||||||
|
**/ios/**/Pods/
|
||||||
|
**/ios/**/.symlinks/
|
||||||
|
**/ios/**/profile
|
||||||
|
**/ios/**/xcuserdata
|
||||||
|
**/ios/.generated/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
**/ios/Flutter/App.framework
|
||||||
|
**/ios/Flutter/Flutter.framework
|
||||||
|
**/ios/Flutter/Flutter.podspec
|
||||||
|
**/ios/Flutter/Generated.xcconfig
|
||||||
|
**/ios/Flutter/app.flx
|
||||||
|
**/ios/Flutter/app.zip
|
||||||
|
**/ios/Flutter/flutter_assets/
|
||||||
|
**/ios/Flutter/flutter_export_environment.sh
|
||||||
|
**/ios/ServiceDefinitions.json
|
||||||
|
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# macOS related
|
||||||
|
**/macos/Flutter/GeneratedPluginRegistrant.swift
|
||||||
|
**/macos/Flutter/Flutter-Debug.xcconfig
|
||||||
|
**/macos/Flutter/Flutter-Release.xcconfig
|
||||||
|
**/macos/Flutter/Flutter-Profile.xcconfig
|
||||||
|
|
||||||
|
# Windows related
|
||||||
|
**/windows/flutter/ephemeral/
|
||||||
|
**/windows/**/*.suo
|
||||||
|
**/windows/**/*.user
|
||||||
|
**/windows/**/*.userosscache
|
||||||
|
**/windows/**/*.sln.docstates
|
||||||
|
**/windows/x64/
|
||||||
|
**/windows/x86/
|
||||||
|
**/windows/**/*.[Cc]ache
|
||||||
|
**/windows/**/!*.[Cc]ache/
|
||||||
|
|
||||||
|
# Web related
|
||||||
|
lib/generated_plugin_registrant.dart
|
||||||
|
|
||||||
|
# Coverage
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!**/ios/**/default.mode1v3
|
||||||
|
!**/ios/**/default.mode2v3
|
||||||
|
!**/ios/**/default.pbxuser
|
||||||
|
!**/ios/**/default.perspectivev3
|
||||||
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
!/dev/ci/**/Gemfile.lock
|
||||||
|
|
||||||
|
# Firebase related
|
||||||
|
.firebase
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# FVM
|
||||||
|
.fvm
|
45
.metadata
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
- platform: android
|
||||||
|
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
- platform: ios
|
||||||
|
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
- platform: linux
|
||||||
|
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
- platform: macos
|
||||||
|
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
- platform: web
|
||||||
|
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
- platform: windows
|
||||||
|
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
75
.releaserc
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"branches": [
|
||||||
|
"main",
|
||||||
|
{
|
||||||
|
"name": "dev",
|
||||||
|
"prerelease": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"semantic-release-export-data",
|
||||||
|
"@semantic-release/commit-analyzer",
|
||||||
|
[
|
||||||
|
"@semantic-release/release-notes-generator",
|
||||||
|
{
|
||||||
|
"presetConfig": {
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"section": "Dependency Updates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chore",
|
||||||
|
"section": "Other Changes",
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "perf",
|
||||||
|
"section": "Performance Improvements",
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "refactor",
|
||||||
|
"section": "Code Improvements",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@semantic-release/changelog",
|
||||||
|
"semantic-release-flutter-plugin",
|
||||||
|
[
|
||||||
|
"@semantic-release/git",
|
||||||
|
{
|
||||||
|
"assets": [
|
||||||
|
"CHANGELOG.md",
|
||||||
|
"pubspec.yaml"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@semantic-release/github",
|
||||||
|
{
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"path": "build/app/outputs/apk/release/revanced-manager-*.apk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"successComment": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@saithodev/semantic-release-backmerge",
|
||||||
|
{
|
||||||
|
"backmergeBranches": [
|
||||||
|
{
|
||||||
|
"from": "main",
|
||||||
|
"to": "dev"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"clearWorkspace": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
6
.run/main.dart.run.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
91
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Generate (Builder)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "flutter packages pub run build_runner build --delete-conflicting-outputs",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Build (Android)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "flutter build apk",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Install (Android)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "adb install build\\app\\outputs\\flutter-apk\\app-release.apk",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Clean (Flutter)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "flutter clean && flutter pub get",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Clean (Builder)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "flutter packages pub run build_runner clean",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Build all (Android)",
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"dependsOn": [
|
||||||
|
"Generate (Builder)",
|
||||||
|
"Build (Android)"
|
||||||
|
],
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Clean all",
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"dependsOn": [
|
||||||
|
"Clean (Flutter)",
|
||||||
|
"Clean (Builder)"
|
||||||
|
],
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Clean all & Build all (Android)",
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"dependsOn": [
|
||||||
|
"Clean all",
|
||||||
|
"Build all (Android)"
|
||||||
|
],
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Clean all & Install (Android)",
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"dependsOn": [
|
||||||
|
"Clean all",
|
||||||
|
"Build all (Android)",
|
||||||
|
"Install (Android)",
|
||||||
|
],
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Build & Install (Android)",
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"dependsOn": [
|
||||||
|
"Build (Android)",
|
||||||
|
"Install (Android)"
|
||||||
|
],
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Validate translations",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "flutter pub run flutter_i18n diff en.json pt.json",
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
1
CHANGELOG.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
103
CONTRIBUTING.md
@ -1,103 +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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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 Manager.
|
|
||||||
|
|
||||||
## 📖 Resources to help you get started
|
|
||||||
|
|
||||||
* The [documentation](/docs/README.md) provides steps to build ReVanced Manager from source
|
|
||||||
* 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-manager/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-manager/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 Manager.
|
|
||||||
> Good motivation has to be provided for a request to be accepted.
|
|
||||||
|
|
||||||
## 🐞 Submitting a bug report
|
|
||||||
|
|
||||||
If you encounter a bug while using ReVanced Manager, open an issue using the
|
|
||||||
[Bug report issue template](https://github.com/ReVanced/revanced-manager/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
|
|
||||||
|
|
||||||
## 📝 How to contribute
|
|
||||||
|
|
||||||
1. Before contributing, it is recommended to open an issue to discuss your change
|
|
||||||
with the maintainers of ReVanced Manager. 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
|
|
||||||
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 Manager
|
|
||||||
|
|
||||||
## 🤚 I want to contribute but don't know how to code
|
|
||||||
|
|
||||||
Even if you don't know how to code, you can still contribute by
|
|
||||||
translating ReVanced Manager on [Crowdin](https://translate.revanced.app/).
|
|
||||||
|
|
||||||
❤️ Thank you for considering contributing to ReVanced Manager,
|
|
||||||
ReVanced
|
|
113
README.md
@ -1,104 +1,31 @@
|
|||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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 Manager
|
# 💊 ReVanced Manager
|
||||||
|
|
||||||

|
The official ReVanced Manager based on Flutter.
|
||||||

|
|
||||||
|
|
||||||
Application to use ReVanced on Android
|
|
||||||
|
|
||||||
## ❓ About
|
|
||||||
|
|
||||||
ReVanced Manager is an application that uses [ReVanced Patcher](https://github.com/revanced/revanced-patcher) to patch Android apps.
|
|
||||||
|
|
||||||
## 💪 Features
|
|
||||||
|
|
||||||
Some of the features ReVanced Manager provides are:
|
|
||||||
|
|
||||||
- ⬇️ **Download**: Automatically download apps using the ReVanced Manager downloader plugin system
|
|
||||||
- 💉 **Patch**: Select and apply patches to any Android app
|
|
||||||
- 🛠️ **Customize**: Manage patches, apps, signing, themes, updates, and many more settings
|
|
||||||
|
|
||||||
## 🔽 Download
|
## 🔽 Download
|
||||||
|
To download latest Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
|
||||||
|
|
||||||
You can download the most recent version of ReVanced Manager at [revanced.app/download](https://revanced.app/download) or from [GitHub releases](https://github.com/ReVanced/revanced-manager/releases/latest).
|
## 📝 Prerequisites
|
||||||
Learn how to use ReVanced Manager by following the [documentation](/docs).
|
1. Android 8 or higher
|
||||||
|
2. Does not work on some armv7 devices
|
||||||
|
|
||||||
## 📚 Everything else
|
## 🔴 Issues
|
||||||
|
For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose).
|
||||||
|
|
||||||
### 📙 Contributing
|
## 💭 Discussion
|
||||||
|
If you wish to discuss the Manager, a thread has been made under the [#development](https://discord.com/channels/952946952348270622/1002922226443632761) channel in the Discord server, please note that this thread may be temporary and may be removed in the future.
|
||||||
|
|
||||||
Thank you for considering contributing to ReVanced Manager.
|
|
||||||
You can find the contribution guidelines [here](CONTRIBUTING.md).
|
|
||||||
|
|
||||||
### 🛠️ Building
|
## 🌐 Translation
|
||||||
|
[](https://crowdin.com/project/revanced)
|
||||||
|
|
||||||
To build a ReVanced Manager, you can follow the [documentation](/docs).
|
If you wish to translate ReVanced Manager, we're accepting translations on [Crowdin](https://translate.revanced.app)
|
||||||
|
|
||||||
### 📄 Documentation
|
## 🛠️ Building Manager from source
|
||||||
|
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
||||||
You can find the documentation for ReVanced Manager [here](/docs).
|
2. Clone the repository locally
|
||||||
|
3. Add your github token in gradle.properties like [this](/docs/4_building.md)
|
||||||
## ⚖️ License
|
4. Open the project in terminal
|
||||||
|
5. Run `flutter pub get` in terminal
|
||||||
ReVanced Manager is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information.
|
6. Then `flutter packages pub run build_runner build --delete-conflicting-outputs` (Must be done on each git pull)
|
||||||
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Manager as long as you track changes/dates in source files.
|
7. To build release apk run `flutter build apk`
|
||||||
Any modifications to ReVanced Manager must also be made available under the GPL, along with build & install instructions.
|
|
||||||
|
163
analysis_options.yaml
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- lib/app/app.locator.dart
|
||||||
|
- lib/app/app.router.dart
|
||||||
|
- lib/models/patch.g.dart
|
||||||
|
- lib/models/patched_application.g.dart
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
- always_declare_return_types
|
||||||
|
- require_trailing_commas
|
||||||
|
- always_put_control_body_on_new_line
|
||||||
|
- always_require_non_null_named_parameters
|
||||||
|
- always_use_package_imports # we do this commonly
|
||||||
|
- annotate_overrides
|
||||||
|
- avoid_bool_literals_in_conditional_expressions
|
||||||
|
- avoid_double_and_int_checks
|
||||||
|
- avoid_empty_else
|
||||||
|
- avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
- avoid_escaping_inner_quotes
|
||||||
|
- avoid_field_initializers_in_const_classes
|
||||||
|
- avoid_function_literals_in_foreach_calls
|
||||||
|
- avoid_implementing_value_types
|
||||||
|
- avoid_init_to_null
|
||||||
|
- avoid_js_rounded_ints
|
||||||
|
- avoid_null_checks_in_equality_operators
|
||||||
|
- avoid_print
|
||||||
|
- avoid_redundant_argument_values
|
||||||
|
- avoid_relative_lib_imports
|
||||||
|
- avoid_renaming_method_parameters
|
||||||
|
- avoid_return_types_on_setters
|
||||||
|
- avoid_returning_null
|
||||||
|
- avoid_returning_null_for_future
|
||||||
|
- avoid_returning_null_for_void
|
||||||
|
- avoid_setters_without_getters
|
||||||
|
- avoid_shadowing_type_parameters
|
||||||
|
- avoid_single_cascade_in_expression_statements
|
||||||
|
- avoid_type_to_string
|
||||||
|
- avoid_types_as_parameter_names
|
||||||
|
- avoid_unnecessary_containers
|
||||||
|
- avoid_void_async
|
||||||
|
- avoid_web_libraries_in_flutter # we use web libraries in web-specific code, and our tests prevent us from using them elsewhere
|
||||||
|
- await_only_futures
|
||||||
|
- camel_case_extensions
|
||||||
|
- camel_case_types
|
||||||
|
- cancel_subscriptions
|
||||||
|
- cast_nullable_to_non_nullable
|
||||||
|
- close_sinks # not reliable enough
|
||||||
|
- control_flow_in_finally
|
||||||
|
- curly_braces_in_flow_control_structures
|
||||||
|
- depend_on_referenced_packages
|
||||||
|
- deprecated_consistency
|
||||||
|
- directives_ordering
|
||||||
|
- empty_catches
|
||||||
|
- empty_constructor_bodies
|
||||||
|
- empty_statements
|
||||||
|
- eol_at_end_of_file
|
||||||
|
- exhaustive_cases
|
||||||
|
- file_names
|
||||||
|
- flutter_style_todos
|
||||||
|
- hash_and_equals
|
||||||
|
- implementation_imports
|
||||||
|
- collection_methods_unrelated_type
|
||||||
|
- leading_newlines_in_multiline_strings
|
||||||
|
- library_names
|
||||||
|
- library_prefixes
|
||||||
|
- library_private_types_in_public_api
|
||||||
|
- missing_whitespace_between_adjacent_strings
|
||||||
|
- no_adjacent_strings_in_list
|
||||||
|
- no_duplicate_case_values
|
||||||
|
- no_logic_in_create_state
|
||||||
|
- non_constant_identifier_names
|
||||||
|
- noop_primitive_operations
|
||||||
|
- null_check_on_nullable_type_parameter
|
||||||
|
- null_closures
|
||||||
|
- overridden_fields
|
||||||
|
- package_api_docs
|
||||||
|
- package_names
|
||||||
|
- package_prefixed_library_names
|
||||||
|
- prefer_adjacent_string_concatenation
|
||||||
|
- prefer_asserts_in_initializer_lists
|
||||||
|
- prefer_collection_literals
|
||||||
|
- prefer_conditional_assignment
|
||||||
|
- prefer_const_constructors
|
||||||
|
- prefer_const_constructors_in_immutables
|
||||||
|
- prefer_const_declarations
|
||||||
|
- prefer_const_literals_to_create_immutables
|
||||||
|
- prefer_contains
|
||||||
|
- prefer_final_fields
|
||||||
|
- prefer_final_in_for_each
|
||||||
|
- prefer_final_locals
|
||||||
|
- prefer_for_elements_to_map_fromIterable
|
||||||
|
- prefer_foreach
|
||||||
|
- prefer_function_declarations_over_variables
|
||||||
|
- prefer_generic_function_type_aliases
|
||||||
|
- prefer_if_elements_to_conditional_expressions
|
||||||
|
- prefer_if_null_operators
|
||||||
|
- prefer_initializing_formals
|
||||||
|
- prefer_inlined_adds
|
||||||
|
- prefer_interpolation_to_compose_strings
|
||||||
|
- prefer_is_empty
|
||||||
|
- prefer_is_not_empty
|
||||||
|
- prefer_is_not_operator
|
||||||
|
- prefer_iterable_whereType
|
||||||
|
- prefer_mixin # Has false positives, see https://github.com/dart-lang/linter/issues/3018
|
||||||
|
- prefer_null_aware_method_calls # "call()" is confusing to people new to the language since it's not documented anywhere
|
||||||
|
- prefer_null_aware_operators
|
||||||
|
- prefer_single_quotes
|
||||||
|
- prefer_spread_collections
|
||||||
|
- prefer_typing_uninitialized_variables
|
||||||
|
- prefer_void_to_null
|
||||||
|
- provide_deprecation_message
|
||||||
|
- recursive_getters
|
||||||
|
- sized_box_for_whitespace
|
||||||
|
- slash_for_doc_comments
|
||||||
|
- sort_child_properties_last
|
||||||
|
- sort_constructors_first
|
||||||
|
- sort_unnamed_constructors_first
|
||||||
|
- test_types_in_equals
|
||||||
|
- throw_in_finally
|
||||||
|
- tighten_type_of_initializing_formals
|
||||||
|
- type_init_formals
|
||||||
|
- unnecessary_brace_in_string_interps
|
||||||
|
- unnecessary_const
|
||||||
|
- unnecessary_getters_setters
|
||||||
|
- unnecessary_new
|
||||||
|
- unnecessary_null_aware_assignments
|
||||||
|
- unnecessary_null_checks
|
||||||
|
- unnecessary_null_in_if_null_operators
|
||||||
|
- unnecessary_nullable_for_final_variable_declarations
|
||||||
|
- unnecessary_overrides
|
||||||
|
- unnecessary_parenthesis
|
||||||
|
- unnecessary_statements
|
||||||
|
- unnecessary_string_escapes
|
||||||
|
- unnecessary_string_interpolations
|
||||||
|
- unnecessary_this
|
||||||
|
- unrelated_type_equality_checks
|
||||||
|
- unsafe_html
|
||||||
|
- use_build_context_synchronously
|
||||||
|
- use_full_hex_values_for_flutter_colors
|
||||||
|
- use_function_type_syntax_for_parameters
|
||||||
|
- use_if_null_to_convert_nulls_to_bools
|
||||||
|
- use_is_even_rather_than_modulo
|
||||||
|
- use_key_in_widget_constructors
|
||||||
|
- use_late_for_private_fields_and_variables
|
||||||
|
- use_named_constants
|
||||||
|
- use_raw_strings
|
||||||
|
- use_rethrow_when_possible
|
||||||
|
- use_setters_to_change_properties
|
||||||
|
- use_test_throws_matchers
|
||||||
|
- valid_regexps
|
||||||
|
- void_checks
|
13
android/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
3
android/Gemfile
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "fastlane"
|
93
android/app/build.gradle
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||||
|
if (flutterRoot == null) {
|
||||||
|
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_11
|
||||||
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '11'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "app.revanced.manager.flutter"
|
||||||
|
minSdkVersion 26
|
||||||
|
targetSdkVersion 33
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
shrinkResources false
|
||||||
|
minifyEnabled false
|
||||||
|
resValue "string", "app_name", "ReVanced Manager"
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
ndk {
|
||||||
|
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
shrinkResources false
|
||||||
|
minifyEnabled false
|
||||||
|
resValue "string", "app_name", "ReVanced Manager Debug"
|
||||||
|
applicationIdSuffix ".debug"
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
ndk {
|
||||||
|
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
exclude '/prebuilt/**'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source '../..'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
|
// ReVanced
|
||||||
|
implementation "app.revanced:revanced-patcher:14.2.2"
|
||||||
|
|
||||||
|
// Signing & aligning
|
||||||
|
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||||
|
implementation("com.android.tools.build:apksig:7.2.2")
|
||||||
|
}
|
4
android/app/src/debug/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="app.revanced.manager.flutter">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
59
android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="app.revanced.manager.flutter">
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="32" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="32" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||||
|
<application
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:largeHeap="true"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
|
android:extractNativeLibs="true"
|
||||||
|
android:enableOnBackInvokedCallback="true">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileProvider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
@ -0,0 +1,300 @@
|
|||||||
|
package app.revanced.manager.flutter
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import app.revanced.manager.flutter.utils.Aapt
|
||||||
|
import app.revanced.manager.flutter.utils.aligning.ZipAligner
|
||||||
|
import app.revanced.manager.flutter.utils.signing.Signer
|
||||||
|
import app.revanced.manager.flutter.utils.zip.ZipFile
|
||||||
|
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
|
||||||
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
|
import app.revanced.patcher.Patcher
|
||||||
|
import app.revanced.patcher.PatcherOptions
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
|
import app.revanced.patcher.patch.PatchResult
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.io.File
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.io.StringWriter
|
||||||
|
import java.util.logging.LogRecord
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity() {
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private lateinit var installerChannel: MethodChannel
|
||||||
|
private var cancel: Boolean = false
|
||||||
|
private var stopResult: MethodChannel.Result? = null
|
||||||
|
|
||||||
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
|
||||||
|
val patcherChannel = "app.revanced.manager.flutter/patcher"
|
||||||
|
val installerChannel = "app.revanced.manager.flutter/installer"
|
||||||
|
|
||||||
|
val mainChannel =
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, patcherChannel)
|
||||||
|
|
||||||
|
this.installerChannel =
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, installerChannel)
|
||||||
|
|
||||||
|
mainChannel.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"runPatcher" -> {
|
||||||
|
val patchBundleFilePath = call.argument<String>("patchBundleFilePath")
|
||||||
|
val originalFilePath = call.argument<String>("originalFilePath")
|
||||||
|
val inputFilePath = call.argument<String>("inputFilePath")
|
||||||
|
val patchedFilePath = call.argument<String>("patchedFilePath")
|
||||||
|
val outFilePath = call.argument<String>("outFilePath")
|
||||||
|
val integrationsPath = call.argument<String>("integrationsPath")
|
||||||
|
val selectedPatches = call.argument<List<String>>("selectedPatches")
|
||||||
|
val cacheDirPath = call.argument<String>("cacheDirPath")
|
||||||
|
val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
|
||||||
|
val keystorePassword = call.argument<String>("keystorePassword")
|
||||||
|
|
||||||
|
if (patchBundleFilePath != null &&
|
||||||
|
originalFilePath != null &&
|
||||||
|
inputFilePath != null &&
|
||||||
|
patchedFilePath != null &&
|
||||||
|
outFilePath != null &&
|
||||||
|
integrationsPath != null &&
|
||||||
|
selectedPatches != null &&
|
||||||
|
cacheDirPath != null &&
|
||||||
|
keyStoreFilePath != null &&
|
||||||
|
keystorePassword != null
|
||||||
|
) {
|
||||||
|
cancel = false
|
||||||
|
runPatcher(
|
||||||
|
result,
|
||||||
|
patchBundleFilePath,
|
||||||
|
originalFilePath,
|
||||||
|
inputFilePath,
|
||||||
|
patchedFilePath,
|
||||||
|
outFilePath,
|
||||||
|
integrationsPath,
|
||||||
|
selectedPatches,
|
||||||
|
cacheDirPath,
|
||||||
|
keyStoreFilePath,
|
||||||
|
keystorePassword
|
||||||
|
)
|
||||||
|
} else result.notImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
"stopPatcher" -> {
|
||||||
|
cancel = true
|
||||||
|
stopResult = result
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runPatcher(
|
||||||
|
result: MethodChannel.Result,
|
||||||
|
patchBundleFilePath: String,
|
||||||
|
originalFilePath: String,
|
||||||
|
inputFilePath: String,
|
||||||
|
patchedFilePath: String,
|
||||||
|
outFilePath: String,
|
||||||
|
integrationsPath: String,
|
||||||
|
selectedPatches: List<String>,
|
||||||
|
cacheDirPath: String,
|
||||||
|
keyStoreFilePath: String,
|
||||||
|
keystorePassword: String
|
||||||
|
) {
|
||||||
|
val originalFile = File(originalFilePath)
|
||||||
|
val inputFile = File(inputFilePath)
|
||||||
|
val patchedFile = File(patchedFilePath)
|
||||||
|
val outFile = File(outFilePath)
|
||||||
|
val integrations = File(integrationsPath)
|
||||||
|
val keyStoreFile = File(keyStoreFilePath)
|
||||||
|
val cacheDir = File(cacheDirPath)
|
||||||
|
|
||||||
|
Thread {
|
||||||
|
fun updateProgress(progress: Double, header: String, log: String) {
|
||||||
|
handler.post {
|
||||||
|
installerChannel.invokeMethod(
|
||||||
|
"update",
|
||||||
|
mapOf(
|
||||||
|
"progress" to progress,
|
||||||
|
"header" to header,
|
||||||
|
"log" to log
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postStop() = handler.post { stopResult!!.success(null) }
|
||||||
|
|
||||||
|
// Setup logger
|
||||||
|
Logger.getLogger("").apply {
|
||||||
|
handlers.forEach {
|
||||||
|
it.close()
|
||||||
|
removeHandler(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
object : java.util.logging.Handler() {
|
||||||
|
override fun publish(record: LogRecord) =
|
||||||
|
updateProgress(-1.0, "", record.message)
|
||||||
|
|
||||||
|
override fun flush() = Unit
|
||||||
|
override fun close() = flush()
|
||||||
|
}.let(::addHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateProgress(0.0, "", "Copying APK")
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
postStop()
|
||||||
|
return@Thread
|
||||||
|
}
|
||||||
|
|
||||||
|
originalFile.copyTo(inputFile, true)
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
postStop()
|
||||||
|
return@Thread
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(0.05, "Reading APK...", "Reading APK")
|
||||||
|
|
||||||
|
val patcher = Patcher(
|
||||||
|
PatcherOptions(
|
||||||
|
inputFile,
|
||||||
|
cacheDir,
|
||||||
|
Aapt.binary(applicationContext).absolutePath,
|
||||||
|
cacheDir.path,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
postStop()
|
||||||
|
return@Thread
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(0.1, "Loading patches...", "Loading patches")
|
||||||
|
|
||||||
|
val patches = PatchBundleLoader.Dex(
|
||||||
|
File(patchBundleFilePath),
|
||||||
|
optimizedDexDirectory = cacheDir
|
||||||
|
).filter { patch ->
|
||||||
|
val isCompatible = patch.compatiblePackages?.any {
|
||||||
|
it.name == patcher.context.packageMetadata.packageName
|
||||||
|
} ?: false
|
||||||
|
|
||||||
|
val compatibleOrUniversal =
|
||||||
|
isCompatible || patch.compatiblePackages.isNullOrEmpty()
|
||||||
|
|
||||||
|
compatibleOrUniversal && selectedPatches.any { it == patch.patchName }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
postStop()
|
||||||
|
return@Thread
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(0.15, "Executing...", "")
|
||||||
|
|
||||||
|
// Update the progress bar every time a patch is executed from 0.15 to 0.7
|
||||||
|
val totalPatchesCount = patches.size
|
||||||
|
val progressStep = 0.55 / totalPatchesCount
|
||||||
|
var progress = 0.15
|
||||||
|
|
||||||
|
patcher.apply {
|
||||||
|
acceptIntegrations(listOf(integrations))
|
||||||
|
acceptPatches(patches)
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
apply(false).collect { patchResult: PatchResult ->
|
||||||
|
if (cancel) {
|
||||||
|
handler.post { stopResult!!.success(null) }
|
||||||
|
this.cancel()
|
||||||
|
this@apply.close()
|
||||||
|
return@collect
|
||||||
|
}
|
||||||
|
|
||||||
|
val msg = patchResult.exception?.let {
|
||||||
|
val writer = StringWriter()
|
||||||
|
it.printStackTrace(PrintWriter(writer))
|
||||||
|
"${patchResult.patchName} failed: $writer"
|
||||||
|
} ?: run {
|
||||||
|
"${patchResult.patchName} succeeded"
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(progress, "", msg)
|
||||||
|
progress += progressStep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
postStop()
|
||||||
|
patcher.close()
|
||||||
|
return@Thread
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(0.8, "Building...", "")
|
||||||
|
|
||||||
|
val res = patcher.get()
|
||||||
|
patcher.close()
|
||||||
|
|
||||||
|
ZipFile(patchedFile).use { file ->
|
||||||
|
res.dexFiles.forEach {
|
||||||
|
if (cancel) {
|
||||||
|
postStop()
|
||||||
|
return@Thread
|
||||||
|
}
|
||||||
|
file.addEntryCompressData(
|
||||||
|
ZipEntry.createWithName(it.name),
|
||||||
|
it.stream.readBytes()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
res.resourceFile?.let {
|
||||||
|
file.copyEntriesFromFileAligned(
|
||||||
|
ZipFile(it),
|
||||||
|
ZipAligner::getEntryAlignment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
file.copyEntriesFromFileAligned(
|
||||||
|
ZipFile(inputFile),
|
||||||
|
ZipAligner::getEntryAlignment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
postStop()
|
||||||
|
return@Thread
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(0.9, "Signing...", "Signing APK")
|
||||||
|
|
||||||
|
try {
|
||||||
|
Signer("ReVanced", keystorePassword)
|
||||||
|
.signApk(patchedFile, outFile, keyStoreFile)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
print("Error signing APK: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(1.0, "Patched", "Patched")
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
if (!cancel) {
|
||||||
|
val stack = ex.stackTraceToString()
|
||||||
|
updateProgress(
|
||||||
|
-100.0,
|
||||||
|
"Aborted",
|
||||||
|
"An error occurred:\n$stack"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.post { result.success(null) }
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package app.revanced.manager.flutter.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object Aapt {
|
||||||
|
fun binary(context: Context): File {
|
||||||
|
return File(context.applicationInfo.nativeLibraryDir).resolveAapt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.resolveAapt() = resolve(list { _, f -> !File(f).isDirectory && f.contains("aapt") }!!.first())
|
@ -0,0 +1,11 @@
|
|||||||
|
package app.revanced.manager.flutter.utils.aligning
|
||||||
|
|
||||||
|
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
|
||||||
|
|
||||||
|
internal object ZipAligner {
|
||||||
|
private const val DEFAULT_ALIGNMENT = 4
|
||||||
|
private const val LIBRARY_ALIGNMENT = 4096
|
||||||
|
|
||||||
|
fun getEntryAlignment(entry: ZipEntry): Int? =
|
||||||
|
if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package app.revanced.manager.flutter.utils.signing
|
||||||
|
|
||||||
|
import com.android.apksig.ApkSigner
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||||
|
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
|
import org.bouncycastle.operator.ContentSigner
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.*
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
internal class Signer(
|
||||||
|
private val cn: String, password: String
|
||||||
|
) {
|
||||||
|
private val passwordCharArray = password.toCharArray()
|
||||||
|
private fun newKeystore(out: File) {
|
||||||
|
val (publicKey, privateKey) = createKey()
|
||||||
|
val privateKS = KeyStore.getInstance("BKS", "BC")
|
||||||
|
privateKS.load(null, passwordCharArray)
|
||||||
|
privateKS.setKeyEntry("alias", privateKey, passwordCharArray, arrayOf(publicKey))
|
||||||
|
privateKS.store(FileOutputStream(out), passwordCharArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createKey(): Pair<X509Certificate, PrivateKey> {
|
||||||
|
val gen = KeyPairGenerator.getInstance("RSA")
|
||||||
|
gen.initialize(2048)
|
||||||
|
val pair = gen.generateKeyPair()
|
||||||
|
var serialNumber: BigInteger
|
||||||
|
do serialNumber =
|
||||||
|
BigInteger.valueOf(SecureRandom().nextLong()) while (serialNumber < BigInteger.ZERO)
|
||||||
|
val x500Name = X500Name("CN=$cn")
|
||||||
|
val builder = X509v3CertificateBuilder(
|
||||||
|
x500Name,
|
||||||
|
serialNumber,
|
||||||
|
Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L),
|
||||||
|
Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 366L * 30L),
|
||||||
|
Locale.ENGLISH,
|
||||||
|
x500Name,
|
||||||
|
SubjectPublicKeyInfo.getInstance(pair.public.encoded)
|
||||||
|
)
|
||||||
|
val signer: ContentSigner = JcaContentSignerBuilder("SHA256withRSA").build(pair.private)
|
||||||
|
return JcaX509CertificateConverter().getCertificate(builder.build(signer)) to pair.private
|
||||||
|
}
|
||||||
|
|
||||||
|
fun signApk(input: File, output: File, ks: File) {
|
||||||
|
Security.addProvider(BouncyCastleProvider())
|
||||||
|
|
||||||
|
if (!ks.exists()) newKeystore(ks)
|
||||||
|
|
||||||
|
val keyStore = KeyStore.getInstance("BKS", "BC")
|
||||||
|
FileInputStream(ks).use { fis -> keyStore.load(fis, null) }
|
||||||
|
val alias = keyStore.aliases().nextElement()
|
||||||
|
|
||||||
|
val config = ApkSigner.SignerConfig.Builder(
|
||||||
|
cn,
|
||||||
|
keyStore.getKey(alias, passwordCharArray) as PrivateKey,
|
||||||
|
listOf(keyStore.getCertificate(alias) as X509Certificate)
|
||||||
|
).build()
|
||||||
|
|
||||||
|
val signer = ApkSigner.Builder(listOf(config))
|
||||||
|
signer.setCreatedBy(cn)
|
||||||
|
signer.setInputApk(input)
|
||||||
|
signer.setOutputApk(output)
|
||||||
|
|
||||||
|
signer.build().sign()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package app.revanced.manager.flutter.utils.zip
|
||||||
|
|
||||||
|
import java.io.DataInput
|
||||||
|
import java.io.DataOutput
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
fun UInt.toLittleEndian() =
|
||||||
|
(((this.toInt() and 0xff000000.toInt()) shr 24) or ((this.toInt() and 0x00ff0000) shr 8) or ((this.toInt() and 0x0000ff00) shl 8) or (this.toInt() shl 24)).toUInt()
|
||||||
|
|
||||||
|
fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort()
|
||||||
|
|
||||||
|
fun UInt.toBigEndian() = (((this.toInt() and 0xff) shl 24) or ((this.toInt() and 0xff00) shl 8)
|
||||||
|
or ((this.toInt() and 0x00ff0000) ushr 8) or (this.toInt() ushr 24)).toUInt()
|
||||||
|
|
||||||
|
fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort()
|
||||||
|
|
||||||
|
fun ByteBuffer.getUShort() = this.short.toUShort()
|
||||||
|
fun ByteBuffer.getUInt() = this.int.toUInt()
|
||||||
|
|
||||||
|
fun ByteBuffer.putUShort(ushort: UShort): ByteBuffer = this.putShort(ushort.toShort())
|
||||||
|
fun ByteBuffer.putUInt(uint: UInt): ByteBuffer = this.putInt(uint.toInt())
|
||||||
|
|
||||||
|
fun DataInput.readUShort() = this.readShort().toUShort()
|
||||||
|
fun DataInput.readUInt() = this.readInt().toUInt()
|
||||||
|
|
||||||
|
fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt())
|
||||||
|
fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt())
|
||||||
|
|
||||||
|
fun DataInput.readUShortLE() = this.readUShort().toBigEndian()
|
||||||
|
fun DataInput.readUIntLE() = this.readUInt().toBigEndian()
|
||||||
|
|
||||||
|
fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian())
|
||||||
|
fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian())
|
@ -0,0 +1,176 @@
|
|||||||
|
package app.revanced.manager.flutter.utils.zip
|
||||||
|
|
||||||
|
import app.revanced.manager.flutter.utils.zip.structures.ZipEndRecord
|
||||||
|
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.File
|
||||||
|
import java.io.RandomAccessFile
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.channels.FileChannel
|
||||||
|
import java.util.zip.CRC32
|
||||||
|
import java.util.zip.Deflater
|
||||||
|
|
||||||
|
class ZipFile(file: File) : Closeable {
|
||||||
|
var entries: MutableList<ZipEntry> = mutableListOf()
|
||||||
|
|
||||||
|
private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw")
|
||||||
|
private var CDNeedsRewrite = false
|
||||||
|
|
||||||
|
private val compressionLevel = 5
|
||||||
|
|
||||||
|
init {
|
||||||
|
//if file isn't empty try to load entries
|
||||||
|
if (file.length() > 0) {
|
||||||
|
val endRecord = findEndRecord()
|
||||||
|
|
||||||
|
if (endRecord.diskNumber > 0u || endRecord.totalEntries != endRecord.diskEntries)
|
||||||
|
throw IllegalArgumentException("Multi-file archives are not supported")
|
||||||
|
|
||||||
|
entries = readEntries(endRecord).toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
//seek back to start for writing
|
||||||
|
filePointer.seek(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findEndRecord(): ZipEndRecord {
|
||||||
|
//look from end to start since end record is at the end
|
||||||
|
for (i in filePointer.length() - 1 downTo 0) {
|
||||||
|
filePointer.seek(i)
|
||||||
|
//possible beginning of signature
|
||||||
|
if (filePointer.readByte() == 0x50.toByte()) {
|
||||||
|
//seek back to get the full int
|
||||||
|
filePointer.seek(i)
|
||||||
|
val possibleSignature = filePointer.readUIntLE()
|
||||||
|
if (possibleSignature == ZipEndRecord.ECD_SIGNATURE) {
|
||||||
|
filePointer.seek(i)
|
||||||
|
return ZipEndRecord.fromECD(filePointer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception("Couldn't find end record")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readEntries(endRecord: ZipEndRecord): List<ZipEntry> {
|
||||||
|
filePointer.seek(endRecord.centralDirectoryStartOffset.toLong())
|
||||||
|
|
||||||
|
val numberOfEntries = endRecord.diskEntries.toInt()
|
||||||
|
|
||||||
|
return buildList(numberOfEntries) {
|
||||||
|
for (i in 1..numberOfEntries) {
|
||||||
|
add(
|
||||||
|
ZipEntry.fromCDE(filePointer).also
|
||||||
|
{
|
||||||
|
//for some reason the local extra field can be different from the central one
|
||||||
|
it.readLocalExtra(
|
||||||
|
filePointer.channel.map(
|
||||||
|
FileChannel.MapMode.READ_ONLY,
|
||||||
|
it.localHeaderOffset.toLong() + 28,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeCD() {
|
||||||
|
val CDStart = filePointer.channel.position().toUInt()
|
||||||
|
|
||||||
|
entries.forEach {
|
||||||
|
filePointer.channel.write(it.toCDE())
|
||||||
|
}
|
||||||
|
|
||||||
|
val entriesCount = entries.size.toUShort()
|
||||||
|
|
||||||
|
val endRecord = ZipEndRecord(
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
entriesCount,
|
||||||
|
entriesCount,
|
||||||
|
filePointer.channel.position().toUInt() - CDStart,
|
||||||
|
CDStart,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
|
filePointer.channel.write(endRecord.toECD())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addEntry(entry: ZipEntry, data: ByteBuffer) {
|
||||||
|
CDNeedsRewrite = true
|
||||||
|
|
||||||
|
entry.localHeaderOffset = filePointer.channel.position().toUInt()
|
||||||
|
|
||||||
|
filePointer.channel.write(entry.toLFH())
|
||||||
|
filePointer.channel.write(data)
|
||||||
|
|
||||||
|
entries.add(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
|
||||||
|
val compressor = Deflater(compressionLevel, true)
|
||||||
|
compressor.setInput(data)
|
||||||
|
compressor.finish()
|
||||||
|
|
||||||
|
val uncompressedSize = data.size
|
||||||
|
val compressedData =
|
||||||
|
ByteArray(uncompressedSize) //i'm guessing compression won't make the data bigger
|
||||||
|
|
||||||
|
val compressedDataLength = compressor.deflate(compressedData)
|
||||||
|
val compressedBuffer =
|
||||||
|
ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray())
|
||||||
|
|
||||||
|
compressor.end()
|
||||||
|
|
||||||
|
val crc = CRC32()
|
||||||
|
crc.update(data)
|
||||||
|
|
||||||
|
entry.compression = 8u //deflate compression
|
||||||
|
entry.uncompressedSize = uncompressedSize.toUInt()
|
||||||
|
entry.compressedSize = compressedDataLength.toUInt()
|
||||||
|
entry.crc32 = crc.value.toUInt()
|
||||||
|
|
||||||
|
addEntry(entry, compressedBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) {
|
||||||
|
alignment?.let {
|
||||||
|
//calculate where data would end up
|
||||||
|
val dataOffset = filePointer.filePointer + entry.LFHSize
|
||||||
|
|
||||||
|
val mod = dataOffset % alignment
|
||||||
|
|
||||||
|
//wrong alignment
|
||||||
|
if (mod != 0L) {
|
||||||
|
//add padding at end of extra field
|
||||||
|
entry.localExtraField =
|
||||||
|
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addEntry(entry, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDataForEntry(entry: ZipEntry): ByteBuffer {
|
||||||
|
return filePointer.channel.map(
|
||||||
|
FileChannel.MapMode.READ_ONLY,
|
||||||
|
entry.dataOffset.toLong(),
|
||||||
|
entry.compressedSize.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) {
|
||||||
|
for (entry in file.entries) {
|
||||||
|
if (entries.any { it.fileName == entry.fileName }) continue //don't add duplicates
|
||||||
|
|
||||||
|
val data = file.getDataForEntry(entry)
|
||||||
|
addEntryCopyData(entry, data, entryAlignment(entry))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
if (CDNeedsRewrite) writeCD()
|
||||||
|
filePointer.close()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package app.revanced.manager.flutter.utils.zip.structures
|
||||||
|
|
||||||
|
import app.revanced.manager.flutter.utils.zip.putUInt
|
||||||
|
import app.revanced.manager.flutter.utils.zip.putUShort
|
||||||
|
import app.revanced.manager.flutter.utils.zip.readUIntLE
|
||||||
|
import app.revanced.manager.flutter.utils.zip.readUShortLE
|
||||||
|
import java.io.DataInput
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
|
data class ZipEndRecord(
|
||||||
|
val diskNumber: UShort,
|
||||||
|
val startingDiskNumber: UShort,
|
||||||
|
val diskEntries: UShort,
|
||||||
|
val totalEntries: UShort,
|
||||||
|
val centralDirectorySize: UInt,
|
||||||
|
val centralDirectoryStartOffset: UInt,
|
||||||
|
val fileComment: String,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ECD_HEADER_SIZE = 22
|
||||||
|
const val ECD_SIGNATURE = 0x06054b50u
|
||||||
|
|
||||||
|
fun fromECD(input: DataInput): ZipEndRecord {
|
||||||
|
val signature = input.readUIntLE()
|
||||||
|
|
||||||
|
if (signature != ECD_SIGNATURE)
|
||||||
|
throw IllegalArgumentException("Input doesn't start with end record signature")
|
||||||
|
|
||||||
|
val diskNumber = input.readUShortLE()
|
||||||
|
val startingDiskNumber = input.readUShortLE()
|
||||||
|
val diskEntries = input.readUShortLE()
|
||||||
|
val totalEntries = input.readUShortLE()
|
||||||
|
val centralDirectorySize = input.readUIntLE()
|
||||||
|
val centralDirectoryStartOffset = input.readUIntLE()
|
||||||
|
val fileCommentLength = input.readUShortLE()
|
||||||
|
var fileComment = ""
|
||||||
|
|
||||||
|
if (fileCommentLength > 0u) {
|
||||||
|
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
|
||||||
|
input.readFully(fileCommentBytes)
|
||||||
|
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ZipEndRecord(
|
||||||
|
diskNumber,
|
||||||
|
startingDiskNumber,
|
||||||
|
diskEntries,
|
||||||
|
totalEntries,
|
||||||
|
centralDirectorySize,
|
||||||
|
centralDirectoryStartOffset,
|
||||||
|
fileComment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toECD(): ByteBuffer {
|
||||||
|
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
||||||
|
|
||||||
|
val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size)
|
||||||
|
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
||||||
|
|
||||||
|
buffer.putUInt(ECD_SIGNATURE)
|
||||||
|
buffer.putUShort(diskNumber)
|
||||||
|
buffer.putUShort(startingDiskNumber)
|
||||||
|
buffer.putUShort(diskEntries)
|
||||||
|
buffer.putUShort(totalEntries)
|
||||||
|
buffer.putUInt(centralDirectorySize)
|
||||||
|
buffer.putUInt(centralDirectoryStartOffset)
|
||||||
|
buffer.putUShort(commentBytes.size.toUShort())
|
||||||
|
|
||||||
|
buffer.put(commentBytes)
|
||||||
|
|
||||||
|
buffer.flip()
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,190 @@
|
|||||||
|
package app.revanced.manager.flutter.utils.zip.structures
|
||||||
|
|
||||||
|
import app.revanced.manager.flutter.utils.zip.*
|
||||||
|
import java.io.DataInput
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
|
data class ZipEntry(
|
||||||
|
val version: UShort,
|
||||||
|
val versionNeeded: UShort,
|
||||||
|
val flags: UShort,
|
||||||
|
var compression: UShort,
|
||||||
|
val modificationTime: UShort,
|
||||||
|
val modificationDate: UShort,
|
||||||
|
var crc32: UInt,
|
||||||
|
var compressedSize: UInt,
|
||||||
|
var uncompressedSize: UInt,
|
||||||
|
val diskNumber: UShort,
|
||||||
|
val internalAttributes: UShort,
|
||||||
|
val externalAttributes: UInt,
|
||||||
|
var localHeaderOffset: UInt,
|
||||||
|
val fileName: String,
|
||||||
|
val extraField: ByteArray,
|
||||||
|
val fileComment: String,
|
||||||
|
var localExtraField: ByteArray = ByteArray(0), //separate for alignment
|
||||||
|
) {
|
||||||
|
val LFHSize: Int
|
||||||
|
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
||||||
|
|
||||||
|
val dataOffset: UInt
|
||||||
|
get() = localHeaderOffset + LFHSize.toUInt()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CDE_HEADER_SIZE = 46
|
||||||
|
const val CDE_SIGNATURE = 0x02014b50u
|
||||||
|
|
||||||
|
const val LFH_HEADER_SIZE = 30
|
||||||
|
const val LFH_SIGNATURE = 0x04034b50u
|
||||||
|
|
||||||
|
fun createWithName(fileName: String): ZipEntry {
|
||||||
|
return ZipEntry(
|
||||||
|
0x1403u, //made by unix, version 20
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0x0821u, //seems to be static time google uses, no idea
|
||||||
|
0x0221u, //same as above
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
fileName,
|
||||||
|
ByteArray(0),
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromCDE(input: DataInput): ZipEntry {
|
||||||
|
val signature = input.readUIntLE()
|
||||||
|
|
||||||
|
if (signature != CDE_SIGNATURE)
|
||||||
|
throw IllegalArgumentException("Input doesn't start with central directory entry signature")
|
||||||
|
|
||||||
|
val version = input.readUShortLE()
|
||||||
|
val versionNeeded = input.readUShortLE()
|
||||||
|
var flags = input.readUShortLE()
|
||||||
|
val compression = input.readUShortLE()
|
||||||
|
val modificationTime = input.readUShortLE()
|
||||||
|
val modificationDate = input.readUShortLE()
|
||||||
|
val crc32 = input.readUIntLE()
|
||||||
|
val compressedSize = input.readUIntLE()
|
||||||
|
val uncompressedSize = input.readUIntLE()
|
||||||
|
val fileNameLength = input.readUShortLE()
|
||||||
|
var fileName = ""
|
||||||
|
val extraFieldLength = input.readUShortLE()
|
||||||
|
val extraField = ByteArray(extraFieldLength.toInt())
|
||||||
|
val fileCommentLength = input.readUShortLE()
|
||||||
|
var fileComment = ""
|
||||||
|
val diskNumber = input.readUShortLE()
|
||||||
|
val internalAttributes = input.readUShortLE()
|
||||||
|
val externalAttributes = input.readUIntLE()
|
||||||
|
val localHeaderOffset = input.readUIntLE()
|
||||||
|
|
||||||
|
val variableFieldsLength =
|
||||||
|
fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
|
||||||
|
|
||||||
|
if (variableFieldsLength > 0) {
|
||||||
|
val fileNameBytes = ByteArray(fileNameLength.toInt())
|
||||||
|
input.readFully(fileNameBytes)
|
||||||
|
fileName = fileNameBytes.toString(Charsets.UTF_8)
|
||||||
|
|
||||||
|
input.readFully(extraField)
|
||||||
|
|
||||||
|
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
|
||||||
|
input.readFully(fileCommentBytes)
|
||||||
|
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
flags = (flags and 0b1000u.inv()
|
||||||
|
.toUShort()) //disable data descriptor flag as they are not used
|
||||||
|
|
||||||
|
return ZipEntry(
|
||||||
|
version,
|
||||||
|
versionNeeded,
|
||||||
|
flags,
|
||||||
|
compression,
|
||||||
|
modificationTime,
|
||||||
|
modificationDate,
|
||||||
|
crc32,
|
||||||
|
compressedSize,
|
||||||
|
uncompressedSize,
|
||||||
|
diskNumber,
|
||||||
|
internalAttributes,
|
||||||
|
externalAttributes,
|
||||||
|
localHeaderOffset,
|
||||||
|
fileName,
|
||||||
|
extraField,
|
||||||
|
fileComment,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readLocalExtra(buffer: ByteBuffer) {
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
localExtraField = ByteArray(buffer.getUShort().toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toLFH(): ByteBuffer {
|
||||||
|
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
||||||
|
|
||||||
|
val buffer = ByteBuffer.allocate(LFH_HEADER_SIZE + nameBytes.size + localExtraField.size)
|
||||||
|
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
||||||
|
|
||||||
|
buffer.putUInt(LFH_SIGNATURE)
|
||||||
|
buffer.putUShort(versionNeeded)
|
||||||
|
buffer.putUShort(flags)
|
||||||
|
buffer.putUShort(compression)
|
||||||
|
buffer.putUShort(modificationTime)
|
||||||
|
buffer.putUShort(modificationDate)
|
||||||
|
buffer.putUInt(crc32)
|
||||||
|
buffer.putUInt(compressedSize)
|
||||||
|
buffer.putUInt(uncompressedSize)
|
||||||
|
buffer.putUShort(nameBytes.size.toUShort())
|
||||||
|
buffer.putUShort(localExtraField.size.toUShort())
|
||||||
|
|
||||||
|
buffer.put(nameBytes)
|
||||||
|
buffer.put(localExtraField)
|
||||||
|
|
||||||
|
buffer.flip()
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toCDE(): ByteBuffer {
|
||||||
|
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
||||||
|
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
||||||
|
|
||||||
|
val buffer =
|
||||||
|
ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size)
|
||||||
|
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
||||||
|
|
||||||
|
buffer.putUInt(CDE_SIGNATURE)
|
||||||
|
buffer.putUShort(version)
|
||||||
|
buffer.putUShort(versionNeeded)
|
||||||
|
buffer.putUShort(flags)
|
||||||
|
buffer.putUShort(compression)
|
||||||
|
buffer.putUShort(modificationTime)
|
||||||
|
buffer.putUShort(modificationDate)
|
||||||
|
buffer.putUInt(crc32)
|
||||||
|
buffer.putUInt(compressedSize)
|
||||||
|
buffer.putUInt(uncompressedSize)
|
||||||
|
buffer.putUShort(nameBytes.size.toUShort())
|
||||||
|
buffer.putUShort(extraField.size.toUShort())
|
||||||
|
buffer.putUShort(commentBytes.size.toUShort())
|
||||||
|
buffer.putUShort(diskNumber)
|
||||||
|
buffer.putUShort(internalAttributes)
|
||||||
|
buffer.putUInt(externalAttributes)
|
||||||
|
buffer.putUInt(localHeaderOffset)
|
||||||
|
|
||||||
|
buffer.put(nameBytes)
|
||||||
|
buffer.put(extraField)
|
||||||
|
buffer.put(commentBytes)
|
||||||
|
|
||||||
|
buffer.flip()
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
19
android/app/src/main/res/values-night-v31/styles.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_launcher_round</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
18
android/app/src/main/res/values-night/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
20
android/app/src/main/res/values-v31/styles.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_launcher_round</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#1B1B1B</color>
|
||||||
|
</resources>
|
18
android/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
4
android/app/src/main/res/xml/file_paths.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<cache-path name="cache" path="." />
|
||||||
|
</paths>
|
4
android/app/src/profile/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="app.revanced.manager.flutter">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
37
android/build.gradle
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.9.0'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:7.1.3'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
||||||
|
credentials {
|
||||||
|
username = (project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR")) as String
|
||||||
|
password = (project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")) as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mavenLocal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = '../build'
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
project.evaluationDependsOn(':app')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("clean", Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
6
android/gradle.properties
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx1536M -XX:+UseParallelGC
|
||||||
|
org.gradle.parallel=true
|
||||||
|
org.gradle.daemon=true
|
||||||
|
org.gradle.caching=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
@ -1,8 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=61ad310d3c7d3e5da131b76bbf22b5a4c0786e9d892dae8c1658d4b484de3caa
|
distributionSha256Sum=a01b6587e15fe7ed120a0ee299c25982a1eee045abd6a9dd5e216b2f628ef9ac
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
11
android/settings.gradle
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
include ':app'
|
||||||
|
|
||||||
|
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||||
|
def properties = new Properties()
|
||||||
|
|
||||||
|
assert localPropertiesFile.exists()
|
||||||
|
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||||
|
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
1
app/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/build
|
|
@ -1,222 +0,0 @@
|
|||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
alias(libs.plugins.android.application)
|
|
||||||
alias(libs.plugins.kotlin.android)
|
|
||||||
alias(libs.plugins.kotlin.serialization)
|
|
||||||
alias(libs.plugins.kotlin.parcelize)
|
|
||||||
alias(libs.plugins.compose.compiler)
|
|
||||||
alias(libs.plugins.devtools)
|
|
||||||
alias(libs.plugins.about.libraries)
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "app.revanced.manager"
|
|
||||||
compileSdk = 35
|
|
||||||
buildToolsVersion = "35.0.1"
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId = "app.revanced.manager"
|
|
||||||
minSdk = 26
|
|
||||||
targetSdk = 35
|
|
||||||
versionCode = 1
|
|
||||||
versionName = "0.0.1"
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
debug {
|
|
||||||
applicationIdSuffix = ".debug"
|
|
||||||
resValue("string", "app_name", "ReVanced Manager (Debug)")
|
|
||||||
isPseudoLocalesEnabled = true
|
|
||||||
|
|
||||||
buildConfigField("long", "BUILD_ID", "${Random.nextLong()}L")
|
|
||||||
}
|
|
||||||
|
|
||||||
release {
|
|
||||||
if (!project.hasProperty("noProguard")) {
|
|
||||||
isMinifyEnabled = true
|
|
||||||
isShrinkResources = true
|
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
|
||||||
}
|
|
||||||
|
|
||||||
val keystoreFile = file("keystore.jks")
|
|
||||||
|
|
||||||
if (project.hasProperty("signAsDebug") || !keystoreFile.exists()) {
|
|
||||||
applicationIdSuffix = ".debug_signed"
|
|
||||||
resValue("string", "app_name", "ReVanced Manager (Debug signed)")
|
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
|
||||||
|
|
||||||
isPseudoLocalesEnabled = true
|
|
||||||
} else {
|
|
||||||
signingConfig = signingConfigs.create("release") {
|
|
||||||
storeFile = keystoreFile
|
|
||||||
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
|
||||||
keyAlias = System.getenv("KEYSTORE_ENTRY_ALIAS")
|
|
||||||
keyPassword = System.getenv("KEYSTORE_ENTRY_PASSWORD")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildConfigField("long", "BUILD_ID", "0L")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
|
|
||||||
dependenciesInfo {
|
|
||||||
includeInApk = false
|
|
||||||
includeInBundle = false
|
|
||||||
}
|
|
||||||
|
|
||||||
packaging {
|
|
||||||
resources.excludes.addAll(
|
|
||||||
listOf(
|
|
||||||
"/prebuilt/**",
|
|
||||||
"META-INF/DEPENDENCIES",
|
|
||||||
"META-INF/**.version",
|
|
||||||
"DebugProbesKt.bin",
|
|
||||||
"kotlin-tooling-metadata.json",
|
|
||||||
"org/bouncycastle/pqc/**.properties",
|
|
||||||
"org/bouncycastle/x509/**.properties",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
jniLibs {
|
|
||||||
useLegacyPackaging = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ksp {
|
|
||||||
arg("room.schemaLocation", "$projectDir/schemas")
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "17"
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFeatures {
|
|
||||||
compose = true
|
|
||||||
aidl = true
|
|
||||||
buildConfig = true
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
androidResources {
|
|
||||||
generateLocaleConfig = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
externalNativeBuild {
|
|
||||||
cmake {
|
|
||||||
path = file("src/main/cpp/CMakeLists.txt")
|
|
||||||
version = "3.22.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
jvmToolchain(17)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
// AndroidX Core
|
|
||||||
implementation(libs.androidx.ktx)
|
|
||||||
implementation(libs.runtime.ktx)
|
|
||||||
implementation(libs.runtime.compose)
|
|
||||||
implementation(libs.splash.screen)
|
|
||||||
implementation(libs.activity.compose)
|
|
||||||
implementation(libs.work.runtime.ktx)
|
|
||||||
implementation(libs.preferences.datastore)
|
|
||||||
implementation(libs.appcompat)
|
|
||||||
|
|
||||||
// Compose
|
|
||||||
implementation(platform(libs.compose.bom))
|
|
||||||
implementation(libs.compose.ui)
|
|
||||||
implementation(libs.compose.ui.preview)
|
|
||||||
implementation(libs.compose.ui.tooling)
|
|
||||||
implementation(libs.compose.livedata)
|
|
||||||
implementation(libs.compose.material.icons.extended)
|
|
||||||
implementation(libs.compose.material3)
|
|
||||||
implementation(libs.navigation.compose)
|
|
||||||
|
|
||||||
// Accompanist
|
|
||||||
implementation(libs.accompanist.drawablepainter)
|
|
||||||
|
|
||||||
// Placeholder
|
|
||||||
implementation(libs.placeholder.material3)
|
|
||||||
|
|
||||||
// HTML Scraper
|
|
||||||
implementation(libs.skrapeit.dsl)
|
|
||||||
implementation(libs.skrapeit.parser)
|
|
||||||
|
|
||||||
// Coil (async image loading, network image)
|
|
||||||
implementation(libs.coil.compose)
|
|
||||||
implementation(libs.coil.appiconloader)
|
|
||||||
|
|
||||||
// KotlinX
|
|
||||||
implementation(libs.kotlinx.serialization.json)
|
|
||||||
implementation(libs.kotlinx.collection.immutable)
|
|
||||||
implementation(libs.kotlinx.datetime)
|
|
||||||
|
|
||||||
// Room
|
|
||||||
implementation(libs.room.runtime)
|
|
||||||
implementation(libs.room.ktx)
|
|
||||||
annotationProcessor(libs.room.compiler)
|
|
||||||
ksp(libs.room.compiler)
|
|
||||||
|
|
||||||
// ReVanced
|
|
||||||
implementation(libs.revanced.patcher)
|
|
||||||
implementation(libs.revanced.library)
|
|
||||||
|
|
||||||
// Downloader plugins
|
|
||||||
implementation(libs.plugin.api)
|
|
||||||
|
|
||||||
// Native processes
|
|
||||||
implementation(libs.kotlin.process)
|
|
||||||
|
|
||||||
// HiddenAPI
|
|
||||||
compileOnly(libs.hidden.api.stub)
|
|
||||||
|
|
||||||
// LibSU
|
|
||||||
implementation(libs.libsu.core)
|
|
||||||
implementation(libs.libsu.service)
|
|
||||||
implementation(libs.libsu.nio)
|
|
||||||
|
|
||||||
// Koin
|
|
||||||
implementation(libs.koin.android)
|
|
||||||
implementation(libs.koin.compose)
|
|
||||||
implementation(libs.koin.compose.navigation)
|
|
||||||
implementation(libs.koin.workmanager)
|
|
||||||
|
|
||||||
// Licenses
|
|
||||||
implementation(libs.about.libraries)
|
|
||||||
|
|
||||||
// Ktor
|
|
||||||
implementation(libs.ktor.core)
|
|
||||||
implementation(libs.ktor.logging)
|
|
||||||
implementation(libs.ktor.okhttp)
|
|
||||||
implementation(libs.ktor.content.negotiation)
|
|
||||||
implementation(libs.ktor.serialization)
|
|
||||||
|
|
||||||
// Markdown
|
|
||||||
implementation(libs.markdown.renderer)
|
|
||||||
|
|
||||||
// Fading Edges
|
|
||||||
implementation(libs.fading.edges)
|
|
||||||
|
|
||||||
// Scrollbars
|
|
||||||
implementation(libs.scrollbars)
|
|
||||||
|
|
||||||
// EnumUtil
|
|
||||||
implementation(libs.enumutil)
|
|
||||||
ksp(libs.enumutil.ksp)
|
|
||||||
|
|
||||||
// Reorderable lists
|
|
||||||
implementation(libs.reorderable)
|
|
||||||
|
|
||||||
// Compose Icons
|
|
||||||
implementation(libs.compose.icons.fontawesome)
|
|
||||||
}
|
|
63
app/proguard-rules.pro
vendored
@ -1,63 +0,0 @@
|
|||||||
# Add project specific ProGuard rules here.
|
|
||||||
# You can control the set of applied configuration files using the
|
|
||||||
# proguardFiles setting in build.gradle.kts.kts.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
-dontobfuscate
|
|
||||||
|
|
||||||
# Required for serialization to work properly
|
|
||||||
-if @kotlinx.serialization.Serializable class **
|
|
||||||
-keepclassmembers class <1> {
|
|
||||||
static <1>$Companion Companion;
|
|
||||||
}
|
|
||||||
-if @kotlinx.serialization.Serializable class ** {
|
|
||||||
static **$* *;
|
|
||||||
}
|
|
||||||
-keepclassmembers class <2>$<3> {
|
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
|
||||||
}
|
|
||||||
-if @kotlinx.serialization.Serializable class ** {
|
|
||||||
public static ** INSTANCE;
|
|
||||||
}
|
|
||||||
-keepclassmembers class <1> {
|
|
||||||
public static <1> INSTANCE;
|
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
# This required for the process runtime.
|
|
||||||
-keep class app.revanced.manager.patcher.runtime.process.* {
|
|
||||||
*;
|
|
||||||
}
|
|
||||||
# Required for the patcher to function correctly
|
|
||||||
-keep class app.revanced.patcher.** {
|
|
||||||
*;
|
|
||||||
}
|
|
||||||
-keep class brut.** {
|
|
||||||
*;
|
|
||||||
}
|
|
||||||
-keep class org.xmlpull.** {
|
|
||||||
*;
|
|
||||||
}
|
|
||||||
-keep class kotlin.** {
|
|
||||||
*;
|
|
||||||
}
|
|
||||||
-keep class org.jf.** {
|
|
||||||
*;
|
|
||||||
}
|
|
||||||
-keep class com.android.** {
|
|
||||||
*;
|
|
||||||
}
|
|
||||||
-keep class app.revanced.manager.plugin.** {
|
|
||||||
*;
|
|
||||||
}
|
|
||||||
|
|
||||||
-dontwarn com.google.auto.value.**
|
|
||||||
-dontwarn java.awt.**
|
|
||||||
-dontwarn javax.**
|
|
||||||
-dontwarn org.slf4j.**
|
|
||||||
-dontwarn it.skrape.fetcher.*
|
|
||||||
-dontwarn com.google.j2objc.annotations.*
|
|
||||||
|
|
||||||
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
|
|
@ -1,429 +0,0 @@
|
|||||||
{
|
|
||||||
"formatVersion": 1,
|
|
||||||
"database": {
|
|
||||||
"version": 1,
|
|
||||||
"identityHash": "d0119047505da435972c5247181de675",
|
|
||||||
"entities": [
|
|
||||||
{
|
|
||||||
"tableName": "patch_bundles",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `name` TEXT NOT NULL, `version` TEXT, `source` TEXT NOT NULL, `auto_update` INTEGER NOT NULL, PRIMARY KEY(`uid`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "uid",
|
|
||||||
"columnName": "uid",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "name",
|
|
||||||
"columnName": "name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "version",
|
|
||||||
"columnName": "version",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "source",
|
|
||||||
"columnName": "source",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "autoUpdate",
|
|
||||||
"columnName": "auto_update",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"uid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "patch_selections",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `patch_bundle` INTEGER NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`uid`), FOREIGN KEY(`patch_bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "uid",
|
|
||||||
"columnName": "uid",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "patchBundle",
|
|
||||||
"columnName": "patch_bundle",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "packageName",
|
|
||||||
"columnName": "package_name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"uid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [
|
|
||||||
{
|
|
||||||
"name": "index_patch_selections_patch_bundle_package_name",
|
|
||||||
"unique": true,
|
|
||||||
"columnNames": [
|
|
||||||
"patch_bundle",
|
|
||||||
"package_name"
|
|
||||||
],
|
|
||||||
"orders": [],
|
|
||||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_patch_selections_patch_bundle_package_name` ON `${TABLE_NAME}` (`patch_bundle`, `package_name`)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"foreignKeys": [
|
|
||||||
{
|
|
||||||
"table": "patch_bundles",
|
|
||||||
"onDelete": "CASCADE",
|
|
||||||
"onUpdate": "NO ACTION",
|
|
||||||
"columns": [
|
|
||||||
"patch_bundle"
|
|
||||||
],
|
|
||||||
"referencedColumns": [
|
|
||||||
"uid"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "selected_patches",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`selection` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, PRIMARY KEY(`selection`, `patch_name`), FOREIGN KEY(`selection`) REFERENCES `patch_selections`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "selection",
|
|
||||||
"columnName": "selection",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "patchName",
|
|
||||||
"columnName": "patch_name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"selection",
|
|
||||||
"patch_name"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": [
|
|
||||||
{
|
|
||||||
"table": "patch_selections",
|
|
||||||
"onDelete": "CASCADE",
|
|
||||||
"onUpdate": "NO ACTION",
|
|
||||||
"columns": [
|
|
||||||
"selection"
|
|
||||||
],
|
|
||||||
"referencedColumns": [
|
|
||||||
"uid"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "downloaded_app",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `version` TEXT NOT NULL, `directory` TEXT NOT NULL, `last_used` INTEGER NOT NULL, PRIMARY KEY(`package_name`, `version`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "packageName",
|
|
||||||
"columnName": "package_name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "version",
|
|
||||||
"columnName": "version",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "directory",
|
|
||||||
"columnName": "directory",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "lastUsed",
|
|
||||||
"columnName": "last_used",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"package_name",
|
|
||||||
"version"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "installed_app",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`current_package_name` TEXT NOT NULL, `original_package_name` TEXT NOT NULL, `version` TEXT NOT NULL, `install_type` TEXT NOT NULL, PRIMARY KEY(`current_package_name`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "currentPackageName",
|
|
||||||
"columnName": "current_package_name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "originalPackageName",
|
|
||||||
"columnName": "original_package_name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "version",
|
|
||||||
"columnName": "version",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "installType",
|
|
||||||
"columnName": "install_type",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"current_package_name"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "applied_patch",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `bundle` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, PRIMARY KEY(`package_name`, `bundle`, `patch_name`), FOREIGN KEY(`package_name`) REFERENCES `installed_app`(`current_package_name`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "packageName",
|
|
||||||
"columnName": "package_name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "bundle",
|
|
||||||
"columnName": "bundle",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "patchName",
|
|
||||||
"columnName": "patch_name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"package_name",
|
|
||||||
"bundle",
|
|
||||||
"patch_name"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [
|
|
||||||
{
|
|
||||||
"name": "index_applied_patch_bundle",
|
|
||||||
"unique": false,
|
|
||||||
"columnNames": [
|
|
||||||
"bundle"
|
|
||||||
],
|
|
||||||
"orders": [],
|
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_applied_patch_bundle` ON `${TABLE_NAME}` (`bundle`)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"foreignKeys": [
|
|
||||||
{
|
|
||||||
"table": "installed_app",
|
|
||||||
"onDelete": "CASCADE",
|
|
||||||
"onUpdate": "NO ACTION",
|
|
||||||
"columns": [
|
|
||||||
"package_name"
|
|
||||||
],
|
|
||||||
"referencedColumns": [
|
|
||||||
"current_package_name"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"table": "patch_bundles",
|
|
||||||
"onDelete": "CASCADE",
|
|
||||||
"onUpdate": "NO ACTION",
|
|
||||||
"columns": [
|
|
||||||
"bundle"
|
|
||||||
],
|
|
||||||
"referencedColumns": [
|
|
||||||
"uid"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "option_groups",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `patch_bundle` INTEGER NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`uid`), FOREIGN KEY(`patch_bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "uid",
|
|
||||||
"columnName": "uid",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "patchBundle",
|
|
||||||
"columnName": "patch_bundle",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "packageName",
|
|
||||||
"columnName": "package_name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"uid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [
|
|
||||||
{
|
|
||||||
"name": "index_option_groups_patch_bundle_package_name",
|
|
||||||
"unique": true,
|
|
||||||
"columnNames": [
|
|
||||||
"patch_bundle",
|
|
||||||
"package_name"
|
|
||||||
],
|
|
||||||
"orders": [],
|
|
||||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_option_groups_patch_bundle_package_name` ON `${TABLE_NAME}` (`patch_bundle`, `package_name`)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"foreignKeys": [
|
|
||||||
{
|
|
||||||
"table": "patch_bundles",
|
|
||||||
"onDelete": "CASCADE",
|
|
||||||
"onUpdate": "NO ACTION",
|
|
||||||
"columns": [
|
|
||||||
"patch_bundle"
|
|
||||||
],
|
|
||||||
"referencedColumns": [
|
|
||||||
"uid"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "options",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, `key` TEXT NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`group`, `patch_name`, `key`), FOREIGN KEY(`group`) REFERENCES `option_groups`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "group",
|
|
||||||
"columnName": "group",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "patchName",
|
|
||||||
"columnName": "patch_name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "key",
|
|
||||||
"columnName": "key",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "value",
|
|
||||||
"columnName": "value",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"group",
|
|
||||||
"patch_name",
|
|
||||||
"key"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": [
|
|
||||||
{
|
|
||||||
"table": "option_groups",
|
|
||||||
"onDelete": "CASCADE",
|
|
||||||
"onUpdate": "NO ACTION",
|
|
||||||
"columns": [
|
|
||||||
"group"
|
|
||||||
],
|
|
||||||
"referencedColumns": [
|
|
||||||
"uid"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "trusted_downloader_plugins",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `signature` BLOB NOT NULL, PRIMARY KEY(`package_name`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "packageName",
|
|
||||||
"columnName": "package_name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "signature",
|
|
||||||
"columnName": "signature",
|
|
||||||
"affinity": "BLOB",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"package_name"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [],
|
|
||||||
"setupQueries": [
|
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd0119047505da435972c5247181de675')"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<permission
|
|
||||||
android:name="app.revanced.manager.permission.PLUGIN_HOST"
|
|
||||||
android:protectionLevel="signature"
|
|
||||||
android:label="@string/plugin_host_permission_label"
|
|
||||||
android:description="@string/plugin_host_permission_description"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<uses-permission android:name="app.revanced.manager.permission.PLUGIN_HOST" />
|
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
|
||||||
tools:ignore="QueryAllPackagesPermission" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
|
||||||
tools:ignore="ScopedStorage" />
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:name=".ManagerApplication"
|
|
||||||
android:allowBackup="true"
|
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
|
||||||
android:largeHeap="true"
|
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:theme="@style/Theme.ReVancedManager"
|
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
tools:targetApi="34">
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".MainActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:theme="@style/Theme.ReVancedManager">
|
|
||||||
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name=".plugin.downloader.webview.WebViewActivity" android:exported="false" android:theme="@style/Theme.WebViewActivity" />
|
|
||||||
|
|
||||||
<service android:name=".service.InstallService" />
|
|
||||||
<service android:name=".service.UninstallService" />
|
|
||||||
|
|
||||||
<service
|
|
||||||
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
|
||||||
android:foregroundServiceType="specialUse"
|
|
||||||
android:exported="false"
|
|
||||||
tools:node="merge">
|
|
||||||
<property
|
|
||||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
|
||||||
android:value="patching"
|
|
||||||
/>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="androidx.startup.InitializationProvider"
|
|
||||||
android:authorities="${applicationId}.androidx-startup"
|
|
||||||
android:exported="false"
|
|
||||||
tools:node="merge">
|
|
||||||
<meta-data
|
|
||||||
android:name="androidx.work.WorkManagerInitializer"
|
|
||||||
android:value="androidx.startup"
|
|
||||||
tools:node="remove" />
|
|
||||||
</provider>
|
|
||||||
</application>
|
|
||||||
</manifest>
|
|
@ -1,8 +0,0 @@
|
|||||||
// IRootService.aidl
|
|
||||||
package app.revanced.manager;
|
|
||||||
|
|
||||||
// Declare any non-default types here with import statements
|
|
||||||
|
|
||||||
interface IRootSystemService {
|
|
||||||
IBinder getFileSystemService();
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
// IPatcherEvents.aidl
|
|
||||||
package app.revanced.manager.patcher.runtime.process;
|
|
||||||
|
|
||||||
// Interface for sending events back to the main app process.
|
|
||||||
oneway interface IPatcherEvents {
|
|
||||||
void log(String level, String msg);
|
|
||||||
void patchSucceeded();
|
|
||||||
void progress(String name, String state, String msg);
|
|
||||||
// The patching process has ended. The exceptionStackTrace is null if it finished successfully.
|
|
||||||
void finished(String exceptionStackTrace);
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
// IPatcherProcess.aidl
|
|
||||||
package app.revanced.manager.patcher.runtime.process;
|
|
||||||
|
|
||||||
import app.revanced.manager.patcher.runtime.process.Parameters;
|
|
||||||
import app.revanced.manager.patcher.runtime.process.IPatcherEvents;
|
|
||||||
|
|
||||||
interface IPatcherProcess {
|
|
||||||
// Returns BuildConfig.BUILD_ID, which is used to ensure the main app and runner process are running the same code.
|
|
||||||
long buildId();
|
|
||||||
// Makes the patcher process exit with code 0
|
|
||||||
oneway void exit();
|
|
||||||
// Starts patching.
|
|
||||||
oneway void start(in Parameters parameters, IPatcherEvents events);
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
// Parameters.aidl
|
|
||||||
package app.revanced.manager.patcher.runtime.process;
|
|
||||||
|
|
||||||
parcelable Parameters;
|
|
@ -1,6 +0,0 @@
|
|||||||
id=__PKG_NAME__-ReVanced
|
|
||||||
name=__LABEL__ ReVanced
|
|
||||||
version=__VERSION__
|
|
||||||
versionCode=0
|
|
||||||
author=ReVanced
|
|
||||||
description=Mounts the patched APK on top of the original one
|
|
@ -1,40 +0,0 @@
|
|||||||
#!/system/bin/sh
|
|
||||||
DIR=${0%/*}
|
|
||||||
|
|
||||||
package_name="__PKG_NAME__"
|
|
||||||
version="__VERSION__"
|
|
||||||
|
|
||||||
rm "$DIR/log"
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
until [ "$(getprop sys.boot_completed)" = 1 ]; do sleep 5; done
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
base_path="$DIR/$package_name.apk"
|
|
||||||
stock_path="$(pm path "$package_name" | grep base | sed 's/package://g')"
|
|
||||||
stock_version="$(dumpsys package "$package_name" | grep versionName | cut -d "=" -f2)"
|
|
||||||
|
|
||||||
echo "base_path: $base_path"
|
|
||||||
echo "stock_path: $stock_path"
|
|
||||||
echo "base_version: $version"
|
|
||||||
echo "stock_version: $stock_version"
|
|
||||||
|
|
||||||
if mount | grep -q "$stock_path" ; then
|
|
||||||
echo "Not mounting as stock path is already mounted"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$version" != "$stock_version" ]; then
|
|
||||||
echo "Not mounting as versions don't match"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$stock_path" ]; then
|
|
||||||
echo "Not mounting as app info could not be loaded"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
mount -o bind "$base_path" "$stock_path"
|
|
||||||
|
|
||||||
} >> "$DIR/log"
|
|
@ -1,38 +0,0 @@
|
|||||||
|
|
||||||
# For more information about using CMake with Android Studio, read the
|
|
||||||
# documentation: https://d.android.com/studio/projects/add-native-code.html.
|
|
||||||
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
|
|
||||||
|
|
||||||
# Sets the minimum CMake version required for this project.
|
|
||||||
cmake_minimum_required(VERSION 3.22.1)
|
|
||||||
|
|
||||||
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
|
|
||||||
# Since this is the top level CMakeLists.txt, the project name is also accessible
|
|
||||||
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
|
|
||||||
# build script scope).
|
|
||||||
project("prop_override")
|
|
||||||
|
|
||||||
# Creates and names a library, sets it as either STATIC
|
|
||||||
# or SHARED, and provides the relative paths to its source code.
|
|
||||||
# You can define multiple libraries, and CMake builds them for you.
|
|
||||||
# Gradle automatically packages shared libraries with your APK.
|
|
||||||
#
|
|
||||||
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
|
|
||||||
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
|
|
||||||
# is preferred for the same purpose.
|
|
||||||
#
|
|
||||||
# In order to load a library into your app from Java/Kotlin, you must call
|
|
||||||
# System.loadLibrary() and pass the name of the library defined here;
|
|
||||||
# for GameActivity/NativeActivity derived applications, the same library name must be
|
|
||||||
# used in the AndroidManifest.xml file.
|
|
||||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
|
||||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
|
||||||
prop_override.cpp)
|
|
||||||
|
|
||||||
# Specifies libraries CMake should link to your target library. You
|
|
||||||
# can link libraries from various origins, such as libraries defined in this
|
|
||||||
# build script, prebuilt third-party libraries, or Android system libraries.
|
|
||||||
target_link_libraries(${CMAKE_PROJECT_NAME}
|
|
||||||
# List libraries link to the target library
|
|
||||||
android
|
|
||||||
log)
|
|
@ -1,62 +0,0 @@
|
|||||||
// Library for overriding Android system properties via environment variables.
|
|
||||||
//
|
|
||||||
// Usage: LD_PRELOAD=prop_override.so PROP_dalvik.vm.heapsize=123M getprop dalvik.vm.heapsize
|
|
||||||
// Output: 123M
|
|
||||||
#include <string>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
// Source: https://android.googlesource.com/platform/system/core/+/100b08a848d018eeb1caa5d5e7c7c2aaac65da15/libcutils/include/cutils/properties.h
|
|
||||||
#define PROP_VALUE_MAX 92
|
|
||||||
// This is the mangled name of "android::base::GetProperty".
|
|
||||||
#define GET_PROPERTY_MANGLED_NAME "_ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_"
|
|
||||||
|
|
||||||
extern "C" typedef int (*property_get_ptr)(const char *, char *, const char *);
|
|
||||||
typedef std::string (*GetProperty_ptr)(const std::string &, const std::string &);
|
|
||||||
|
|
||||||
char *GetPropOverride(const std::string &key) {
|
|
||||||
auto envKey = "PROP_" + key;
|
|
||||||
|
|
||||||
return getenv(envKey.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// See: https://android.googlesource.com/platform/system/core/+/100b08a848d018eeb1caa5d5e7c7c2aaac65da15/libcutils/properties.cpp
|
|
||||||
extern "C" int property_get(const char *key, char *value, const char *default_value) {
|
|
||||||
auto replacement = GetPropOverride(std::string(key));
|
|
||||||
if (replacement) {
|
|
||||||
int len = strnlen(replacement, PROP_VALUE_MAX);
|
|
||||||
|
|
||||||
strncpy(value, replacement, len);
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static property_get_ptr original = NULL;
|
|
||||||
if (!original) {
|
|
||||||
// Get the address of the original function.
|
|
||||||
original = reinterpret_cast<property_get_ptr>(dlsym(RTLD_NEXT, "property_get"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return original(key, value, default_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defining android::base::GetProperty ourselves won't work because std::string has a slightly different "path" in the NDK version of the C++ standard library.
|
|
||||||
// We can get around this by forcing the function to adopt a specific name using the asm keyword.
|
|
||||||
std::string GetProperty(const std::string &, const std::string &) asm(GET_PROPERTY_MANGLED_NAME);
|
|
||||||
|
|
||||||
|
|
||||||
// See: https://android.googlesource.com/platform/system/libbase/+/1a34bb67c4f3ba0a1ea6f4f20ac9fe117ba4fe64/properties.cpp
|
|
||||||
// This isn't used for the properties we want to override, but property_get is deprecated so that could change in the future.
|
|
||||||
std::string GetProperty(const std::string &key, const std::string &default_value) {
|
|
||||||
auto replacement = GetPropOverride(key);
|
|
||||||
if (replacement) {
|
|
||||||
return std::string(replacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
static GetProperty_ptr original = NULL;
|
|
||||||
if (!original) {
|
|
||||||
original = reinterpret_cast<GetProperty_ptr>(dlsym(RTLD_NEXT, GET_PROPERTY_MANGLED_NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
return original(key, default_value);
|
|
||||||
}
|
|
@ -1,336 +0,0 @@
|
|||||||
package app.revanced.manager
|
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.animation.slideInHorizontally
|
|
||||||
import androidx.compose.animation.slideOutHorizontally
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.navigation.NavBackStackEntry
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.compose.NavHost
|
|
||||||
import androidx.navigation.compose.composable
|
|
||||||
import androidx.navigation.compose.navigation
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
|
||||||
import androidx.navigation.toRoute
|
|
||||||
import app.revanced.manager.ui.model.navigation.AppSelector
|
|
||||||
import app.revanced.manager.ui.model.navigation.ComplexParameter
|
|
||||||
import app.revanced.manager.ui.model.navigation.Dashboard
|
|
||||||
import app.revanced.manager.ui.model.navigation.InstalledApplicationInfo
|
|
||||||
import app.revanced.manager.ui.model.navigation.Patcher
|
|
||||||
import app.revanced.manager.ui.model.navigation.SelectedApplicationInfo
|
|
||||||
import app.revanced.manager.ui.model.navigation.Settings
|
|
||||||
import app.revanced.manager.ui.model.navigation.Update
|
|
||||||
import app.revanced.manager.ui.screen.AppSelectorScreen
|
|
||||||
import app.revanced.manager.ui.screen.DashboardScreen
|
|
||||||
import app.revanced.manager.ui.screen.InstalledAppInfoScreen
|
|
||||||
import app.revanced.manager.ui.screen.PatcherScreen
|
|
||||||
import app.revanced.manager.ui.screen.PatchesSelectorScreen
|
|
||||||
import app.revanced.manager.ui.screen.RequiredOptionsScreen
|
|
||||||
import app.revanced.manager.ui.screen.SelectedAppInfoScreen
|
|
||||||
import app.revanced.manager.ui.screen.SettingsScreen
|
|
||||||
import app.revanced.manager.ui.screen.UpdateScreen
|
|
||||||
import app.revanced.manager.ui.screen.settings.AboutSettingsScreen
|
|
||||||
import app.revanced.manager.ui.screen.settings.AdvancedSettingsScreen
|
|
||||||
import app.revanced.manager.ui.screen.settings.ContributorSettingsScreen
|
|
||||||
import app.revanced.manager.ui.screen.settings.DeveloperSettingsScreen
|
|
||||||
import app.revanced.manager.ui.screen.settings.DownloadsSettingsScreen
|
|
||||||
import app.revanced.manager.ui.screen.settings.GeneralSettingsScreen
|
|
||||||
import app.revanced.manager.ui.screen.settings.ImportExportSettingsScreen
|
|
||||||
import app.revanced.manager.ui.screen.settings.LicensesSettingsScreen
|
|
||||||
import app.revanced.manager.ui.screen.settings.update.ChangelogsSettingsScreen
|
|
||||||
import app.revanced.manager.ui.screen.settings.update.UpdatesSettingsScreen
|
|
||||||
import app.revanced.manager.ui.theme.ReVancedManagerTheme
|
|
||||||
import app.revanced.manager.ui.theme.Theme
|
|
||||||
import app.revanced.manager.ui.viewmodel.MainViewModel
|
|
||||||
import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
|
|
||||||
import app.revanced.manager.util.EventEffect
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.koin.androidx.compose.koinViewModel
|
|
||||||
import org.koin.androidx.compose.navigation.koinNavViewModel
|
|
||||||
import org.koin.core.parameter.parametersOf
|
|
||||||
import org.koin.androidx.viewmodel.ext.android.getViewModel as getActivityViewModel
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
||||||
enableEdgeToEdge()
|
|
||||||
installSplashScreen()
|
|
||||||
|
|
||||||
val vm: MainViewModel = getActivityViewModel()
|
|
||||||
|
|
||||||
setContent {
|
|
||||||
val launcher = rememberLauncherForActivityResult(
|
|
||||||
ActivityResultContracts.StartActivityForResult(),
|
|
||||||
onResult = vm::applyLegacySettings
|
|
||||||
)
|
|
||||||
val theme by vm.prefs.theme.getAsState()
|
|
||||||
val dynamicColor by vm.prefs.dynamicColor.getAsState()
|
|
||||||
|
|
||||||
EventEffect(vm.legacyImportActivityFlow) {
|
|
||||||
try {
|
|
||||||
launcher.launch(it)
|
|
||||||
} catch (_: ActivityNotFoundException) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ReVancedManagerTheme(
|
|
||||||
darkTheme = theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK,
|
|
||||||
dynamicColor = dynamicColor
|
|
||||||
) {
|
|
||||||
ReVancedManager(vm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ReVancedManager(vm: MainViewModel) {
|
|
||||||
val navController = rememberNavController()
|
|
||||||
|
|
||||||
EventEffect(vm.appSelectFlow) { app ->
|
|
||||||
navController.navigateComplex(
|
|
||||||
SelectedApplicationInfo,
|
|
||||||
SelectedApplicationInfo.ViewModelParams(app)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
NavHost(
|
|
||||||
navController = navController,
|
|
||||||
startDestination = Dashboard,
|
|
||||||
enterTransition = { slideInHorizontally(initialOffsetX = { it }) },
|
|
||||||
exitTransition = { slideOutHorizontally(targetOffsetX = { -it / 3 }) },
|
|
||||||
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it / 3 }) },
|
|
||||||
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) },
|
|
||||||
) {
|
|
||||||
composable<Dashboard> {
|
|
||||||
DashboardScreen(
|
|
||||||
onSettingsClick = { navController.navigate(Settings) },
|
|
||||||
onAppSelectorClick = {
|
|
||||||
navController.navigate(AppSelector)
|
|
||||||
},
|
|
||||||
onUpdateClick = {
|
|
||||||
navController.navigate(Update())
|
|
||||||
},
|
|
||||||
onDownloaderPluginClick = {
|
|
||||||
navController.navigate(Settings.Downloads)
|
|
||||||
},
|
|
||||||
onAppClick = { packageName ->
|
|
||||||
navController.navigate(InstalledApplicationInfo(packageName))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<InstalledApplicationInfo> {
|
|
||||||
val data = it.toRoute<InstalledApplicationInfo>()
|
|
||||||
|
|
||||||
InstalledAppInfoScreen(
|
|
||||||
onPatchClick = vm::selectApp,
|
|
||||||
onBackClick = navController::popBackStack,
|
|
||||||
viewModel = koinViewModel { parametersOf(data.packageName) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<AppSelector> {
|
|
||||||
AppSelectorScreen(
|
|
||||||
onSelect = vm::selectApp,
|
|
||||||
onStorageSelect = vm::selectApp,
|
|
||||||
onBackClick = navController::popBackStack
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Patcher> {
|
|
||||||
PatcherScreen(
|
|
||||||
onBackClick = {
|
|
||||||
navController.navigate(route = Dashboard) {
|
|
||||||
launchSingleTop = true
|
|
||||||
popUpTo<Dashboard> {
|
|
||||||
inclusive = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
viewModel = koinViewModel { parametersOf(it.getComplexArg<Patcher.ViewModelParams>()) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Update> {
|
|
||||||
val data = it.toRoute<Update>()
|
|
||||||
|
|
||||||
UpdateScreen(
|
|
||||||
onBackClick = navController::popBackStack,
|
|
||||||
vm = koinViewModel { parametersOf(data.downloadOnScreenEntry) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
navigation<SelectedApplicationInfo>(startDestination = SelectedApplicationInfo.Main) {
|
|
||||||
composable<SelectedApplicationInfo.Main> {
|
|
||||||
val parentBackStackEntry = navController.navGraphEntry(it)
|
|
||||||
val data =
|
|
||||||
parentBackStackEntry.getComplexArg<SelectedApplicationInfo.ViewModelParams>()
|
|
||||||
val viewModel =
|
|
||||||
koinNavViewModel<SelectedAppInfoViewModel>(viewModelStoreOwner = parentBackStackEntry) {
|
|
||||||
parametersOf(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectedAppInfoScreen(
|
|
||||||
onBackClick = navController::popBackStack,
|
|
||||||
onPatchClick = {
|
|
||||||
it.lifecycleScope.launch {
|
|
||||||
navController.navigateComplex(
|
|
||||||
Patcher,
|
|
||||||
viewModel.getPatcherParams()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPatchSelectorClick = { app, patches, options ->
|
|
||||||
navController.navigateComplex(
|
|
||||||
SelectedApplicationInfo.PatchesSelector,
|
|
||||||
SelectedApplicationInfo.PatchesSelector.ViewModelParams(
|
|
||||||
app,
|
|
||||||
patches,
|
|
||||||
options
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onRequiredOptions = { app, patches, options ->
|
|
||||||
navController.navigateComplex(
|
|
||||||
SelectedApplicationInfo.RequiredOptions,
|
|
||||||
SelectedApplicationInfo.PatchesSelector.ViewModelParams(
|
|
||||||
app,
|
|
||||||
patches,
|
|
||||||
options
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
vm = viewModel
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<SelectedApplicationInfo.PatchesSelector> {
|
|
||||||
val data =
|
|
||||||
it.getComplexArg<SelectedApplicationInfo.PatchesSelector.ViewModelParams>()
|
|
||||||
val selectedAppInfoVm = koinNavViewModel<SelectedAppInfoViewModel>(
|
|
||||||
viewModelStoreOwner = navController.navGraphEntry(it)
|
|
||||||
)
|
|
||||||
|
|
||||||
PatchesSelectorScreen(
|
|
||||||
onBackClick = navController::popBackStack,
|
|
||||||
onSave = { patches, options ->
|
|
||||||
selectedAppInfoVm.updateConfiguration(patches, options)
|
|
||||||
navController.popBackStack()
|
|
||||||
},
|
|
||||||
viewModel = koinViewModel { parametersOf(data) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<SelectedApplicationInfo.RequiredOptions> {
|
|
||||||
val data =
|
|
||||||
it.getComplexArg<SelectedApplicationInfo.PatchesSelector.ViewModelParams>()
|
|
||||||
val selectedAppInfoVm = koinNavViewModel<SelectedAppInfoViewModel>(
|
|
||||||
viewModelStoreOwner = navController.navGraphEntry(it)
|
|
||||||
)
|
|
||||||
|
|
||||||
RequiredOptionsScreen(
|
|
||||||
onBackClick = navController::popBackStack,
|
|
||||||
onContinue = { patches, options ->
|
|
||||||
selectedAppInfoVm.updateConfiguration(patches, options)
|
|
||||||
it.lifecycleScope.launch {
|
|
||||||
navController.navigateComplex(
|
|
||||||
Patcher,
|
|
||||||
selectedAppInfoVm.getPatcherParams()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
vm = koinViewModel { parametersOf(data) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
navigation<Settings>(startDestination = Settings.Main) {
|
|
||||||
composable<Settings.Main> {
|
|
||||||
SettingsScreen(
|
|
||||||
onBackClick = navController::popBackStack,
|
|
||||||
navigate = navController::navigate
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Settings.General> {
|
|
||||||
GeneralSettingsScreen(onBackClick = navController::popBackStack)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Settings.Advanced> {
|
|
||||||
AdvancedSettingsScreen(onBackClick = navController::popBackStack)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Settings.Developer> {
|
|
||||||
DeveloperSettingsScreen(onBackClick = navController::popBackStack)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Settings.Updates> {
|
|
||||||
UpdatesSettingsScreen(
|
|
||||||
onBackClick = navController::popBackStack,
|
|
||||||
onChangelogClick = { navController.navigate(Settings.Changelogs) },
|
|
||||||
onUpdateClick = { navController.navigate(Update()) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Settings.Downloads> {
|
|
||||||
DownloadsSettingsScreen(onBackClick = navController::popBackStack)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Settings.ImportExport> {
|
|
||||||
ImportExportSettingsScreen(onBackClick = navController::popBackStack)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Settings.About> {
|
|
||||||
AboutSettingsScreen(
|
|
||||||
onBackClick = navController::popBackStack,
|
|
||||||
navigate = navController::navigate
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Settings.Changelogs> {
|
|
||||||
ChangelogsSettingsScreen(onBackClick = navController::popBackStack)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Settings.Contributors> {
|
|
||||||
ContributorSettingsScreen(onBackClick = navController::popBackStack)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable<Settings.Licenses> {
|
|
||||||
LicensesSettingsScreen(onBackClick = navController::popBackStack)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun NavController.navGraphEntry(entry: NavBackStackEntry) =
|
|
||||||
remember(entry) { getBackStackEntry(entry.destination.parent!!.id) }
|
|
||||||
|
|
||||||
// Androidx Navigation does not support storing complex types in route objects, so we have to store them inside the saved state handle of the back stack entry instead.
|
|
||||||
private fun <T : Parcelable, R : ComplexParameter<T>> NavController.navigateComplex(
|
|
||||||
route: R,
|
|
||||||
data: T
|
|
||||||
) {
|
|
||||||
navigate(route)
|
|
||||||
getBackStackEntry(route).savedStateHandle["args"] = data
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T : Parcelable> NavBackStackEntry.getComplexArg() = savedStateHandle.get<T>("args")!!
|
|
@ -1,110 +0,0 @@
|
|||||||
package app.revanced.manager
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.Application
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import app.revanced.manager.data.platform.Filesystem
|
|
||||||
import app.revanced.manager.di.*
|
|
||||||
import app.revanced.manager.domain.manager.PreferencesManager
|
|
||||||
import app.revanced.manager.domain.repository.DownloaderPluginRepository
|
|
||||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
|
||||||
import app.revanced.manager.util.tag
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import coil.Coil
|
|
||||||
import coil.ImageLoader
|
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import com.topjohnwu.superuser.internal.BuilderImpl
|
|
||||||
import kotlinx.coroutines.MainScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
|
||||||
import org.koin.android.ext.android.inject
|
|
||||||
import org.koin.android.ext.koin.androidContext
|
|
||||||
import org.koin.android.ext.koin.androidLogger
|
|
||||||
import org.koin.androidx.workmanager.koin.workManagerFactory
|
|
||||||
import org.koin.core.context.startKoin
|
|
||||||
|
|
||||||
class ManagerApplication : Application() {
|
|
||||||
private val scope = MainScope()
|
|
||||||
private val prefs: PreferencesManager by inject()
|
|
||||||
private val patchBundleRepository: PatchBundleRepository by inject()
|
|
||||||
private val downloaderPluginRepository: DownloaderPluginRepository by inject()
|
|
||||||
private val fs: Filesystem by inject()
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
|
|
||||||
startKoin {
|
|
||||||
androidContext(this@ManagerApplication)
|
|
||||||
androidLogger()
|
|
||||||
workManagerFactory()
|
|
||||||
modules(
|
|
||||||
httpModule,
|
|
||||||
preferencesModule,
|
|
||||||
repositoryModule,
|
|
||||||
serviceModule,
|
|
||||||
managerModule,
|
|
||||||
workerModule,
|
|
||||||
viewModelModule,
|
|
||||||
databaseModule,
|
|
||||||
rootModule
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val pixels = 512
|
|
||||||
Coil.setImageLoader(
|
|
||||||
ImageLoader.Builder(this)
|
|
||||||
.components {
|
|
||||||
add(AppIconKeyer())
|
|
||||||
add(AppIconFetcher.Factory(pixels, true, this@ManagerApplication))
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
|
|
||||||
val shellBuilder = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER)
|
|
||||||
Shell.setDefaultBuilder(shellBuilder)
|
|
||||||
|
|
||||||
scope.launch {
|
|
||||||
prefs.preload()
|
|
||||||
}
|
|
||||||
scope.launch(Dispatchers.Default) {
|
|
||||||
downloaderPluginRepository.reload()
|
|
||||||
}
|
|
||||||
scope.launch(Dispatchers.Default) {
|
|
||||||
with(patchBundleRepository) {
|
|
||||||
reload()
|
|
||||||
updateCheck()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
|
|
||||||
private var firstActivityCreated = false
|
|
||||||
|
|
||||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
|
||||||
if (firstActivityCreated) return
|
|
||||||
firstActivityCreated = true
|
|
||||||
|
|
||||||
// We do not want to call onFreshProcessStart() if there is state to restore.
|
|
||||||
// This can happen on system-initiated process death.
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
Log.d(tag, "Fresh process created")
|
|
||||||
onFreshProcessStart()
|
|
||||||
} else Log.d(tag, "System-initiated process death detected")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityStarted(activity: Activity) {}
|
|
||||||
override fun onActivityResumed(activity: Activity) {}
|
|
||||||
override fun onActivityPaused(activity: Activity) {}
|
|
||||||
override fun onActivityStopped(activity: Activity) {}
|
|
||||||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
|
||||||
override fun onActivityDestroyed(activity: Activity) {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onFreshProcessStart() {
|
|
||||||
fs.uiTempDir.apply {
|
|
||||||
deleteRecursively()
|
|
||||||
mkdirs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
package app.revanced.manager.data.platform
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Environment
|
|
||||||
import androidx.activity.result.contract.ActivityResultContract
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import app.revanced.manager.util.RequestManageStorageContract
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
class Filesystem(private val app: Application) {
|
|
||||||
val contentResolver = app.contentResolver // TODO: move Content Resolver operations to here.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A directory that gets cleared when the app restarts.
|
|
||||||
* Do not store paths to this directory in a parcel.
|
|
||||||
*/
|
|
||||||
val tempDir: File = app.getDir("ephemeral", Context.MODE_PRIVATE).apply {
|
|
||||||
deleteRecursively()
|
|
||||||
mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A directory for storing temporary files related to UI.
|
|
||||||
* This is the same as [tempDir], but does not get cleared on system-initiated process death.
|
|
||||||
* Paths to this directory can be safely stored in parcels.
|
|
||||||
*/
|
|
||||||
val uiTempDir: File = app.getDir("ui_ephemeral", Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
fun externalFilesDir(): Path = Environment.getExternalStorageDirectory().toPath()
|
|
||||||
|
|
||||||
private fun usesManagePermission() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
|
||||||
|
|
||||||
private val storagePermissionName =
|
|
||||||
if (usesManagePermission()) Manifest.permission.MANAGE_EXTERNAL_STORAGE else Manifest.permission.READ_EXTERNAL_STORAGE
|
|
||||||
|
|
||||||
fun permissionContract(): Pair<ActivityResultContract<String, Boolean>, String> {
|
|
||||||
val contract =
|
|
||||||
if (usesManagePermission()) RequestManageStorageContract() else ActivityResultContracts.RequestPermission()
|
|
||||||
return contract to storagePermissionName
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hasStoragePermission() =
|
|
||||||
if (usesManagePermission()) Environment.isExternalStorageManager() else app.checkSelfPermission(
|
|
||||||
storagePermissionName
|
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package app.revanced.manager.data.platform
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.net.NetworkCapabilities
|
|
||||||
import androidx.core.content.getSystemService
|
|
||||||
|
|
||||||
class NetworkInfo(app: Application) {
|
|
||||||
private val connectivityManager = app.getSystemService<ConnectivityManager>()!!
|
|
||||||
|
|
||||||
private fun getCapabilities() = connectivityManager.activeNetwork?.let { connectivityManager.getNetworkCapabilities(it) }
|
|
||||||
fun isConnected() = connectivityManager.activeNetwork != null
|
|
||||||
fun isUnmetered() = getCapabilities()?.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) ?: true
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if it is safe to download large files.
|
|
||||||
*/
|
|
||||||
fun isSafe() = isConnected() && isUnmetered()
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package app.revanced.manager.data.room
|
|
||||||
|
|
||||||
import androidx.room.Database
|
|
||||||
import androidx.room.RoomDatabase
|
|
||||||
import androidx.room.TypeConverters
|
|
||||||
import app.revanced.manager.data.room.apps.downloaded.DownloadedAppDao
|
|
||||||
import app.revanced.manager.data.room.apps.downloaded.DownloadedApp
|
|
||||||
import app.revanced.manager.data.room.apps.installed.AppliedPatch
|
|
||||||
import app.revanced.manager.data.room.apps.installed.InstalledApp
|
|
||||||
import app.revanced.manager.data.room.apps.installed.InstalledAppDao
|
|
||||||
import app.revanced.manager.data.room.selection.PatchSelection
|
|
||||||
import app.revanced.manager.data.room.selection.SelectedPatch
|
|
||||||
import app.revanced.manager.data.room.selection.SelectionDao
|
|
||||||
import app.revanced.manager.data.room.bundles.PatchBundleDao
|
|
||||||
import app.revanced.manager.data.room.bundles.PatchBundleEntity
|
|
||||||
import app.revanced.manager.data.room.options.Option
|
|
||||||
import app.revanced.manager.data.room.options.OptionDao
|
|
||||||
import app.revanced.manager.data.room.options.OptionGroup
|
|
||||||
import app.revanced.manager.data.room.plugins.TrustedDownloaderPlugin
|
|
||||||
import app.revanced.manager.data.room.plugins.TrustedDownloaderPluginDao
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
@Database(
|
|
||||||
entities = [PatchBundleEntity::class, PatchSelection::class, SelectedPatch::class, DownloadedApp::class, InstalledApp::class, AppliedPatch::class, OptionGroup::class, Option::class, TrustedDownloaderPlugin::class],
|
|
||||||
version = 1
|
|
||||||
)
|
|
||||||
@TypeConverters(Converters::class)
|
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
|
||||||
abstract fun patchBundleDao(): PatchBundleDao
|
|
||||||
abstract fun selectionDao(): SelectionDao
|
|
||||||
abstract fun downloadedAppDao(): DownloadedAppDao
|
|
||||||
abstract fun installedAppDao(): InstalledAppDao
|
|
||||||
abstract fun optionDao(): OptionDao
|
|
||||||
abstract fun trustedDownloaderPluginDao(): TrustedDownloaderPluginDao
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun generateUid() = Random.Default.nextInt()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package app.revanced.manager.data.room
|
|
||||||
|
|
||||||
import androidx.room.TypeConverter
|
|
||||||
import app.revanced.manager.data.room.bundles.Source
|
|
||||||
import app.revanced.manager.data.room.options.Option.SerializedValue
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class Converters {
|
|
||||||
@TypeConverter
|
|
||||||
fun sourceFromString(value: String) = Source.from(value)
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun sourceToString(value: Source) = value.toString()
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun fileFromString(value: String) = File(value)
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun fileToString(file: File): String = file.path
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun serializedOptionFromString(value: String) = SerializedValue.fromJsonString(value)
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun serializedOptionToString(value: SerializedValue) = value.toJsonString()
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.apps.downloaded
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "downloaded_app",
|
|
||||||
primaryKeys = ["package_name", "version"]
|
|
||||||
)
|
|
||||||
data class DownloadedApp(
|
|
||||||
@ColumnInfo(name = "package_name") val packageName: String,
|
|
||||||
@ColumnInfo(name = "version") val version: String,
|
|
||||||
@ColumnInfo(name = "directory") val directory: File,
|
|
||||||
@ColumnInfo(name = "last_used") val lastUsed: Long = System.currentTimeMillis()
|
|
||||||
)
|
|
@ -1,26 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.apps.downloaded
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Delete
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Upsert
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface DownloadedAppDao {
|
|
||||||
@Query("SELECT * FROM downloaded_app")
|
|
||||||
fun getAllApps(): Flow<List<DownloadedApp>>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM downloaded_app WHERE package_name = :packageName AND version = :version")
|
|
||||||
suspend fun get(packageName: String, version: String): DownloadedApp?
|
|
||||||
|
|
||||||
@Upsert
|
|
||||||
suspend fun upsert(downloadedApp: DownloadedApp)
|
|
||||||
|
|
||||||
@Query("UPDATE downloaded_app SET last_used = :newValue WHERE package_name = :packageName AND version = :version")
|
|
||||||
suspend fun markUsed(packageName: String, version: String, newValue: Long = System.currentTimeMillis())
|
|
||||||
|
|
||||||
@Delete
|
|
||||||
suspend fun delete(downloadedApps: Collection<DownloadedApp>)
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.apps.installed
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.Index
|
|
||||||
import app.revanced.manager.data.room.bundles.PatchBundleEntity
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
@Entity(
|
|
||||||
tableName = "applied_patch",
|
|
||||||
primaryKeys = ["package_name", "bundle", "patch_name"],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
InstalledApp::class,
|
|
||||||
parentColumns = ["current_package_name"],
|
|
||||||
childColumns = ["package_name"],
|
|
||||||
onDelete = ForeignKey.CASCADE
|
|
||||||
),
|
|
||||||
ForeignKey(
|
|
||||||
PatchBundleEntity::class,
|
|
||||||
parentColumns = ["uid"],
|
|
||||||
childColumns = ["bundle"],
|
|
||||||
onDelete = ForeignKey.CASCADE
|
|
||||||
)
|
|
||||||
],
|
|
||||||
indices = [Index(value = ["bundle"], unique = false)]
|
|
||||||
)
|
|
||||||
data class AppliedPatch(
|
|
||||||
@ColumnInfo(name = "package_name") val packageName: String,
|
|
||||||
@ColumnInfo(name = "bundle") val bundle: Int,
|
|
||||||
@ColumnInfo(name = "patch_name") val patchName: String
|
|
||||||
) : Parcelable
|
|
@ -1,20 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.apps.installed
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import app.revanced.manager.R
|
|
||||||
|
|
||||||
enum class InstallType(val stringResource: Int) {
|
|
||||||
DEFAULT(R.string.default_install),
|
|
||||||
MOUNT(R.string.mount_install)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity(tableName = "installed_app")
|
|
||||||
data class InstalledApp(
|
|
||||||
@PrimaryKey
|
|
||||||
@ColumnInfo(name = "current_package_name") val currentPackageName: String,
|
|
||||||
@ColumnInfo(name = "original_package_name") val originalPackageName: String,
|
|
||||||
@ColumnInfo(name = "version") val version: String,
|
|
||||||
@ColumnInfo(name = "install_type") val installType: InstallType
|
|
||||||
)
|
|
@ -1,46 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.apps.installed
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Delete
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.MapColumn
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Transaction
|
|
||||||
import androidx.room.Upsert
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface InstalledAppDao {
|
|
||||||
@Query("SELECT * FROM installed_app")
|
|
||||||
fun getAll(): Flow<List<InstalledApp>>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM installed_app WHERE current_package_name = :packageName")
|
|
||||||
suspend fun get(packageName: String): InstalledApp?
|
|
||||||
|
|
||||||
@Query(
|
|
||||||
"SELECT bundle, patch_name FROM applied_patch" +
|
|
||||||
" WHERE package_name = :packageName"
|
|
||||||
)
|
|
||||||
suspend fun getPatchesSelection(packageName: String): Map<@MapColumn("bundle") Int, List<@MapColumn(
|
|
||||||
"patch_name"
|
|
||||||
) String>>
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
suspend fun upsertApp(installedApp: InstalledApp, appliedPatches: List<AppliedPatch>) {
|
|
||||||
upsertApp(installedApp)
|
|
||||||
deleteAppliedPatches(installedApp.currentPackageName)
|
|
||||||
insertAppliedPatches(appliedPatches)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Upsert
|
|
||||||
suspend fun upsertApp(installedApp: InstalledApp)
|
|
||||||
|
|
||||||
@Insert
|
|
||||||
suspend fun insertAppliedPatches(appliedPatches: List<AppliedPatch>)
|
|
||||||
|
|
||||||
@Query("DELETE FROM applied_patch WHERE package_name = :packageName")
|
|
||||||
suspend fun deleteAppliedPatches(packageName: String)
|
|
||||||
|
|
||||||
@Delete
|
|
||||||
suspend fun delete(installedApp: InstalledApp)
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.bundles
|
|
||||||
|
|
||||||
import androidx.room.*
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface PatchBundleDao {
|
|
||||||
@Query("SELECT * FROM patch_bundles")
|
|
||||||
suspend fun all(): List<PatchBundleEntity>
|
|
||||||
|
|
||||||
@Query("SELECT version, auto_update FROM patch_bundles WHERE uid = :uid")
|
|
||||||
fun getPropsById(uid: Int): Flow<BundleProperties?>
|
|
||||||
|
|
||||||
@Query("UPDATE patch_bundles SET version = :patches WHERE uid = :uid")
|
|
||||||
suspend fun updateVersion(uid: Int, patches: String?)
|
|
||||||
|
|
||||||
@Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid")
|
|
||||||
suspend fun setAutoUpdate(uid: Int, value: Boolean)
|
|
||||||
|
|
||||||
@Query("UPDATE patch_bundles SET name = :value WHERE uid = :uid")
|
|
||||||
suspend fun setName(uid: Int, value: String)
|
|
||||||
|
|
||||||
@Query("DELETE FROM patch_bundles WHERE uid != 0")
|
|
||||||
suspend fun purgeCustomBundles()
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
suspend fun reset() {
|
|
||||||
purgeCustomBundles()
|
|
||||||
updateVersion(0, null) // Reset the main source
|
|
||||||
}
|
|
||||||
|
|
||||||
@Query("DELETE FROM patch_bundles WHERE uid = :uid")
|
|
||||||
suspend fun remove(uid: Int)
|
|
||||||
|
|
||||||
@Insert
|
|
||||||
suspend fun add(source: PatchBundleEntity)
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.bundles
|
|
||||||
|
|
||||||
import androidx.room.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
|
|
||||||
sealed class Source {
|
|
||||||
object Local : Source() {
|
|
||||||
const val SENTINEL = "local"
|
|
||||||
|
|
||||||
override fun toString() = SENTINEL
|
|
||||||
}
|
|
||||||
|
|
||||||
object API : Source() {
|
|
||||||
const val SENTINEL = "api"
|
|
||||||
|
|
||||||
override fun toString() = SENTINEL
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Remote(val url: Url) : Source() {
|
|
||||||
override fun toString() = url.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(value: String) = when (value) {
|
|
||||||
Local.SENTINEL -> Local
|
|
||||||
API.SENTINEL -> API
|
|
||||||
else -> Remote(Url(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity(tableName = "patch_bundles")
|
|
||||||
data class PatchBundleEntity(
|
|
||||||
@PrimaryKey val uid: Int,
|
|
||||||
@ColumnInfo(name = "name") val name: String,
|
|
||||||
@ColumnInfo(name = "version") val version: String? = null,
|
|
||||||
@ColumnInfo(name = "source") val source: Source,
|
|
||||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
|
||||||
)
|
|
||||||
|
|
||||||
data class BundleProperties(
|
|
||||||
@ColumnInfo(name = "version") val version: String? = null,
|
|
||||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
|
||||||
)
|
|
@ -1,116 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.options
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import app.revanced.manager.patcher.patch.Option
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.SerializationException
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonElement
|
|
||||||
import kotlinx.serialization.json.JsonNull
|
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
|
||||||
import kotlinx.serialization.json.add
|
|
||||||
import kotlinx.serialization.json.boolean
|
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
|
||||||
import kotlinx.serialization.json.float
|
|
||||||
import kotlinx.serialization.json.int
|
|
||||||
import kotlinx.serialization.json.jsonArray
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import kotlinx.serialization.json.long
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.KType
|
|
||||||
import kotlin.reflect.typeOf
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "options",
|
|
||||||
primaryKeys = ["group", "patch_name", "key"],
|
|
||||||
foreignKeys = [ForeignKey(
|
|
||||||
OptionGroup::class,
|
|
||||||
parentColumns = ["uid"],
|
|
||||||
childColumns = ["group"],
|
|
||||||
onDelete = ForeignKey.CASCADE
|
|
||||||
)]
|
|
||||||
)
|
|
||||||
data class Option(
|
|
||||||
@ColumnInfo(name = "group") val group: Int,
|
|
||||||
@ColumnInfo(name = "patch_name") val patchName: String,
|
|
||||||
@ColumnInfo(name = "key") val key: String,
|
|
||||||
// Encoded as Json.
|
|
||||||
@ColumnInfo(name = "value") val value: SerializedValue,
|
|
||||||
) {
|
|
||||||
@Serializable
|
|
||||||
data class SerializedValue(val raw: JsonElement) {
|
|
||||||
fun toJsonString() = json.encodeToString(raw)
|
|
||||||
fun deserializeFor(option: Option<*>): Any? {
|
|
||||||
if (raw is JsonNull) return null
|
|
||||||
|
|
||||||
val errorMessage = "Cannot deserialize value as ${option.type}"
|
|
||||||
try {
|
|
||||||
if (option.type.classifier == List::class) {
|
|
||||||
val elementType = option.type.arguments.first().type!!
|
|
||||||
return raw.jsonArray.map { deserializeBasicType(elementType, it.jsonPrimitive) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return deserializeBasicType(option.type, raw.jsonPrimitive)
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
throw SerializationException(errorMessage, e)
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
throw SerializationException(errorMessage, e)
|
|
||||||
} catch (e: kotlinx.serialization.SerializationException) {
|
|
||||||
throw SerializationException(errorMessage, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val json = Json {
|
|
||||||
// Patcher does not forbid the use of these values, so we should support them.
|
|
||||||
allowSpecialFloatingPointValues = true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deserializeBasicType(type: KType, value: JsonPrimitive) = when (type) {
|
|
||||||
typeOf<Boolean>() -> value.boolean
|
|
||||||
typeOf<Int>() -> value.int
|
|
||||||
typeOf<Long>() -> value.long
|
|
||||||
typeOf<Float>() -> value.float
|
|
||||||
typeOf<String>() -> value.content.also {
|
|
||||||
if (!value.isString) throw SerializationException(
|
|
||||||
"Expected value to be a string: $value"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw SerializationException("Unknown type: $type")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromJsonString(value: String) = SerializedValue(json.decodeFromString(value))
|
|
||||||
fun fromValue(value: Any?) = SerializedValue(when (value) {
|
|
||||||
null -> JsonNull
|
|
||||||
is Number -> JsonPrimitive(value)
|
|
||||||
is Boolean -> JsonPrimitive(value)
|
|
||||||
is String -> JsonPrimitive(value)
|
|
||||||
is List<*> -> buildJsonArray {
|
|
||||||
var elementClass: KClass<out Any>? = null
|
|
||||||
|
|
||||||
value.forEach {
|
|
||||||
when (it) {
|
|
||||||
null -> throw SerializationException("List elements must not be null")
|
|
||||||
is Number -> add(it)
|
|
||||||
is Boolean -> add(it)
|
|
||||||
is String -> add(it)
|
|
||||||
else -> throw SerializationException("Unknown element type: ${it::class.simpleName}")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elementClass == null) elementClass = it::class
|
|
||||||
else if (elementClass != it::class) throw SerializationException("List elements must have the same type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw SerializationException("Unknown type: ${value::class.simpleName}")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SerializationException(message: String, cause: Throwable? = null) :
|
|
||||||
Exception(message, cause)
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.options
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.MapColumn
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Transaction
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
abstract class OptionDao {
|
|
||||||
@Transaction
|
|
||||||
@Query(
|
|
||||||
"SELECT patch_bundle, `group`, patch_name, `key`, value FROM option_groups" +
|
|
||||||
" LEFT JOIN options ON uid = options.`group`" +
|
|
||||||
" WHERE package_name = :packageName"
|
|
||||||
)
|
|
||||||
abstract suspend fun getOptions(packageName: String): Map<@MapColumn("patch_bundle") Int, List<Option>>
|
|
||||||
|
|
||||||
@Query("SELECT uid FROM option_groups WHERE patch_bundle = :bundleUid AND package_name = :packageName")
|
|
||||||
abstract suspend fun getGroupId(bundleUid: Int, packageName: String): Int?
|
|
||||||
|
|
||||||
@Query("SELECT package_name FROM option_groups")
|
|
||||||
abstract fun getPackagesWithOptions(): Flow<List<String>>
|
|
||||||
|
|
||||||
@Insert
|
|
||||||
abstract suspend fun createOptionGroup(group: OptionGroup)
|
|
||||||
|
|
||||||
@Query("DELETE FROM option_groups WHERE patch_bundle = :uid")
|
|
||||||
abstract suspend fun clearForPatchBundle(uid: Int)
|
|
||||||
|
|
||||||
@Query("DELETE FROM option_groups WHERE package_name = :packageName")
|
|
||||||
abstract suspend fun clearForPackage(packageName: String)
|
|
||||||
|
|
||||||
@Query("DELETE FROM option_groups")
|
|
||||||
abstract suspend fun reset()
|
|
||||||
|
|
||||||
@Insert
|
|
||||||
protected abstract suspend fun insertOptions(patches: List<Option>)
|
|
||||||
|
|
||||||
@Query("DELETE FROM options WHERE `group` = :groupId")
|
|
||||||
protected abstract suspend fun clearGroup(groupId: Int)
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
open suspend fun updateOptions(options: Map<Int, List<Option>>) =
|
|
||||||
options.forEach { (groupId, options) ->
|
|
||||||
clearGroup(groupId)
|
|
||||||
insertOptions(options)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.options
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.Index
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import app.revanced.manager.data.room.bundles.PatchBundleEntity
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "option_groups",
|
|
||||||
foreignKeys = [ForeignKey(
|
|
||||||
PatchBundleEntity::class,
|
|
||||||
parentColumns = ["uid"],
|
|
||||||
childColumns = ["patch_bundle"],
|
|
||||||
onDelete = ForeignKey.CASCADE
|
|
||||||
)],
|
|
||||||
indices = [Index(value = ["patch_bundle", "package_name"], unique = true)]
|
|
||||||
)
|
|
||||||
data class OptionGroup(
|
|
||||||
@PrimaryKey val uid: Int,
|
|
||||||
@ColumnInfo(name = "patch_bundle") val patchBundle: Int,
|
|
||||||
@ColumnInfo(name = "package_name") val packageName: String
|
|
||||||
)
|
|
@ -1,11 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.plugins
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity(tableName = "trusted_downloader_plugins")
|
|
||||||
class TrustedDownloaderPlugin(
|
|
||||||
@PrimaryKey @ColumnInfo(name = "package_name") val packageName: String,
|
|
||||||
@ColumnInfo(name = "signature") val signature: ByteArray
|
|
||||||
)
|
|
@ -1,22 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.plugins
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Transaction
|
|
||||||
import androidx.room.Upsert
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface TrustedDownloaderPluginDao {
|
|
||||||
@Query("SELECT signature FROM trusted_downloader_plugins WHERE package_name = :packageName")
|
|
||||||
suspend fun getTrustedSignature(packageName: String): ByteArray?
|
|
||||||
|
|
||||||
@Upsert
|
|
||||||
suspend fun upsertTrust(plugin: TrustedDownloaderPlugin)
|
|
||||||
|
|
||||||
@Query("DELETE FROM trusted_downloader_plugins WHERE package_name = :packageName")
|
|
||||||
suspend fun remove(packageName: String)
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
@Query("DELETE FROM trusted_downloader_plugins WHERE package_name IN (:packageNames)")
|
|
||||||
suspend fun removeAll(packageNames: Set<String>)
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.selection
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.Index
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import app.revanced.manager.data.room.bundles.PatchBundleEntity
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "patch_selections",
|
|
||||||
foreignKeys = [ForeignKey(
|
|
||||||
PatchBundleEntity::class,
|
|
||||||
parentColumns = ["uid"],
|
|
||||||
childColumns = ["patch_bundle"],
|
|
||||||
onDelete = ForeignKey.CASCADE
|
|
||||||
)],
|
|
||||||
indices = [Index(value = ["patch_bundle", "package_name"], unique = true)]
|
|
||||||
)
|
|
||||||
data class PatchSelection(
|
|
||||||
@PrimaryKey val uid: Int,
|
|
||||||
@ColumnInfo(name = "patch_bundle") val patchBundle: Int,
|
|
||||||
@ColumnInfo(name = "package_name") val packageName: String
|
|
||||||
)
|
|
@ -1,20 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.selection
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "selected_patches",
|
|
||||||
primaryKeys = ["selection", "patch_name"],
|
|
||||||
foreignKeys = [ForeignKey(
|
|
||||||
PatchSelection::class,
|
|
||||||
parentColumns = ["uid"],
|
|
||||||
childColumns = ["selection"],
|
|
||||||
onDelete = ForeignKey.CASCADE
|
|
||||||
)]
|
|
||||||
)
|
|
||||||
data class SelectedPatch(
|
|
||||||
@ColumnInfo(name = "selection") val selection: Int,
|
|
||||||
@ColumnInfo(name = "patch_name") val patchName: String
|
|
||||||
)
|
|
@ -1,58 +0,0 @@
|
|||||||
package app.revanced.manager.data.room.selection
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.MapColumn
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Transaction
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
abstract class SelectionDao {
|
|
||||||
@Transaction
|
|
||||||
@Query(
|
|
||||||
"SELECT patch_bundle, patch_name FROM patch_selections" +
|
|
||||||
" LEFT JOIN selected_patches ON uid = selected_patches.selection" +
|
|
||||||
" WHERE package_name = :packageName"
|
|
||||||
)
|
|
||||||
abstract suspend fun getSelectedPatches(packageName: String): Map<@MapColumn("patch_bundle") Int, List<@MapColumn(
|
|
||||||
"patch_name"
|
|
||||||
) String>>
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
@Query(
|
|
||||||
"SELECT package_name, patch_name FROM patch_selections" +
|
|
||||||
" LEFT JOIN selected_patches ON uid = selected_patches.selection" +
|
|
||||||
" WHERE patch_bundle = :bundleUid"
|
|
||||||
)
|
|
||||||
abstract suspend fun exportSelection(bundleUid: Int): Map<@MapColumn("package_name") String, List<@MapColumn(
|
|
||||||
"patch_name"
|
|
||||||
) String>>
|
|
||||||
|
|
||||||
@Query("SELECT uid FROM patch_selections WHERE patch_bundle = :bundleUid AND package_name = :packageName")
|
|
||||||
abstract suspend fun getSelectionId(bundleUid: Int, packageName: String): Int?
|
|
||||||
|
|
||||||
@Insert
|
|
||||||
abstract suspend fun createSelection(selection: PatchSelection)
|
|
||||||
|
|
||||||
@Query("DELETE FROM patch_selections WHERE patch_bundle = :uid")
|
|
||||||
abstract suspend fun clearForPatchBundle(uid: Int)
|
|
||||||
|
|
||||||
@Query("DELETE FROM patch_selections WHERE package_name = :packageName")
|
|
||||||
abstract suspend fun clearForPackage(packageName: String)
|
|
||||||
|
|
||||||
@Query("DELETE FROM patch_selections")
|
|
||||||
abstract suspend fun reset()
|
|
||||||
|
|
||||||
@Insert
|
|
||||||
protected abstract suspend fun selectPatches(patches: List<SelectedPatch>)
|
|
||||||
|
|
||||||
@Query("DELETE FROM selected_patches WHERE selection = :selectionId")
|
|
||||||
protected abstract suspend fun clearSelection(selectionId: Int)
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
open suspend fun updateSelections(selections: Map<Int, Set<String>>) =
|
|
||||||
selections.forEach { (selectionUid, patches) ->
|
|
||||||
clearSelection(selectionUid)
|
|
||||||
selectPatches(patches.map { SelectedPatch(selectionUid, it) })
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package app.revanced.manager.di
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.room.Room
|
|
||||||
import app.revanced.manager.data.room.AppDatabase
|
|
||||||
import org.koin.android.ext.koin.androidContext
|
|
||||||
import org.koin.dsl.module
|
|
||||||
|
|
||||||
val databaseModule = module {
|
|
||||||
fun provideAppDatabase(context: Context) = Room.databaseBuilder(context, AppDatabase::class.java, "manager").build()
|
|
||||||
|
|
||||||
single {
|
|
||||||
provideAppDatabase(androidContext())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
package app.revanced.manager.di
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import app.revanced.manager.BuildConfig
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.engine.okhttp.*
|
|
||||||
import io.ktor.client.plugins.HttpTimeout
|
|
||||||
import io.ktor.client.plugins.UserAgent
|
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.Cache
|
|
||||||
import okhttp3.Dns
|
|
||||||
import org.koin.android.ext.koin.androidContext
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
|
||||||
import org.koin.dsl.module
|
|
||||||
import java.net.Inet4Address
|
|
||||||
import java.net.InetAddress
|
|
||||||
|
|
||||||
val httpModule = module {
|
|
||||||
fun provideHttpClient(context: Context, json: Json) = HttpClient(OkHttp) {
|
|
||||||
engine {
|
|
||||||
config {
|
|
||||||
dns(object : Dns {
|
|
||||||
override fun lookup(hostname: String): List<InetAddress> {
|
|
||||||
val addresses = Dns.SYSTEM.lookup(hostname)
|
|
||||||
return if (hostname == "raw.githubusercontent.com") {
|
|
||||||
addresses.filterIsInstance<Inet4Address>()
|
|
||||||
} else {
|
|
||||||
addresses
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
cache(Cache(context.cacheDir.resolve("cache").also { it.mkdirs() }, 1024 * 1024 * 100))
|
|
||||||
followRedirects(true)
|
|
||||||
followSslRedirects(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json(json)
|
|
||||||
}
|
|
||||||
install(HttpTimeout) {
|
|
||||||
socketTimeoutMillis = 10000
|
|
||||||
}
|
|
||||||
install(UserAgent) {
|
|
||||||
agent = "ReVanced-Manager/${BuildConfig.VERSION_CODE}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun provideJson() = Json {
|
|
||||||
encodeDefaults = true
|
|
||||||
isLenient = true
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
}
|
|
||||||
|
|
||||||
single {
|
|
||||||
provideHttpClient(androidContext(), get())
|
|
||||||
}
|
|
||||||
singleOf(::provideJson)
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package app.revanced.manager.di
|
|
||||||
|
|
||||||
import app.revanced.manager.domain.manager.KeystoreManager
|
|
||||||
import app.revanced.manager.util.PM
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
|
||||||
import org.koin.dsl.module
|
|
||||||
|
|
||||||
val managerModule = module {
|
|
||||||
singleOf(::KeystoreManager)
|
|
||||||
singleOf(::PM)
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.manager.di
|
|
||||||
|
|
||||||
import app.revanced.manager.domain.manager.PreferencesManager
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
|
||||||
import org.koin.dsl.module
|
|
||||||
|
|
||||||
val preferencesModule = module {
|
|
||||||
singleOf(::PreferencesManager)
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package app.revanced.manager.di
|
|
||||||
|
|
||||||
import app.revanced.manager.data.platform.Filesystem
|
|
||||||
import app.revanced.manager.data.platform.NetworkInfo
|
|
||||||
import app.revanced.manager.domain.repository.*
|
|
||||||
import app.revanced.manager.domain.worker.WorkerRepository
|
|
||||||
import app.revanced.manager.network.api.ReVancedAPI
|
|
||||||
import org.koin.core.module.dsl.createdAtStart
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
|
||||||
import org.koin.dsl.module
|
|
||||||
|
|
||||||
val repositoryModule = module {
|
|
||||||
singleOf(::ReVancedAPI)
|
|
||||||
singleOf(::Filesystem) {
|
|
||||||
createdAtStart()
|
|
||||||
}
|
|
||||||
singleOf(::NetworkInfo)
|
|
||||||
singleOf(::PatchBundlePersistenceRepository)
|
|
||||||
singleOf(::PatchSelectionRepository)
|
|
||||||
singleOf(::PatchOptionsRepository)
|
|
||||||
singleOf(::PatchBundleRepository) {
|
|
||||||
// It is best to load patch bundles ASAP
|
|
||||||
createdAtStart()
|
|
||||||
}
|
|
||||||
singleOf(::DownloaderPluginRepository)
|
|
||||||
singleOf(::WorkerRepository)
|
|
||||||
singleOf(::DownloadedAppRepository)
|
|
||||||
singleOf(::InstalledAppRepository)
|
|
||||||
}
|
|