mirror of
https://github.com/ReVanced/revanced-manager-downloaders.git
synced 2026-01-11 13:56:17 +00:00
feat: Add play store downloader (#10)
This commit is contained in:
14
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
14
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -11,18 +11,18 @@ body:
|
|||||||
<source
|
<source
|
||||||
width="256px"
|
width="256px"
|
||||||
media="(prefers-color-scheme: dark)"
|
media="(prefers-color-scheme: dark)"
|
||||||
srcset="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
srcset="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
width="256px"
|
width="256px"
|
||||||
src="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
src="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||||
>
|
>
|
||||||
</picture>
|
</picture>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://revanced.app/">
|
<a href="https://revanced.app/">
|
||||||
<picture>
|
<picture>
|
||||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-logo/revanced-logo.svg" />
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-logo/revanced-logo.svg" />
|
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/ReVanced">
|
<a href="https://github.com/ReVanced">
|
||||||
@@ -68,8 +68,8 @@ body:
|
|||||||
|
|
||||||
Before creating a new bug report, please keep the following in mind:
|
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-apkmirror-downloader/issues?q=label%3A%22Bug+report%22).
|
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-manager-downloaders/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-apkmirror-downloader/blob/main/CONTRIBUTING.md).
|
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager-downloaders/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).
|
- **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
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
@@ -79,7 +79,7 @@ body:
|
|||||||
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
|
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
|
||||||
- Add images and videos if possible
|
- Add images and videos if possible
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Error logs
|
label: Error logs
|
||||||
|
|||||||
12
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
12
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -11,18 +11,18 @@ body:
|
|||||||
<source
|
<source
|
||||||
width="256px"
|
width="256px"
|
||||||
media="(prefers-color-scheme: dark)"
|
media="(prefers-color-scheme: dark)"
|
||||||
srcset="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
srcset="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
width="256px"
|
width="256px"
|
||||||
src="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
src="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||||
>
|
>
|
||||||
</picture>
|
</picture>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://revanced.app/">
|
<a href="https://revanced.app/">
|
||||||
<picture>
|
<picture>
|
||||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-logo/revanced-logo.svg" />
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-logo/revanced-logo.svg" />
|
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/ReVanced">
|
<a href="https://github.com/ReVanced">
|
||||||
@@ -68,8 +68,8 @@ body:
|
|||||||
|
|
||||||
Before creating a new feature request, please keep the following in mind:
|
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-apkmirror-downloader/issues?q=label%3A%22Feature+request%22).
|
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-manager-downloaders/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-apkmirror-downloader/blob/main/CONTRIBUTING.md).
|
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager-downloaders/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).
|
- **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
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
10
.github/workflows/build_pull_request.yml
vendored
10
.github/workflows/build_pull_request.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
submodules: true
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
@@ -23,9 +23,15 @@ jobs:
|
|||||||
java-version: "17"
|
java-version: "17"
|
||||||
|
|
||||||
- name: Cache Gradle
|
- name: Cache Gradle
|
||||||
uses: burrunan/gradle-cache-action@v2
|
uses: burrunan/gradle-cache-action@v3
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: ./gradlew build --no-daemon
|
run: ./gradlew build --no-daemon
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: revanced-manager-downloaders
|
||||||
|
path: downloaders/*/build/outputs/apk/release/*.apk
|
||||||
|
|||||||
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -12,6 +12,8 @@ jobs:
|
|||||||
name: Release
|
name: Release
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
id-token: write
|
||||||
|
attestations: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -20,7 +22,7 @@ jobs:
|
|||||||
# Make sure the release step uses its own credentials:
|
# Make sure the release step uses its own credentials:
|
||||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
fetch-depth: 0
|
submodules: true
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
@@ -29,12 +31,12 @@ jobs:
|
|||||||
java-version: "17"
|
java-version: "17"
|
||||||
|
|
||||||
- name: Cache Gradle
|
- name: Cache Gradle
|
||||||
uses: burrunan/gradle-cache-action@v2
|
uses: burrunan/gradle-cache-action@v3
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: ./gradlew build --no-daemon
|
run: ./gradlew assembleRelease --no-daemon
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
@@ -64,3 +66,10 @@ jobs:
|
|||||||
KEYSTORE_ENTRY_PASSWORD: ${{ secrets.KEYSTORE_ENTRY_PASSWORD }}
|
KEYSTORE_ENTRY_PASSWORD: ${{ secrets.KEYSTORE_ENTRY_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
npx semantic-release
|
npx semantic-release
|
||||||
|
|
||||||
|
- name: Attest
|
||||||
|
if: steps.release.outputs.new_release_published == 'true'
|
||||||
|
uses: actions/attest-build-provenance@v2
|
||||||
|
with:
|
||||||
|
subject-name: 'ReVanced Downloader ${{ steps.release.outputs.new_release_git_tag }}'
|
||||||
|
subject-path: downloaders/*/build/outputs/apk/release/*.apk
|
||||||
|
|||||||
140
.gitignore
vendored
140
.gitignore
vendored
@@ -1,130 +1,16 @@
|
|||||||
### Java template
|
|
||||||
# Compiled class file
|
|
||||||
*.class
|
|
||||||
|
|
||||||
# Log file
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# BlueJ files
|
|
||||||
*.ctxt
|
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
|
||||||
.mtj.tmp/
|
|
||||||
|
|
||||||
# Package Files #
|
|
||||||
*.jar
|
|
||||||
*.war
|
|
||||||
*.nar
|
|
||||||
*.ear
|
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
|
||||||
hs_err_pid*
|
|
||||||
|
|
||||||
### JetBrains template
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
.idea/artifacts
|
|
||||||
.idea/compiler.xml
|
|
||||||
.idea/jarRepositories.xml
|
|
||||||
.idea/modules.xml
|
|
||||||
.idea/*.iml
|
|
||||||
.idea/modules
|
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### Gradle template
|
|
||||||
.gradle
|
.gradle
|
||||||
**/build/
|
/local.properties
|
||||||
!src/**/build/
|
/.idea/caches
|
||||||
|
/.idea/libraries
|
||||||
# Ignore Gradle GUI config
|
/.idea/modules.xml
|
||||||
gradle-app.setting
|
/.idea/workspace.xml
|
||||||
|
/.idea/navEditor.xml
|
||||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
/.idea/assetWizardSettings.xml
|
||||||
!gradle-wrapper.jar
|
.DS_Store
|
||||||
|
**/build
|
||||||
# Cache of project
|
/captures
|
||||||
.gradletasknamecache
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
|
|
||||||
# gradle/wrapper/gradle-wrapper.properties
|
|
||||||
|
|
||||||
# Potentially copyrighted test APK
|
|
||||||
*.apk
|
|
||||||
|
|
||||||
# Ignore vscode config
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Ignore IDEA files
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
.kotlin/
|
|
||||||
|
|
||||||
local.properties
|
local.properties
|
||||||
|
.idea/
|
||||||
|
|||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "arsclib"]
|
||||||
|
path = arsclib
|
||||||
|
url = https://github.com/REAndroid/ARSCLib
|
||||||
@@ -32,7 +32,10 @@
|
|||||||
{
|
{
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
"path": "build/outputs/apk/release/*.apk?(.asc)",
|
"path": "play-store-downloader/build/outputs/apk/release/play-store-downloader-*.apk?(.asc)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "apkmirror-downloader/build/outputs/apk/release/apkmirror-downloader-*.apk?(.asc)",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
successComment: false
|
successComment: false
|
||||||
|
|||||||
@@ -60,38 +60,38 @@
|
|||||||
|
|
||||||
# 👋 Contribution guidelines
|
# 👋 Contribution guidelines
|
||||||
|
|
||||||
This document describes how to contribute to ReVanced Manager downloader template.
|
This document describes how to contribute to ReVanced Manager Downloaders.
|
||||||
|
|
||||||
## 📖 Resources to help you get started
|
## 📖 Resources to help you get started
|
||||||
|
|
||||||
* [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
|
* [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-downloader-template/issues) are where we keep track of bugs and feature requests
|
* [Issues](https://github.com/ReVanced/revanced-manager-downloaders/issues) are where we keep track of bugs and feature requests
|
||||||
|
|
||||||
## 🙏 Submitting a feature request
|
## 🙏 Submitting a feature request
|
||||||
|
|
||||||
Features can be requested by opening an issue using the
|
Features can be requested by opening an issue using the
|
||||||
[Feature request issue template](https://github.com/ReVanced/revanced-manager-downloader-template/issues/new?assignees=&labels=Feature+request&projects=&template=feature_request.yml&title=feat%3A+).
|
[Feature request issue template](https://github.com/ReVanced/revanced-manager-downloaders/issues/new?assignees=&labels=Feature+request&projects=&template=feature_request.yml&title=feat%3A+).
|
||||||
|
|
||||||
> **Note**
|
> **Note**
|
||||||
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Manager downloader template.
|
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Manager Downloaders.
|
||||||
> Good motivation has to be provided for a request to be accepted.
|
> Good motivation has to be provided for a request to be accepted.
|
||||||
|
|
||||||
## 🐞 Submitting a bug report
|
## 🐞 Submitting a bug report
|
||||||
|
|
||||||
If you encounter a bug while using ReVanced Manager downloader template, open an issue using the
|
If you encounter a bug while using ReVanced Manager Downloaders, open an issue using the
|
||||||
[Bug report issue template](https://github.com/ReVanced/revanced-manager-downloader-template/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
|
[Bug report issue template](https://github.com/ReVanced/revanced-manager-downloaders/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
|
||||||
|
|
||||||
## 📝 How to contribute
|
## 📝 How to contribute
|
||||||
|
|
||||||
1. Before contributing, it is recommended to open an issue to discuss your change
|
1. Before contributing, it is recommended to open an issue to discuss your change
|
||||||
with the maintainers of ReVanced Manager downloader template. This will help you determine whether your change is acceptable
|
with the maintainers of ReVanced Manager Downloaders. This will help you determine whether your change is acceptable
|
||||||
and whether it is worth your time to implement it
|
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`
|
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
|
||||||
3. Commit your changes
|
3. Commit your changes
|
||||||
4. Submit a pull request to the `dev` branch of the repository and reference issues
|
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
|
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,
|
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 downloader template
|
it will be merged into the `dev` branch and will be included in the next release of ReVanced Manager Downloaders
|
||||||
|
|
||||||
❤️ Thank you for considering contributing to ReVanced Manager downloader template,
|
❤️ Thank you for considering contributing to ReVanced Manager Downloaders,
|
||||||
ReVanced
|
ReVanced
|
||||||
|
|||||||
31
README.md
31
README.md
@@ -58,34 +58,43 @@
|
|||||||
Continuing the legacy of Vanced
|
Continuing the legacy of Vanced
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# 👋🔌 ReVanced Manager APKMirror downloader
|
# 👋🔌 ReVanced Manager Downloaders
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
An [APKMirror](https://www.apkmirror.com/) plugin for ReVanced Manager.
|
The collection of ReVanced downloaders.
|
||||||
|
|
||||||
## 🧑💻 Usage
|
## 🧑💻 Usage
|
||||||
|
|
||||||
- Plugins are managed as Android apps. Download and install the APK file from the releases page.
|
- A downloader is managed as Android apps. Download and install the APK file from the releases page.
|
||||||
- After installing, restart ReVanced Manager and enable the plugin in the settings.
|
- After installing, restart ReVanced Manager and enable the downloader in the settings.
|
||||||
- The plugin will now be usable.
|
- The downloader will now be usable.
|
||||||
|
|
||||||
The plugin works by opening the APKMirror website in an embedded browser. If the search string contains a version, you must select that version. Selecting another version in that situation will cause patching to fail.
|
|
||||||
|
### APMMirror Downloader
|
||||||
|
This downloader will open an [APKMirror](https://www.apkmirror.com/) page where you can download the apk as you would normally do.
|
||||||
|
If the chosen file is a bundle (.apkm file), the downloader will automatically merge it into a .apk file.
|
||||||
|
|
||||||
|
### Play Store Downloader
|
||||||
|
When you get prompted, log in to Google with your account. After that, you will be able to download apps from the Play Store.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Due to technical limitations, it is only possible to download the latest version of the app. If the ReVanced suggested version differs from the latest, the installation will fail.
|
||||||
|
|
||||||
## 📚 Everything else
|
## 📚 Everything else
|
||||||
|
|
||||||
### 📙 Contributing
|
### 📙 Contributing
|
||||||
|
|
||||||
Thank you for considering contributing to ReVanced Manager APKMirror downloader.
|
Thank you for considering contributing to ReVanced Manager Downloaders.
|
||||||
You can find the contribution guidelines [here](CONTRIBUTING.md).
|
You can find the contribution guidelines [here](CONTRIBUTING.md).
|
||||||
|
|
||||||
### 🛠️ Building
|
### 🛠️ Building
|
||||||
|
|
||||||
To build ReVanced Manager downloader template, a Java Development Kit (JDK) and Git must be installed.
|
To build ReVanced Manager Downloaders, a Java Development Kit (JDK) and Git must be installed.
|
||||||
Follow the steps below to build ReVanced Manager downloader template:
|
Follow the steps below to build ReVanced Manager Downloaders:
|
||||||
|
|
||||||
1. Run `git clone git@github.com:ReVanced/revanced-manager-apkmirror-downloader.git` to clone the repository
|
1. Run `git clone git@github.com:ReVanced/revanced-manager-downloaders.git` to clone the repository
|
||||||
2. Run `gradlew assembleRelease` to build the project
|
2. Run `gradlew assembleRelease` to build the project
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
|
|||||||
1
arsclib
Submodule
1
arsclib
Submodule
Submodule arsclib added at a44577e73f
182
build.gradle.kts
182
build.gradle.kts
@@ -1,88 +1,110 @@
|
|||||||
|
import com.android.build.gradle.AppExtension
|
||||||
|
import com.android.build.gradle.internal.api.ApkVariantOutputImpl
|
||||||
|
import org.gradle.plugins.signing.SigningExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.parcelize) apply false
|
||||||
publishing
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
signing
|
alias(libs.plugins.compose.compiler) apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
subprojects {
|
||||||
compileOnly(libs.plugin.api)
|
repositories {
|
||||||
}
|
google()
|
||||||
|
mavenCentral()
|
||||||
android {
|
maven {
|
||||||
val packageName = "app.revanced.manager.plugin.downloader.apkmirror"
|
name = "GitHubPackages"
|
||||||
|
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||||
namespace = packageName
|
credentials {
|
||||||
compileSdk = 35
|
username = providers.gradleProperty("gpr.user")
|
||||||
|
.getOrElse(System.getenv("GITHUB_ACTOR"))
|
||||||
defaultConfig {
|
password =
|
||||||
applicationId = packageName
|
providers.gradleProperty("gpr.key").getOrElse(System.getenv("GITHUB_TOKEN"))
|
||||||
minSdk = 26
|
|
||||||
targetSdk = 35
|
|
||||||
versionName = version.toString()
|
|
||||||
versionCode = versionName!!.filter { it.isDigit() }.toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
proguardFiles(
|
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
||||||
"proguard-rules.pro",
|
|
||||||
)
|
|
||||||
|
|
||||||
val keystoreFile = file("keystore.jks")
|
|
||||||
signingConfig =
|
|
||||||
if (keystoreFile.exists()) {
|
|
||||||
signingConfigs.create("release") {
|
|
||||||
storeFile = keystoreFile
|
|
||||||
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
|
||||||
keyAlias = System.getenv("KEYSTORE_ENTRY_ALIAS")
|
|
||||||
keyPassword = System.getenv("KEYSTORE_ENTRY_PASSWORD")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
signingConfigs["debug"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "17"
|
|
||||||
}
|
|
||||||
|
|
||||||
applicationVariants.all {
|
|
||||||
outputs.all {
|
|
||||||
this as com.android.build.gradle.internal.api.ApkVariantOutputImpl
|
|
||||||
|
|
||||||
outputFileName = "${rootProject.name}-$version.apk"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
|
||||||
val assembleReleaseSignApk by registering {
|
|
||||||
dependsOn("assembleRelease")
|
|
||||||
|
|
||||||
val apk = layout.buildDirectory.file("outputs/apk/release/${rootProject.name}-$version.apk")
|
|
||||||
|
|
||||||
inputs.file(apk).withPropertyName("input")
|
|
||||||
outputs.file(apk.map { it.asFile.resolveSibling("${it.asFile.name}.asc") })
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
signing {
|
|
||||||
useGpgCmd()
|
|
||||||
sign(*inputs.files.files.toTypedArray())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used by gradle-semantic-release-plugin.
|
if (project.path.endsWith("-downloader")) {
|
||||||
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435.
|
apply(plugin = "com.android.application")
|
||||||
publish {
|
apply(plugin = "org.jetbrains.kotlin.android")
|
||||||
dependsOn(assembleReleaseSignApk)
|
apply(plugin = "maven-publish")
|
||||||
|
apply(plugin = "signing")
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
"compileOnly"(rootProject.libs.plugin.api)
|
||||||
|
}
|
||||||
|
|
||||||
|
configure<AppExtension> {
|
||||||
|
compileSdkVersion(35)
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
targetSdk = 35
|
||||||
|
versionName = version.toString()
|
||||||
|
versionCode = versionName!!.filter { it.isDigit() }.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
getByName("release") {
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
|
||||||
|
val keystoreFile = file("${rootDir}/keystore.jks")
|
||||||
|
signingConfig =
|
||||||
|
if (keystoreFile.exists()) {
|
||||||
|
signingConfigs.create("release") {
|
||||||
|
storeFile = keystoreFile
|
||||||
|
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
||||||
|
keyAlias = System.getenv("KEYSTORE_ENTRY_ALIAS")
|
||||||
|
keyPassword = System.getenv("KEYSTORE_ENTRY_PASSWORD")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signingConfigs.getByName("debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationVariants.all {
|
||||||
|
outputs.all {
|
||||||
|
this as ApkVariantOutputImpl
|
||||||
|
outputFileName = "${project.name}-$version.apk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<KotlinCompile>().configureEach {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("assembleReleaseSignApk") {
|
||||||
|
dependsOn("assembleRelease")
|
||||||
|
|
||||||
|
val apk = layout.buildDirectory.file("outputs/apk/release/${project.name}-$version.apk")
|
||||||
|
|
||||||
|
inputs.file(apk).withPropertyName("input")
|
||||||
|
outputs.file(apk.map { it.asFile.resolveSibling("${it.asFile.name}.asc") })
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
project.configure<SigningExtension> {
|
||||||
|
useGpgCmd()
|
||||||
|
sign(*inputs.files.files.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named("publish") {
|
||||||
|
dependsOn("assembleReleaseSignApk")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
16
downloaders/apkmirror-downloader/build.gradle.kts
Normal file
16
downloaders/apkmirror-downloader/build.gradle.kts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlin.parcelize)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
val packageName = "app.revanced.manager.plugin.downloader.apkmirror"
|
||||||
|
namespace = packageName
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":arsclib"))
|
||||||
|
implementation(project(":shared"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-feature android:name="app.revanced.manager.plugin.downloader" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
tools:targetApi="35">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="app.revanced.manager.plugin.downloader.class"
|
||||||
|
android:value="app.revanced.manager.plugin.downloader.apkmirror.APKMirrorDownloaderKt" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
@file:Suppress("Unused")
|
||||||
|
|
||||||
|
package app.revanced.manager.plugin.downloader.apkmirror
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.util.Log
|
||||||
|
import app.revanced.manager.plugin.downloader.DownloadUrl
|
||||||
|
import app.revanced.manager.plugin.downloader.Downloader
|
||||||
|
import app.revanced.manager.plugin.downloader.download
|
||||||
|
import app.revanced.manager.plugin.downloader.webview.runWebView
|
||||||
|
import app.revanced.manager.plugin.utils.Merger
|
||||||
|
import com.reandroid.apk.APKLogger
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URI
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
import kotlin.io.path.ExperimentalPathApi
|
||||||
|
import kotlin.io.path.deleteRecursively
|
||||||
|
import kotlin.io.path.outputStream
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class ApkMirrorApp(
|
||||||
|
val downloadUrl: DownloadUrl,
|
||||||
|
val packageName: String
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
private object ArscLogger : APKLogger {
|
||||||
|
const val TAG = "ARSCLib"
|
||||||
|
|
||||||
|
override fun logMessage(msg: String) {
|
||||||
|
Log.i(TAG, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logError(msg: String, tr: Throwable?) {
|
||||||
|
Log.e(TAG, msg, tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logVerbose(msg: String) {
|
||||||
|
Log.v(TAG, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPathApi::class)
|
||||||
|
val ApkMirrorDownloader = Downloader<ApkMirrorApp> {
|
||||||
|
get { packageName, version ->
|
||||||
|
ApkMirrorApp(
|
||||||
|
downloadUrl = runWebView("APKMirror") {
|
||||||
|
download { url, _, userAgent ->
|
||||||
|
finish(
|
||||||
|
DownloadUrl(
|
||||||
|
url,
|
||||||
|
mapOf("User-Agent" to userAgent)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
with(Uri.Builder()) {
|
||||||
|
scheme("https")
|
||||||
|
authority("www.apkmirror.com")
|
||||||
|
mapOf(
|
||||||
|
"post_type" to "app_release",
|
||||||
|
"searchtype" to "apk",
|
||||||
|
"s" to (version?.let { "$packageName $it" } ?: packageName),
|
||||||
|
"bundles%5B%5D" to "apk_files" // bundles[]
|
||||||
|
).forEach { (key, value) ->
|
||||||
|
appendQueryParameter(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
build().toString()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
packageName = packageName
|
||||||
|
) to version
|
||||||
|
}
|
||||||
|
|
||||||
|
download { app, outputStream ->
|
||||||
|
val workingDir = Files.createTempDirectory("apkmirror_dl")
|
||||||
|
val downloadedFile = workingDir.resolve("${app.packageName}.apk").also {
|
||||||
|
it.outputStream().use { output ->
|
||||||
|
app.downloadUrl.toDownloadResult().first.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (URI(app.downloadUrl.url).path.substringAfterLast('/').endsWith(".apk")) {
|
||||||
|
Files.copy(downloadedFile, outputStream)
|
||||||
|
} else {
|
||||||
|
val xapkWorkingDir = workingDir.resolve("xapk").also { it.toFile().mkdirs() }
|
||||||
|
|
||||||
|
ZipFile(downloadedFile.toString()).use { zip ->
|
||||||
|
zip.entries().asSequence().forEach { entry ->
|
||||||
|
xapkWorkingDir.resolve(entry.name).also { it.parent.toFile().mkdirs() }.also { outputFile ->
|
||||||
|
zip.getInputStream(entry).use { input ->
|
||||||
|
File(outputFile.toString()).outputStream().use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Merger.merge(xapkWorkingDir, ArscLogger).writeApk(outputStream)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
workingDir.deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">ReVanced Manager: APKMirror downloader</string>
|
||||||
|
</resources>
|
||||||
34
downloaders/play-store-downloader/build.gradle.kts
Normal file
34
downloaders/play-store-downloader/build.gradle.kts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlin.parcelize)
|
||||||
|
alias(libs.plugins.compose.compiler)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
val packageName = "app.revanced.manager.plugin.downloader.play.store"
|
||||||
|
namespace = packageName
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.gplayapi)
|
||||||
|
implementation(project(":arsclib"))
|
||||||
|
implementation(project(":shared"))
|
||||||
|
|
||||||
|
implementation(libs.ktor.core)
|
||||||
|
implementation(libs.ktor.logging)
|
||||||
|
implementation(libs.ktor.okhttp)
|
||||||
|
|
||||||
|
implementation(libs.compose.activity)
|
||||||
|
implementation(platform(libs.compose.bom))
|
||||||
|
implementation(libs.compose.ui)
|
||||||
|
implementation(libs.compose.ui.tooling)
|
||||||
|
implementation(libs.compose.material3)
|
||||||
|
implementation(libs.compose.webview)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
aidl = true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-feature android:name="app.revanced.manager.plugin.downloader" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
tools:targetApi="34">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.AuthActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="app.revanced.manager.permission.PLUGIN_HOST"
|
||||||
|
android:theme="@android:style/Theme.DeviceDefault" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".service.CredentialProviderService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="app.revanced.manager.permission.PLUGIN_HOST" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="app.revanced.manager.plugin.downloader.class"
|
||||||
|
android:value="app.revanced.manager.plugin.downloader.play.store.PlayStorePluginKt" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// ICredentialProvider.aidl
|
||||||
|
package app.revanced.manager.plugin.downloader.play.store;
|
||||||
|
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.data.Credentials;
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.data.ParcelProperties;
|
||||||
|
|
||||||
|
interface ICredentialProvider {
|
||||||
|
@nullable Credentials retrieveCredentials();
|
||||||
|
ParcelProperties getProperties();
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Credentials.aidl
|
||||||
|
package app.revanced.manager.plugin.downloader.play.store.data;
|
||||||
|
|
||||||
|
parcelable Credentials;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// ParcelProperties.aidl
|
||||||
|
package app.revanced.manager.plugin.downloader.play.store.data;
|
||||||
|
|
||||||
|
parcelable ParcelProperties;
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package app.revanced.manager.plugin.downloader.play.store
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.util.Log
|
||||||
|
import app.revanced.manager.plugin.downloader.*
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.data.Credentials
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.data.Http
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.service.CredentialProviderService
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.ui.AuthActivity
|
||||||
|
import app.revanced.manager.plugin.utils.Merger
|
||||||
|
import com.aurora.gplayapi.data.models.File as GPlayFile
|
||||||
|
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||||
|
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||||
|
import com.reandroid.apk.APKLogger
|
||||||
|
import io.ktor.client.request.url
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.StandardOpenOption
|
||||||
|
import java.util.Properties
|
||||||
|
import kotlin.io.path.ExperimentalPathApi
|
||||||
|
import kotlin.io.path.deleteRecursively
|
||||||
|
import kotlin.io.path.listDirectoryEntries
|
||||||
|
import kotlin.io.path.outputStream
|
||||||
|
|
||||||
|
private val allowedFileTypes = arrayOf(GPlayFile.FileType.BASE, GPlayFile.FileType.SPLIT)
|
||||||
|
const val LOG_TAG = "PlayStorePlugin"
|
||||||
|
|
||||||
|
private object ArscLogger : APKLogger {
|
||||||
|
const val TAG = "ARSCLib"
|
||||||
|
|
||||||
|
override fun logMessage(msg: String) {
|
||||||
|
Log.i(TAG, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logError(msg: String, tr: Throwable?) {
|
||||||
|
Log.e(TAG, msg, tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logVerbose(msg: String) {
|
||||||
|
Log.v(TAG, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class GPlayApp(
|
||||||
|
val files: List<GPlayFile>
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
@Suppress("Unused")
|
||||||
|
@OptIn(ExperimentalPathApi::class)
|
||||||
|
val playStoreDownloader = Downloader<GPlayApp> {
|
||||||
|
get { packageName, version ->
|
||||||
|
val (credentials, deviceProps) = useService<CredentialProviderService, Pair<Credentials, Properties>> { binder ->
|
||||||
|
val credentialProvider = ICredentialProvider.Stub.asInterface(binder)
|
||||||
|
val props = credentialProvider.properties.value
|
||||||
|
credentialProvider.retrieveCredentials()?.let { return@useService it to props }
|
||||||
|
|
||||||
|
try {
|
||||||
|
requestStartActivity<AuthActivity>()
|
||||||
|
} catch (e: UserInteractionException.Activity.NotCompleted) {
|
||||||
|
if (e.resultCode == AuthActivity.RESULT_FAILED) throw Exception(
|
||||||
|
"Login failed: ${
|
||||||
|
e.intent?.getStringExtra(
|
||||||
|
AuthActivity.FAILURE_MESSAGE_KEY
|
||||||
|
)
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialProvider.retrieveCredentials()?.let { it to props } ?: throw Exception("Could not get credentials")
|
||||||
|
}
|
||||||
|
val authData = credentials.toAuthData(deviceProps)
|
||||||
|
|
||||||
|
val app = try {
|
||||||
|
AppDetailsHelper(authData).using(Http).getAppByPackageName(packageName)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(LOG_TAG, "Got exception while trying to get app", e)
|
||||||
|
null
|
||||||
|
}?.takeUnless { version != null && it.versionName != version } ?: return@get null
|
||||||
|
if (!app.isFree) return@get null
|
||||||
|
|
||||||
|
GPlayApp(
|
||||||
|
app.fileList.filterNot { it.url.isBlank() }.ifEmpty {
|
||||||
|
PurchaseHelper(authData).using(Http).purchase(
|
||||||
|
app.packageName,
|
||||||
|
app.versionCode,
|
||||||
|
app.offerType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) to app.versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
download { app, outputStream ->
|
||||||
|
val apkDir = Files.createTempDirectory("play_dl")
|
||||||
|
try {
|
||||||
|
if (app.files.isEmpty()) error("No valid files to download")
|
||||||
|
app.files.forEach { file ->
|
||||||
|
if (file.type !in allowedFileTypes) error("${file.name} could not be downloaded because it has an unsupported type: ${file.type.name}")
|
||||||
|
apkDir.resolve(file.name).outputStream(StandardOpenOption.CREATE_NEW)
|
||||||
|
.use { stream ->
|
||||||
|
Http.download(stream) {
|
||||||
|
url(file.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val apkFiles = apkDir.listDirectoryEntries()
|
||||||
|
if (apkFiles.size == 1)
|
||||||
|
Files.copy(apkFiles.first(), outputStream)
|
||||||
|
else
|
||||||
|
Merger.merge(apkDir, ArscLogger).writeApk(outputStream)
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
apkDir.deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package app.revanced.manager.plugin.downloader.play.store.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.aurora.gplayapi.helpers.AuthHelper
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import java.util.Properties
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Credentials(val email: String, val aasToken: String) : Parcelable {
|
||||||
|
fun toAuthData(deviceProperties: Properties) =
|
||||||
|
AuthHelper.using(Http)
|
||||||
|
.build(email, aasToken, AuthHelper.Token.AAS, properties = deviceProperties)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Context.credentialsSharedPrefs() =
|
||||||
|
getSharedPreferences("credentials", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
fun Context.saveCredentials(credentials: Credentials) {
|
||||||
|
with(credentialsSharedPrefs().edit()) {
|
||||||
|
putString(EMAIL_KEY, credentials.email)
|
||||||
|
putString(AAS_TOKEN_KEY, credentials.aasToken)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.readSavedCredentials() = with(credentialsSharedPrefs()) {
|
||||||
|
val email = getString(EMAIL_KEY, null) ?: return@with null
|
||||||
|
val aasToken = getString(AAS_TOKEN_KEY, null) ?: return@with null
|
||||||
|
|
||||||
|
Credentials(email, aasToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val EMAIL_KEY = "email"
|
||||||
|
private const val AAS_TOKEN_KEY = "aas_token"
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package app.revanced.manager.plugin.downloader.play.store.data
|
||||||
|
|
||||||
|
import com.aurora.gplayapi.data.models.PlayResponse
|
||||||
|
import com.aurora.gplayapi.network.IHttpClient
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.call.body
|
||||||
|
import io.ktor.client.engine.okhttp.OkHttp
|
||||||
|
import io.ktor.client.plugins.HttpTimeout
|
||||||
|
import io.ktor.client.request.HttpRequestBuilder
|
||||||
|
import io.ktor.client.request.headers
|
||||||
|
import io.ktor.client.request.parameter
|
||||||
|
import io.ktor.client.request.prepareGet
|
||||||
|
import io.ktor.client.request.request
|
||||||
|
import io.ktor.client.request.setBody
|
||||||
|
import io.ktor.client.statement.bodyAsChannel
|
||||||
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.utils.io.jvm.javaio.copyTo
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
object Http : IHttpClient {
|
||||||
|
val client = HttpClient(OkHttp) {
|
||||||
|
install(HttpTimeout) {
|
||||||
|
socketTimeoutMillis = 10000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _responseCode = MutableStateFlow(100)
|
||||||
|
override val responseCode = _responseCode.asStateFlow()
|
||||||
|
|
||||||
|
suspend inline fun download(
|
||||||
|
outputStream: OutputStream,
|
||||||
|
block: HttpRequestBuilder.() -> Unit
|
||||||
|
) = client.prepareGet(block).execute {
|
||||||
|
it.bodyAsChannel().copyTo(outputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playRequest(
|
||||||
|
method: HttpMethod,
|
||||||
|
url: String,
|
||||||
|
headers: Map<String, String>,
|
||||||
|
builder: suspend HttpRequestBuilder.() -> Unit = {}
|
||||||
|
) = runBlocking {
|
||||||
|
val response = client.request(url) {
|
||||||
|
this.method = method
|
||||||
|
this.headers {
|
||||||
|
headers.forEach { (name, value) ->
|
||||||
|
append(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder()
|
||||||
|
}
|
||||||
|
|
||||||
|
_responseCode.apply {
|
||||||
|
// State flows will not emit the same value twice
|
||||||
|
value = 0
|
||||||
|
value = response.status.value
|
||||||
|
}
|
||||||
|
|
||||||
|
val success = response.status.value in 200..299
|
||||||
|
PlayResponse(
|
||||||
|
code = response.status.value,
|
||||||
|
isSuccessful = success,
|
||||||
|
responseBytes = response.body(),
|
||||||
|
errorString = if (!success) response.status.description else ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(url: String, headers: Map<String, String>) =
|
||||||
|
playRequest(HttpMethod.Get, url, headers)
|
||||||
|
|
||||||
|
override fun get(
|
||||||
|
url: String,
|
||||||
|
headers: Map<String, String>,
|
||||||
|
params: Map<String, String>
|
||||||
|
) = playRequest(HttpMethod.Get, url, headers) {
|
||||||
|
params.forEach { (name, value) ->
|
||||||
|
parameter(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(url: String, headers: Map<String, String>, paramString: String) = playRequest(
|
||||||
|
HttpMethod.Get, url + paramString, headers
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getAuth(url: String) = PlayResponse(
|
||||||
|
isSuccessful = false,
|
||||||
|
code = 444
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun post(url: String, headers: Map<String, String>, body: ByteArray) = playRequest(
|
||||||
|
HttpMethod.Post, url, headers
|
||||||
|
) {
|
||||||
|
setBody(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun post(
|
||||||
|
url: String,
|
||||||
|
headers: Map<String, String>,
|
||||||
|
params: Map<String, String>
|
||||||
|
) = playRequest(HttpMethod.Post, url, headers) {
|
||||||
|
params.forEach { (name, value) ->
|
||||||
|
parameter(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun postAuth(url: String, body: ByteArray) = getAuth(url)
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package app.revanced.manager.plugin.downloader.play.store.data
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.opengl.EGL14
|
||||||
|
import android.opengl.GLES10
|
||||||
|
import javax.microedition.khronos.egl.EGL10
|
||||||
|
import javax.microedition.khronos.egl.EGLConfig
|
||||||
|
import javax.microedition.khronos.egl.EGLContext
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import java.util.Properties
|
||||||
|
import javax.microedition.khronos.egl.EGLDisplay
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class ParcelProperties(val value: Properties) : Parcelable
|
||||||
|
|
||||||
|
object PropertiesProvider {
|
||||||
|
fun createDeviceProperties(context: Context) = with(context) {
|
||||||
|
Properties().apply {
|
||||||
|
//Build Props
|
||||||
|
setProperty("UserReadableName", "${Build.DEVICE}-default")
|
||||||
|
setProperty("Build.HARDWARE", Build.HARDWARE)
|
||||||
|
setProperty("Build.RADIO", Build.getRadioVersion() ?: "unknown")
|
||||||
|
setProperty("Build.FINGERPRINT", Build.FINGERPRINT)
|
||||||
|
setProperty("Build.BRAND", Build.BRAND)
|
||||||
|
setProperty("Build.DEVICE", Build.DEVICE)
|
||||||
|
setProperty("Build.VERSION.SDK_INT", "${Build.VERSION.SDK_INT}")
|
||||||
|
setProperty("Build.VERSION.RELEASE", Build.VERSION.RELEASE)
|
||||||
|
setProperty("Build.MODEL", Build.MODEL)
|
||||||
|
setProperty("Build.MANUFACTURER", Build.MANUFACTURER)
|
||||||
|
setProperty("Build.PRODUCT", Build.PRODUCT)
|
||||||
|
setProperty("Build.ID", Build.ID)
|
||||||
|
setProperty("Build.BOOTLOADER", Build.BOOTLOADER)
|
||||||
|
|
||||||
|
val config = resources.configuration
|
||||||
|
setProperty("TouchScreen", "${config.touchscreen}")
|
||||||
|
setProperty("Keyboard", "${config.keyboard}")
|
||||||
|
setProperty("Navigation", "${config.navigation}")
|
||||||
|
setProperty("ScreenLayout", "${config.screenLayout and 15}")
|
||||||
|
setProperty("HasHardKeyboard", "${config.keyboard == Configuration.KEYBOARD_QWERTY}")
|
||||||
|
setProperty(
|
||||||
|
"HasFiveWayNavigation",
|
||||||
|
"${config.navigation == Configuration.NAVIGATIONHIDDEN_YES}"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Display Metrics
|
||||||
|
val metrics = resources.displayMetrics
|
||||||
|
setProperty("Screen.Density", "${metrics.densityDpi}")
|
||||||
|
setProperty("Screen.Width", "${metrics.widthPixels}")
|
||||||
|
setProperty("Screen.Height", "${metrics.heightPixels}")
|
||||||
|
|
||||||
|
|
||||||
|
//Supported Platforms
|
||||||
|
setProperty("Platforms", Build.SUPPORTED_ABIS.commaSeparated())
|
||||||
|
//Supported Features
|
||||||
|
setProperty("Features", features.commaSeparated())
|
||||||
|
//Shared Locales
|
||||||
|
setProperty("Locales", locales.commaSeparated())
|
||||||
|
//Shared Libraries
|
||||||
|
setProperty("SharedLibraries", sharedLibraries.commaSeparated())
|
||||||
|
//GL Extensions
|
||||||
|
setProperty(
|
||||||
|
"GL.Version",
|
||||||
|
getSystemService<ActivityManager>()!!.deviceConfigurationInfo.reqGlEsVersion.toString()
|
||||||
|
)
|
||||||
|
setProperty(
|
||||||
|
"GL.Extensions",
|
||||||
|
getEglExtensions().commaSeparated()
|
||||||
|
)
|
||||||
|
|
||||||
|
//Google Related Props
|
||||||
|
setProperty("Client", "android-google")
|
||||||
|
setProperty("GSF.version", "203615037")
|
||||||
|
setProperty("Vending.version", "82201710")
|
||||||
|
setProperty("Vending.versionString", "22.0.17-21 [0] [PR] 332555730")
|
||||||
|
|
||||||
|
//MISC
|
||||||
|
setProperty("Roaming", "mobile-notroaming")
|
||||||
|
setProperty("TimeZone", "UTC-10")
|
||||||
|
|
||||||
|
//Telephony (USA 3650 AT&T)
|
||||||
|
setProperty("CellOperator", "310")
|
||||||
|
setProperty("SimOperator", "38")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Array<String>.commaSeparated() = joinToString(separator = ",")
|
||||||
|
private fun Iterable<String>.commaSeparated() = joinToString(separator = ",")
|
||||||
|
|
||||||
|
private val Context.features
|
||||||
|
get() = packageManager.systemAvailableFeatures.map { it.name }
|
||||||
|
.filterNot(String::isNullOrEmpty)
|
||||||
|
private val Context.locales
|
||||||
|
get() = assets.locales.filter(String::isNullOrEmpty).map { it.replace("-", "_") }
|
||||||
|
private val Context.sharedLibraries
|
||||||
|
get() = packageManager.systemSharedLibraryNames.orEmpty().filterNotNull()
|
||||||
|
|
||||||
|
private fun getEglExtensions() = buildSet {
|
||||||
|
val egl = EGLContext.getEGL() as EGL10
|
||||||
|
val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
|
||||||
|
egl.eglInitialize(display, IntArray(2))
|
||||||
|
try {
|
||||||
|
val numConfigsContainer = IntArray(1)
|
||||||
|
if (!egl.eglGetConfigs(display, null, 0, numConfigsContainer)) return@buildSet
|
||||||
|
val numConfigs = numConfigsContainer[0]
|
||||||
|
val configs = arrayOfNulls<EGLConfig>(numConfigs).apply {
|
||||||
|
if (!egl.eglGetConfigs(
|
||||||
|
display,
|
||||||
|
this,
|
||||||
|
numConfigs,
|
||||||
|
numConfigsContainer
|
||||||
|
)
|
||||||
|
) return@buildSet
|
||||||
|
}.requireNoNulls()
|
||||||
|
|
||||||
|
configs.forEach {
|
||||||
|
val resultContainer = IntArray(1)
|
||||||
|
egl.eglGetConfigAttrib(display, it, EGL10.EGL_CONFIG_CAVEAT, resultContainer)
|
||||||
|
if (resultContainer[0] == EGL10.EGL_SLOW_CONFIG) return@forEach
|
||||||
|
|
||||||
|
egl.eglGetConfigAttrib(display, it, EGL10.EGL_SURFACE_TYPE, resultContainer)
|
||||||
|
if (EGL10.EGL_PBUFFER_BIT and resultContainer[0] == 0) return@forEach
|
||||||
|
|
||||||
|
egl.eglGetConfigAttrib(display, it, EGL10.EGL_RENDERABLE_TYPE, resultContainer)
|
||||||
|
if (EGL14.EGL_OPENGL_ES_BIT and resultContainer[0] != 0) addEglExtensions(
|
||||||
|
egl,
|
||||||
|
display,
|
||||||
|
it,
|
||||||
|
null,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
if (EGL14.EGL_OPENGL_ES2_BIT and resultContainer[0] != 0) addEglExtensions(
|
||||||
|
egl,
|
||||||
|
display,
|
||||||
|
it,
|
||||||
|
openGlEs2AttribList,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
egl.eglTerminate(display)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addEglExtensions(
|
||||||
|
egl: EGL10,
|
||||||
|
eglDisplay: EGLDisplay,
|
||||||
|
eglConfig: EGLConfig,
|
||||||
|
eglContextAttribList: IntArray?,
|
||||||
|
target: MutableSet<String>
|
||||||
|
) {
|
||||||
|
val eglContext =
|
||||||
|
egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, eglContextAttribList)
|
||||||
|
if (eglContext === EGL10.EGL_NO_CONTEXT) return
|
||||||
|
|
||||||
|
val eglSurface = egl.eglCreatePbufferSurface(eglDisplay, eglConfig, eglSurfaceAttribList)
|
||||||
|
if (eglSurface === EGL10.EGL_NO_SURFACE) {
|
||||||
|
egl.eglDestroyContext(eglDisplay, eglContext)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)
|
||||||
|
val extensionsString = GLES10.glGetString(GLES10.GL_EXTENSIONS)
|
||||||
|
if (!extensionsString.isNullOrEmpty()) {
|
||||||
|
val extensions = extensionsString.split(" ".toRegex())
|
||||||
|
target.addAll(extensions)
|
||||||
|
}
|
||||||
|
egl.eglMakeCurrent(
|
||||||
|
eglDisplay,
|
||||||
|
EGL10.EGL_NO_SURFACE,
|
||||||
|
EGL10.EGL_NO_SURFACE,
|
||||||
|
EGL10.EGL_NO_CONTEXT
|
||||||
|
)
|
||||||
|
egl.eglDestroySurface(eglDisplay, eglSurface)
|
||||||
|
egl.eglDestroyContext(eglDisplay, eglContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val eglSurfaceAttribList
|
||||||
|
get() = intArrayOf(
|
||||||
|
EGL10.EGL_WIDTH,
|
||||||
|
EGL10.EGL_PBUFFER_BIT,
|
||||||
|
EGL10.EGL_HEIGHT,
|
||||||
|
EGL10.EGL_PBUFFER_BIT,
|
||||||
|
EGL10.EGL_NONE
|
||||||
|
)
|
||||||
|
|
||||||
|
private val openGlEs2AttribList
|
||||||
|
get() = intArrayOf(
|
||||||
|
EGL14.EGL_CONTEXT_CLIENT_VERSION,
|
||||||
|
EGL10.EGL_PIXMAP_BIT,
|
||||||
|
EGL10.EGL_NONE
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.manager.plugin.downloader.play.store.service
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.ICredentialProvider
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.data.ParcelProperties
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.data.PropertiesProvider
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.data.readSavedCredentials
|
||||||
|
|
||||||
|
class CredentialProviderService : Service() {
|
||||||
|
private val binder = object : ICredentialProvider.Stub() {
|
||||||
|
override fun retrieveCredentials() = try {
|
||||||
|
readSavedCredentials()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("CredentialService", "Got exception while retrieving credentials", e)
|
||||||
|
throw IllegalStateException("An exception was raised when reading credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getProperties() =
|
||||||
|
ParcelProperties(PropertiesProvider.createDeviceProperties(this@CredentialProviderService))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder = binder.asBinder()
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package app.revanced.manager.plugin.downloader.play.store.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import com.kevinnzou.web.WebView
|
||||||
|
import com.kevinnzou.web.rememberWebViewState
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class AuthActivity : ComponentActivity() {
|
||||||
|
private val vm: AuthActivityViewModel by viewModels()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
val (code, intent) = vm.awaitActivityResultCode()
|
||||||
|
setResult(code, intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
val colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
|
||||||
|
|
||||||
|
MaterialTheme(colorScheme) {
|
||||||
|
Scaffold { paddingValues ->
|
||||||
|
Column(modifier = Modifier.padding(paddingValues)) {
|
||||||
|
val state =
|
||||||
|
rememberWebViewState(url = AuthActivityViewModel.EMBEDDED_SETUP_URL)
|
||||||
|
|
||||||
|
WebView(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
state = state,
|
||||||
|
onCreated = vm::setupWebView,
|
||||||
|
client = vm.webViewClient
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val RESULT_FAILED = RESULT_FIRST_USER + 1
|
||||||
|
const val FAILURE_MESSAGE_KEY = "FAIL_MESSAGE"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package app.revanced.manager.plugin.downloader.play.store.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import android.webkit.CookieManager
|
||||||
|
import android.webkit.WebSettings
|
||||||
|
import android.webkit.WebView
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.LOG_TAG
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.data.Credentials
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.data.Http
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.data.PropertiesProvider
|
||||||
|
import app.revanced.manager.plugin.downloader.play.store.data.saveCredentials
|
||||||
|
import com.aurora.gplayapi.helpers.AuthValidator
|
||||||
|
import com.kevinnzou.web.AccompanistWebViewClient
|
||||||
|
import io.ktor.client.request.headers
|
||||||
|
import io.ktor.client.request.post
|
||||||
|
import io.ktor.client.request.setBody
|
||||||
|
import io.ktor.client.statement.bodyAsText
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
|
import io.ktor.http.append
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.StringTokenizer
|
||||||
|
|
||||||
|
class AuthActivityViewModel(app: Application) : AndroidViewModel(app) {
|
||||||
|
private val cookieManager = CookieManager.getInstance()!!
|
||||||
|
private val activityResultCode =
|
||||||
|
CompletableDeferred<Pair<Int, Intent?>>(parent = viewModelScope.coroutineContext.job)
|
||||||
|
|
||||||
|
val webViewClient = object : AccompanistWebViewClient() {
|
||||||
|
override fun onPageFinished(view: WebView, url: String?) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
|
||||||
|
val cookieString = cookieManager.getCookie(url) ?: return
|
||||||
|
val cookies = buildMap {
|
||||||
|
putAll(cookiePattern.findAll(cookieString).map {
|
||||||
|
val (key, value) = it.destructured
|
||||||
|
key to value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
cookies[OAUTH_COOKIE]?.let { token ->
|
||||||
|
view.evaluateJavascript(EXTRACT_EMAIL_JS) {
|
||||||
|
val email = it.replace("\"".toRegex(), "")
|
||||||
|
getAndSaveAasToken(email, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
cookieManager.removeAllCookies(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun awaitActivityResultCode() = activityResultCode.await()
|
||||||
|
private fun finishActivity(code: Int, intent: Intent? = null) =
|
||||||
|
activityResultCode.complete(code to intent)
|
||||||
|
|
||||||
|
private fun getAndSaveAasToken(email: String, oauthToken: String) = viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val response = getAC2DMResponse(email, oauthToken)
|
||||||
|
val aasToken = response["Token"] ?: throw Exception("AC2DM did not return a token")
|
||||||
|
val credentials = Credentials(email, aasToken)
|
||||||
|
val context = getApplication<Application>()
|
||||||
|
val deviceProps = PropertiesProvider.createDeviceProperties(context)
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val authData = credentials.toAuthData(deviceProps)
|
||||||
|
val validator = AuthValidator(authData).using(Http)
|
||||||
|
|
||||||
|
if (!validator.isValid()) throw Exception("Credential validation failed")
|
||||||
|
context.saveCredentials(credentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
finishActivity(Activity.RESULT_OK)
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(LOG_TAG, "Could not get and save token", e)
|
||||||
|
finishActivity(AuthActivity.RESULT_FAILED, Intent().apply {
|
||||||
|
putExtra(AuthActivity.FAILURE_MESSAGE_KEY, e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getAC2DMResponse(email: String, oauthToken: String): Map<String, String> {
|
||||||
|
val locale = Locale.getDefault()
|
||||||
|
val formData = mapOf(
|
||||||
|
"lang" to locale.toString().replace("_", "-"),
|
||||||
|
"google_play_services_version" to PLAY_SERVICES_VERSION_CODE,
|
||||||
|
"sdk_version" to BUILD_VERSION_SDK,
|
||||||
|
"device_country" to locale.country.lowercase(Locale.US),
|
||||||
|
"Email" to email,
|
||||||
|
"service" to "ac2dm",
|
||||||
|
"get_accountid" to 1,
|
||||||
|
"ACCESS_TOKEN" to 1,
|
||||||
|
"callerPkg" to GMS_PACKAGE_NAME,
|
||||||
|
"add_account" to 1,
|
||||||
|
"Token" to oauthToken,
|
||||||
|
"callerSig" to CALLER_SIGNATURE
|
||||||
|
).entries.joinToString(separator = "&") { (key, value) -> "$key=$value" }
|
||||||
|
|
||||||
|
val response = Http.client.post(TOKEN_AUTH_URL) {
|
||||||
|
headers {
|
||||||
|
set("app", GMS_PACKAGE_NAME)
|
||||||
|
set(HttpHeaders.UserAgent, "")
|
||||||
|
append(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
setBody(formData)
|
||||||
|
}
|
||||||
|
|
||||||
|
val body = response.bodyAsText()
|
||||||
|
return buildMap {
|
||||||
|
val st = StringTokenizer(body, "\n\r")
|
||||||
|
while (st.hasMoreTokens()) {
|
||||||
|
val (key, value) = st.nextToken().split("=", limit = 2)
|
||||||
|
put(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
fun setupWebView(webView: WebView) = webView.apply {
|
||||||
|
cookieManager.acceptThirdPartyCookies(this)
|
||||||
|
cookieManager.setAcceptThirdPartyCookies(this, true)
|
||||||
|
|
||||||
|
settings.apply {
|
||||||
|
safeBrowsingEnabled = false
|
||||||
|
allowContentAccess = true
|
||||||
|
databaseEnabled = true
|
||||||
|
domStorageEnabled = true
|
||||||
|
javaScriptEnabled = true
|
||||||
|
cacheMode = WebSettings.LOAD_DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object Constants {
|
||||||
|
private const val OAUTH_COOKIE = "oauth_token"
|
||||||
|
private const val EXTRACT_EMAIL_JS =
|
||||||
|
"(function() { return document.querySelector('[data-profile-identifier]').innerText; })();"
|
||||||
|
const val EMBEDDED_SETUP_URL = "https://accounts.google.com/EmbeddedSetup"
|
||||||
|
private const val TOKEN_AUTH_URL = "https://android.clients.google.com/auth"
|
||||||
|
private const val BUILD_VERSION_SDK = 28
|
||||||
|
private const val PLAY_SERVICES_VERSION_CODE = 19629032
|
||||||
|
private const val GMS_PACKAGE_NAME = "com.google.android.gms"
|
||||||
|
private const val CALLER_SIGNATURE = "38918a453d07199354f8b19af05ec6562ced5788"
|
||||||
|
private val cookiePattern = "([^=]+)=([^;]*);?\\s?".toRegex()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">ReVanced Manager: Play Store downloader</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Sample backup rules file; uncomment and customize as necessary.
|
||||||
|
See https://developer.android.com/guide/topics/data/autobackup
|
||||||
|
for details.
|
||||||
|
Note: This file is ignored for devices older that API 31
|
||||||
|
See https://developer.android.com/about/versions/12/backup-restore
|
||||||
|
-->
|
||||||
|
<full-backup-content>
|
||||||
|
<!--
|
||||||
|
<include domain="sharedpref" path="."/>
|
||||||
|
<exclude domain="sharedpref" path="device.xml"/>
|
||||||
|
-->
|
||||||
|
</full-backup-content>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Sample data extraction rules file; uncomment and customize as necessary.
|
||||||
|
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||||
|
for details.
|
||||||
|
-->
|
||||||
|
<data-extraction-rules>
|
||||||
|
<cloud-backup>
|
||||||
|
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||||
|
<include .../>
|
||||||
|
<exclude .../>
|
||||||
|
-->
|
||||||
|
</cloud-backup>
|
||||||
|
<!--
|
||||||
|
<device-transfer>
|
||||||
|
<include .../>
|
||||||
|
<exclude .../>
|
||||||
|
</device-transfer>
|
||||||
|
-->
|
||||||
|
</data-extraction-rules>
|
||||||
@@ -1,7 +1,24 @@
|
|||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs = -Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. For more details, visit
|
||||||
|
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
android.nonTransitiveRClass = true
|
# Kotlin code style for this project: "official" or "obsolete":
|
||||||
android.nonFinalResIds = false
|
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
org.gradle.parallel = true
|
# Enables namespacing of each library's R class so that its R class includes only the
|
||||||
org.gradle.caching = true
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
version = 1.0.0
|
# thereby reducing the size of the R class for that library
|
||||||
|
android.nonTransitiveRClass = true
|
||||||
|
version = 1.0.0-dev.1
|
||||||
|
|||||||
@@ -1,11 +1,36 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
agp = "8.7.3"
|
||||||
|
kotlin = "2.1.10"
|
||||||
|
gplayapi = "3.4.4"
|
||||||
|
arsclib = "1.3.5"
|
||||||
|
kotlinx-coroutines-core = "1.9.0"
|
||||||
|
ktor = "2.3.9"
|
||||||
|
|
||||||
|
compose-activity = "1.10.1"
|
||||||
|
ui-tooling = "1.8.2"
|
||||||
|
compose-bom = "2025.06.00"
|
||||||
|
material3 = "1.3.2"
|
||||||
|
compose-webview = "0.33.6"
|
||||||
|
|
||||||
plugin-api = "1.0.0"
|
plugin-api = "1.0.0"
|
||||||
android-gradle-plugin = "8.7.3"
|
|
||||||
kotlin = "2.1.0"
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
||||||
plugin-api = { group = "app.revanced", name = "revanced-manager-downloader-api", version.ref = "plugin-api" }
|
plugin-api = { group = "app.revanced", name = "revanced-manager-downloader-api", version.ref = "plugin-api" }
|
||||||
|
gplayapi = { group = "com.auroraoss", name = "gplayapi", version.ref = "gplayapi" }
|
||||||
|
arsclib = { group = "io.github.reandroid", name = "ARSCLib", version.ref = "arsclib" }
|
||||||
|
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
|
||||||
|
compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
|
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "ui-tooling" }
|
||||||
|
compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
|
||||||
|
compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "compose-activity" }
|
||||||
|
compose-webview = { group = "io.github.kevinnzou", name = "compose-webview", version.ref = "compose-webview" }
|
||||||
|
ktor-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
|
||||||
|
ktor-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" }
|
||||||
|
ktor-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
|
||||||
|
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,8 +1,6 @@
|
|||||||
|
#Tue Jul 23 17:28:58 CEST 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
285
gradlew
vendored
285
gradlew
vendored
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright 2015 the original author or authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -15,104 +15,69 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
##
|
||||||
# Gradle start up script for POSIX generated by Gradle.
|
## Gradle start up script for UN*X
|
||||||
#
|
##
|
||||||
# Important for running:
|
|
||||||
#
|
|
||||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
|
||||||
# noncompliant, but you have some other compliant shell such as ksh or
|
|
||||||
# bash, then to run this script, type that shell name before the whole
|
|
||||||
# command line, like:
|
|
||||||
#
|
|
||||||
# ksh Gradle
|
|
||||||
#
|
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
|
||||||
# requires all of these POSIX shell features:
|
|
||||||
# * functions;
|
|
||||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
|
||||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
|
||||||
# * compound commands having a testable exit status, especially «case»;
|
|
||||||
# * various built-in commands including «command», «set», and «ulimit».
|
|
||||||
#
|
|
||||||
# Important for patching:
|
|
||||||
#
|
|
||||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
|
||||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
|
||||||
#
|
|
||||||
# The "traditional" practice of packing multiple parameters into a
|
|
||||||
# space-separated string is a well documented source of bugs and security
|
|
||||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
|
||||||
# options in "$@", and eventually passing that to Java.
|
|
||||||
#
|
|
||||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
|
||||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
|
||||||
# see the in-line comments for details.
|
|
||||||
#
|
|
||||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
|
||||||
# Darwin, MinGW, and NonStop.
|
|
||||||
#
|
|
||||||
# (3) This script is generated from the Groovy template
|
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
|
||||||
# within the Gradle project.
|
|
||||||
#
|
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
|
||||||
#
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
app_path=$0
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
# Need this for daisy-chained symlinks.
|
while [ -h "$PRG" ] ; do
|
||||||
while
|
ls=`ls -ld "$PRG"`
|
||||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
[ -h "$app_path" ]
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
do
|
PRG="$link"
|
||||||
ls=$( ls -ld "$app_path" )
|
else
|
||||||
link=${ls#*' -> '}
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
case $link in #(
|
fi
|
||||||
/*) app_path=$link ;; #(
|
|
||||||
*) app_path=$APP_HOME$link ;;
|
|
||||||
esac
|
|
||||||
done
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
# This is normally unused
|
APP_NAME="Gradle"
|
||||||
# shellcheck disable=SC2034
|
APP_BASE_NAME=`basename "$0"`
|
||||||
APP_BASE_NAME=${0##*/}
|
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
' "$PWD" ) || exit
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD="maximum"
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
} >&2
|
}
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
} >&2
|
}
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "$( uname )" in #(
|
case "`uname`" in
|
||||||
CYGWIN* ) cygwin=true ;; #(
|
CYGWIN* )
|
||||||
Darwin* ) darwin=true ;; #(
|
cygwin=true
|
||||||
MSYS* | MINGW* ) msys=true ;; #(
|
;;
|
||||||
NONSTOP* ) nonstop=true ;;
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
@@ -122,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
else
|
else
|
||||||
JAVACMD=$JAVA_HOME/bin/java
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
@@ -133,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD="java"
|
||||||
if ! command -v java >/dev/null 2>&1
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
then
|
|
||||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
case $MAX_FD in #(
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
max*)
|
if [ $? -eq 0 ] ; then
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
# shellcheck disable=SC2039,SC3045
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
fi
|
||||||
warn "Could not query maximum file descriptor limit"
|
ulimit -n $MAX_FD
|
||||||
esac
|
if [ $? -ne 0 ] ; then
|
||||||
case $MAX_FD in #(
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
'' | soft) :;; #(
|
fi
|
||||||
*)
|
else
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
# shellcheck disable=SC2039,SC3045
|
fi
|
||||||
ulimit -n "$MAX_FD" ||
|
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
|
||||||
esac
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command, stacking in reverse order:
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
# * args from the command line
|
if $darwin; then
|
||||||
# * the main class name
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
# * -classpath
|
fi
|
||||||
# * -D...appname settings
|
|
||||||
# * --module-path (only if needed)
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
for arg do
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
if
|
SEP=""
|
||||||
case $arg in #(
|
for dir in $ROOTDIRSRAW ; do
|
||||||
-*) false ;; # don't mess with options #(
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
SEP="|"
|
||||||
[ -e "$t" ] ;; #(
|
|
||||||
*) false ;;
|
|
||||||
esac
|
|
||||||
then
|
|
||||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
|
||||||
fi
|
|
||||||
# Roll the args list around exactly as many times as the number of
|
|
||||||
# args, so each arg winds up back in the position where it started, but
|
|
||||||
# possibly modified.
|
|
||||||
#
|
|
||||||
# NB: a `for` loop captures its iteration list before it begins, so
|
|
||||||
# changing the positional parameters here affects neither the number of
|
|
||||||
# iterations, nor the values presented in `arg`.
|
|
||||||
shift # remove old arg
|
|
||||||
set -- "$@" "$arg" # push replacement arg
|
|
||||||
done
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
# Collect all arguments for the java command:
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
|
||||||
# and any embedded shellness will be escaped.
|
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
|
||||||
# treated as '${Hostname}' itself on the command line.
|
|
||||||
|
|
||||||
set -- \
|
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
|
||||||
-classpath "$CLASSPATH" \
|
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
|
||||||
if ! command -v xargs >/dev/null 2>&1
|
|
||||||
then
|
|
||||||
die "xargs is not available"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
|
||||||
#
|
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
|
||||||
#
|
|
||||||
# In Bash we could simply go:
|
|
||||||
#
|
|
||||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
|
||||||
# set -- "${ARGS[@]}" "$@"
|
|
||||||
#
|
|
||||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
|
||||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
|
||||||
# character that might be a shell metacharacter, then use eval to reverse
|
|
||||||
# that process (while maintaining the separation between arguments), and wrap
|
|
||||||
# the whole thing up as a single "set" statement.
|
|
||||||
#
|
|
||||||
# This will of course break if any of these variables contains a newline or
|
|
||||||
# an unmatched quote.
|
|
||||||
#
|
|
||||||
|
|
||||||
eval "set -- $(
|
|
||||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
|
||||||
xargs -n1 |
|
|
||||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
|
||||||
tr '\n' ' '
|
|
||||||
)" '"$@"'
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
37
gradlew.bat
vendored
37
gradlew.bat
vendored
@@ -13,10 +13,8 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
@rem SPDX-License-Identifier: Apache-2.0
|
|
||||||
@rem
|
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -27,8 +25,7 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
@rem This is normally unused
|
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -43,13 +40,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -59,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -78,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
exit /b 1
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
1408
package-lock.json
generated
1408
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,6 @@
|
|||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1",
|
||||||
"gradle-semantic-release-plugin": "^1.10.1",
|
"gradle-semantic-release-plugin": "^1.10.1",
|
||||||
"semantic-release": "^24.1.2"
|
"semantic-release": "^24.2.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
proguard-rules.pro
vendored
22
proguard-rules.pro
vendored
@@ -1 +1,21 @@
|
|||||||
-keep public class app.revanced.manager.plugin.downloader.apkmirror.*
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -1,19 +1,24 @@
|
|||||||
rootProject.name = "revanced-manager-apkmirror-downloader"
|
rootProject.name = "revanced-manager-downloaders"
|
||||||
|
|
||||||
pluginManagement.repositories {
|
pluginManagement {
|
||||||
gradlePluginPortal()
|
repositories {
|
||||||
google()
|
gradlePluginPortal()
|
||||||
}
|
google()
|
||||||
|
maven {
|
||||||
dependencyResolutionManagement.repositories {
|
name = "GitHubPackages"
|
||||||
mavenCentral()
|
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||||
google()
|
credentials {
|
||||||
maven {
|
username = providers.gradleProperty("gpr.user").getOrElse(System.getenv("GITHUB_ACTOR"))
|
||||||
name = "GitHubPackages"
|
password = providers.gradleProperty("gpr.key").getOrElse(System.getenv("GITHUB_TOKEN"))
|
||||||
url = uri("https://maven.pkg.github.com/revanced/registry")
|
}
|
||||||
credentials {
|
|
||||||
username = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR")
|
|
||||||
password = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
include(":shared")
|
||||||
|
include(":arsclib")
|
||||||
|
file("downloaders").listFiles()
|
||||||
|
?.forEach {
|
||||||
|
include(":${it.name}")
|
||||||
|
project(":${it.name}").projectDir = file("downloaders/${it.name}")
|
||||||
|
}
|
||||||
|
|||||||
8
shared/build.gradle.kts
Normal file
8
shared/build.gradle.kts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("jvm")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
implementation(project(":arsclib"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package app.revanced.manager.plugin.utils
|
||||||
|
|
||||||
|
import com.reandroid.apk.APKLogger
|
||||||
|
import com.reandroid.apk.ApkBundle
|
||||||
|
import com.reandroid.apk.ApkModule
|
||||||
|
import com.reandroid.app.AndroidManifest
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class Merger {
|
||||||
|
companion object Factory {
|
||||||
|
suspend fun merge(apkDir: Path, arscLogger: APKLogger): ApkModule {
|
||||||
|
val closeables = mutableSetOf<Closeable>()
|
||||||
|
try {
|
||||||
|
// Merge splits
|
||||||
|
val merged = withContext(Dispatchers.Default) {
|
||||||
|
with(ApkBundle()) {
|
||||||
|
setAPKLogger(arscLogger)
|
||||||
|
loadApkDirectory(apkDir.toFile())
|
||||||
|
closeables.addAll(modules)
|
||||||
|
mergeModules().also(closeables::add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
merged.androidManifest.apply {
|
||||||
|
arrayOf(
|
||||||
|
AndroidManifest.ID_isSplitRequired,
|
||||||
|
AndroidManifest.ID_extractNativeLibs
|
||||||
|
).forEach {
|
||||||
|
applicationElement.removeAttributesWithId(it)
|
||||||
|
manifestElement.removeAttributesWithId(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayOf(
|
||||||
|
AndroidManifest.NAME_requiredSplitTypes,
|
||||||
|
AndroidManifest.NAME_splitTypes
|
||||||
|
).forEach(manifestElement::removeAttributesWithName)
|
||||||
|
|
||||||
|
val pattern = "^com\\.android\\.(stamp|vending)\\.".toRegex()
|
||||||
|
applicationElement.removeElementsIf { element ->
|
||||||
|
if (element.name != AndroidManifest.TAG_meta_data) return@removeElementsIf false
|
||||||
|
val nameAttr =
|
||||||
|
element.getAttributes { it.nameId == AndroidManifest.ID_name }
|
||||||
|
.asSequence().single()
|
||||||
|
|
||||||
|
pattern.containsMatchIn(nameAttr.valueString)
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged
|
||||||
|
} finally {
|
||||||
|
closeables.forEach(Closeable::close)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user