From 38c6aa4f6dbaa230212cd6e229609faecd615349 Mon Sep 17 00:00:00 2001
From: brosssh <44944126+brosssh@users.noreply.github.com>
Date: Thu, 3 Jul 2025 13:59:15 +0200
Subject: [PATCH] feat: Add play store downloader (#10)
---
.github/ISSUE_TEMPLATE/bug_report.yml | 14 +-
.github/ISSUE_TEMPLATE/feature_request.yml | 12 +-
.github/workflows/build_pull_request.yml | 10 +-
.github/workflows/release.yml | 15 +-
.gitignore | 140 +-
.gitmodules | 3 +
.releaserc | 5 +-
CONTRIBUTING.md | 18 +-
README.md | 31 +-
arsclib | 1 +
build.gradle.kts | 182 ++-
.../apkmirror-downloader/build.gradle.kts | 16 +
.../src/main/AndroidManifest.xml | 16 +
.../apkmirror/APKMirrorDownloader.kt | 110 ++
.../res/drawable/ic_launcher_background.xml | 170 ++
.../res/drawable/ic_launcher_foreground.xml | 30 +
.../main/res/mipmap-anydpi/ic_launcher.xml | 6 +
.../res/mipmap-anydpi/ic_launcher_round.xml | 6 +
.../src/main/res/values/strings.xml | 3 +
.../play-store-downloader/build.gradle.kts | 34 +
.../src/main/AndroidManifest.xml | 29 +
.../play/store/ICredentialProvider.aidl | 10 +
.../play/store/data/Credentials.aidl | 4 +
.../play/store/data/ParcelProperties.aidl | 4 +
.../downloader/play/store/PlayStorePlugin.kt | 118 ++
.../downloader/play/store/data/Credentials.kt | 35 +
.../plugin/downloader/play/store/data/Http.kt | 110 ++
.../play/store/data/PropertiesProvider.kt | 197 +++
.../service/CredentialProviderService.kt | 26 +
.../downloader/play/store/ui/AuthActivity.kt | 63 +
.../play/store/ui/AuthActivityViewModel.kt | 161 ++
.../res/drawable/ic_launcher_background.xml | 170 ++
.../res/drawable/ic_launcher_foreground.xml | 30 +
.../main/res/mipmap-anydpi/ic_launcher.xml | 6 +
.../res/mipmap-anydpi/ic_launcher_round.xml | 6 +
.../src/main/res/values/strings.xml | 3 +
.../src/main/res/xml/backup_rules.xml | 13 +
.../main/res/xml/data_extraction_rules.xml | 19 +
gradle.properties | 27 +-
gradle/libs.versions.toml | 31 +-
gradle/wrapper/gradle-wrapper.jar | Bin 43504 -> 59203 bytes
gradle/wrapper/gradle-wrapper.properties | 4 +-
gradlew | 285 ++--
gradlew.bat | 37 +-
package-lock.json | 1408 +++++++++--------
package.json | 2 +-
proguard-rules.pro | 22 +-
settings.gradle.kts | 35 +-
shared/build.gradle.kts | 8 +
.../revanced/manager/plugin/utils/Merger.kt | 59 +
50 files changed, 2629 insertions(+), 1115 deletions(-)
create mode 100644 .gitmodules
create mode 160000 arsclib
create mode 100644 downloaders/apkmirror-downloader/build.gradle.kts
create mode 100644 downloaders/apkmirror-downloader/src/main/AndroidManifest.xml
create mode 100644 downloaders/apkmirror-downloader/src/main/kotlin/app/revanced/manager/plugin/downloader/apkmirror/APKMirrorDownloader.kt
create mode 100644 downloaders/apkmirror-downloader/src/main/res/drawable/ic_launcher_background.xml
create mode 100644 downloaders/apkmirror-downloader/src/main/res/drawable/ic_launcher_foreground.xml
create mode 100644 downloaders/apkmirror-downloader/src/main/res/mipmap-anydpi/ic_launcher.xml
create mode 100644 downloaders/apkmirror-downloader/src/main/res/mipmap-anydpi/ic_launcher_round.xml
create mode 100644 downloaders/apkmirror-downloader/src/main/res/values/strings.xml
create mode 100644 downloaders/play-store-downloader/build.gradle.kts
create mode 100644 downloaders/play-store-downloader/src/main/AndroidManifest.xml
create mode 100644 downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/ICredentialProvider.aidl
create mode 100644 downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/data/Credentials.aidl
create mode 100644 downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/data/ParcelProperties.aidl
create mode 100644 downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/PlayStorePlugin.kt
create mode 100644 downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/Credentials.kt
create mode 100644 downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/Http.kt
create mode 100644 downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/PropertiesProvider.kt
create mode 100644 downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/service/CredentialProviderService.kt
create mode 100644 downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/ui/AuthActivity.kt
create mode 100644 downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/ui/AuthActivityViewModel.kt
create mode 100644 downloaders/play-store-downloader/src/main/res/drawable/ic_launcher_background.xml
create mode 100644 downloaders/play-store-downloader/src/main/res/drawable/ic_launcher_foreground.xml
create mode 100644 downloaders/play-store-downloader/src/main/res/mipmap-anydpi/ic_launcher.xml
create mode 100644 downloaders/play-store-downloader/src/main/res/mipmap-anydpi/ic_launcher_round.xml
create mode 100644 downloaders/play-store-downloader/src/main/res/values/strings.xml
create mode 100644 downloaders/play-store-downloader/src/main/res/xml/backup_rules.xml
create mode 100644 downloaders/play-store-downloader/src/main/res/xml/data_extraction_rules.xml
create mode 100644 shared/build.gradle.kts
create mode 100644 shared/src/main/kotlin/app/revanced/manager/plugin/utils/Merger.kt
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index a8ddc61..8488a98 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -11,18 +11,18 @@ body:
-
-
+
+
@@ -68,8 +68,8 @@ body:
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).
- - **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).
+ - **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-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).
- type: textarea
attributes:
@@ -79,7 +79,7 @@ body:
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
- Add images and videos if possible
validations:
- required: true
+ required: true
- type: textarea
attributes:
label: Error logs
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 7afbdc6..f6baf8b 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -11,18 +11,18 @@ body:
-
-
+
+
@@ -68,8 +68,8 @@ body:
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).
- - **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).
+ - **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-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).
- type: textarea
attributes:
diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml
index 536b661..87bd4c2 100644
--- a/.github/workflows/build_pull_request.yml
+++ b/.github/workflows/build_pull_request.yml
@@ -14,7 +14,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
with:
- fetch-depth: 0
+ submodules: true
- name: Setup Java
uses: actions/setup-java@v4
@@ -23,9 +23,15 @@ jobs:
java-version: "17"
- name: Cache Gradle
- uses: burrunan/gradle-cache-action@v2
+ uses: burrunan/gradle-cache-action@v3
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8ec52ab..40af410 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,6 +12,8 @@ jobs:
name: Release
permissions:
contents: write
+ id-token: write
+ attestations: write
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -20,7 +22,7 @@ jobs:
# Make sure the release step uses its own credentials:
# https://github.com/cycjimmy/semantic-release-action#private-packages
persist-credentials: false
- fetch-depth: 0
+ submodules: true
- name: Setup Java
uses: actions/setup-java@v4
@@ -29,12 +31,12 @@ jobs:
java-version: "17"
- name: Cache Gradle
- uses: burrunan/gradle-cache-action@v2
+ uses: burrunan/gradle-cache-action@v3
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: ./gradlew build --no-daemon
+ run: ./gradlew assembleRelease --no-daemon
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -64,3 +66,10 @@ jobs:
KEYSTORE_ENTRY_PASSWORD: ${{ secrets.KEYSTORE_ENTRY_PASSWORD }}
run: |
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
diff --git a/.gitignore b/.gitignore
index 390ce08..9b6d2bb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
-*.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
-**/build/
-!src/**/build/
-
-# Ignore Gradle GUI config
-gradle-app.setting
-
-# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
-!gradle-wrapper.jar
-
-# Cache of project
-.gradletasknamecache
-
-# # 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
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+**/build
+/captures
+.externalNativeBuild
+.cxx
local.properties
+.idea/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..2b32932
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "arsclib"]
+ path = arsclib
+ url = https://github.com/REAndroid/ARSCLib
diff --git a/.releaserc b/.releaserc
index 6ad60d9..b539863 100644
--- a/.releaserc
+++ b/.releaserc
@@ -32,7 +32,10 @@
{
"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
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8a7f8a8..7cb60c6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -60,38 +60,38 @@
# 👋 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
* [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
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**
-> 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.
## 🐞 Submitting a bug report
-If you encounter a bug while using ReVanced Manager downloader template, 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+).
+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-downloaders/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
## 📝 How to contribute
1. Before contributing, it is recommended to open an issue to discuss your change
-with the maintainers of ReVanced Manager 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
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
3. Commit your changes
4. Submit a pull request to the `dev` branch of the repository and reference issues
that your pull request closes in the description of your pull request
5. Our team will review your pull request and provide feedback. Once your pull request is approved,
-it will be merged into the `dev` branch and will be included in the next release of ReVanced Manager 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
diff --git a/README.md b/README.md
index 0cfbf43..6910822 100644
--- a/README.md
+++ b/README.md
@@ -58,34 +58,43 @@
Continuing the legacy of Vanced
-# 👋🔌 ReVanced Manager APKMirror downloader
+# 👋🔌 ReVanced Manager Downloaders
-
+

-An [APKMirror](https://www.apkmirror.com/) plugin for ReVanced Manager.
+The collection of ReVanced downloaders.
## 🧑💻 Usage
-- Plugins are 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.
-- The plugin will now be usable.
+- 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 downloader in the settings.
+- 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
### 📙 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).
### 🛠️ Building
-To build ReVanced Manager downloader template, a Java Development Kit (JDK) and Git must be installed.
-Follow the steps below to build ReVanced Manager downloader template:
+To build ReVanced Manager Downloaders, a Java Development Kit (JDK) and Git must be installed.
+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
> [!NOTE]
diff --git a/arsclib b/arsclib
new file mode 160000
index 0000000..a44577e
--- /dev/null
+++ b/arsclib
@@ -0,0 +1 @@
+Subproject commit a44577e73f1a9ce150833e96d98cfa299ddae23c
diff --git a/build.gradle.kts b/build.gradle.kts
index 8012b04..f26d53b 100644
--- a/build.gradle.kts
+++ b/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 {
- alias(libs.plugins.android.application)
- alias(libs.plugins.kotlin.android)
- publishing
- signing
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.kotlin.parcelize) apply false
+ alias(libs.plugins.kotlin.android) apply false
+ alias(libs.plugins.compose.compiler) apply false
}
-dependencies {
- compileOnly(libs.plugin.api)
-}
-
-android {
- val packageName = "app.revanced.manager.plugin.downloader.apkmirror"
-
- namespace = packageName
- compileSdk = 35
-
- defaultConfig {
- applicationId = packageName
- 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())
+subprojects {
+ repositories {
+ google()
+ mavenCentral()
+ maven {
+ name = "GitHubPackages"
+ url = uri("https://maven.pkg.github.com/revanced/registry")
+ credentials {
+ username = providers.gradleProperty("gpr.user")
+ .getOrElse(System.getenv("GITHUB_ACTOR"))
+ password =
+ providers.gradleProperty("gpr.key").getOrElse(System.getenv("GITHUB_TOKEN"))
}
}
}
- // Used by gradle-semantic-release-plugin.
- // Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435.
- publish {
- dependsOn(assembleReleaseSignApk)
+ if (project.path.endsWith("-downloader")) {
+ apply(plugin = "com.android.application")
+ apply(plugin = "org.jetbrains.kotlin.android")
+ apply(plugin = "maven-publish")
+ apply(plugin = "signing")
+
+ dependencies {
+ "compileOnly"(rootProject.libs.plugin.api)
+ }
+
+ configure {
+ 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().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 {
+ useGpgCmd()
+ sign(*inputs.files.files.toTypedArray())
+ }
+ }
+ }
+
+ tasks.named("publish") {
+ dependsOn("assembleReleaseSignApk")
+ }
}
-}
+}
\ No newline at end of file
diff --git a/downloaders/apkmirror-downloader/build.gradle.kts b/downloaders/apkmirror-downloader/build.gradle.kts
new file mode 100644
index 0000000..74a9201
--- /dev/null
+++ b/downloaders/apkmirror-downloader/build.gradle.kts
@@ -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"))
+ }
+}
\ No newline at end of file
diff --git a/downloaders/apkmirror-downloader/src/main/AndroidManifest.xml b/downloaders/apkmirror-downloader/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..149dc26
--- /dev/null
+++ b/downloaders/apkmirror-downloader/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/downloaders/apkmirror-downloader/src/main/kotlin/app/revanced/manager/plugin/downloader/apkmirror/APKMirrorDownloader.kt b/downloaders/apkmirror-downloader/src/main/kotlin/app/revanced/manager/plugin/downloader/apkmirror/APKMirrorDownloader.kt
new file mode 100644
index 0000000..270c580
--- /dev/null
+++ b/downloaders/apkmirror-downloader/src/main/kotlin/app/revanced/manager/plugin/downloader/apkmirror/APKMirrorDownloader.kt
@@ -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 {
+ 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()
+ }
+ }
+}
\ No newline at end of file
diff --git a/downloaders/apkmirror-downloader/src/main/res/drawable/ic_launcher_background.xml b/downloaders/apkmirror-downloader/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/downloaders/apkmirror-downloader/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/downloaders/apkmirror-downloader/src/main/res/drawable/ic_launcher_foreground.xml b/downloaders/apkmirror-downloader/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/downloaders/apkmirror-downloader/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/downloaders/apkmirror-downloader/src/main/res/mipmap-anydpi/ic_launcher.xml b/downloaders/apkmirror-downloader/src/main/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/downloaders/apkmirror-downloader/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/downloaders/apkmirror-downloader/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/downloaders/apkmirror-downloader/src/main/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/downloaders/apkmirror-downloader/src/main/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/downloaders/apkmirror-downloader/src/main/res/values/strings.xml b/downloaders/apkmirror-downloader/src/main/res/values/strings.xml
new file mode 100644
index 0000000..6dae166
--- /dev/null
+++ b/downloaders/apkmirror-downloader/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ ReVanced Manager: APKMirror downloader
+
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/build.gradle.kts b/downloaders/play-store-downloader/build.gradle.kts
new file mode 100644
index 0000000..b375148
--- /dev/null
+++ b/downloaders/play-store-downloader/build.gradle.kts
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/AndroidManifest.xml b/downloaders/play-store-downloader/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..63ca7d8
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/AndroidManifest.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/ICredentialProvider.aidl b/downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/ICredentialProvider.aidl
new file mode 100644
index 0000000..10fdcec
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/ICredentialProvider.aidl
@@ -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();
+}
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/data/Credentials.aidl b/downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/data/Credentials.aidl
new file mode 100644
index 0000000..59dd539
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/data/Credentials.aidl
@@ -0,0 +1,4 @@
+// Credentials.aidl
+package app.revanced.manager.plugin.downloader.play.store.data;
+
+parcelable Credentials;
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/data/ParcelProperties.aidl b/downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/data/ParcelProperties.aidl
new file mode 100644
index 0000000..fef9f80
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/aidl/app/revanced/manager/plugin/downloader/play/store/data/ParcelProperties.aidl
@@ -0,0 +1,4 @@
+// ParcelProperties.aidl
+package app.revanced.manager.plugin.downloader.play.store.data;
+
+parcelable ParcelProperties;
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/PlayStorePlugin.kt b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/PlayStorePlugin.kt
new file mode 100644
index 0000000..f37c1b7
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/PlayStorePlugin.kt
@@ -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
+) : Parcelable
+
+@Suppress("Unused")
+@OptIn(ExperimentalPathApi::class)
+val playStoreDownloader = Downloader {
+ get { packageName, version ->
+ val (credentials, deviceProps) = useService> { binder ->
+ val credentialProvider = ICredentialProvider.Stub.asInterface(binder)
+ val props = credentialProvider.properties.value
+ credentialProvider.retrieveCredentials()?.let { return@useService it to props }
+
+ try {
+ requestStartActivity()
+ } 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()
+ }
+ }
+}
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/Credentials.kt b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/Credentials.kt
new file mode 100644
index 0000000..c254c17
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/Credentials.kt
@@ -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"
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/Http.kt b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/Http.kt
new file mode 100644
index 0000000..0deaf39
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/Http.kt
@@ -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,
+ 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) =
+ playRequest(HttpMethod.Get, url, headers)
+
+ override fun get(
+ url: String,
+ headers: Map,
+ params: Map
+ ) = playRequest(HttpMethod.Get, url, headers) {
+ params.forEach { (name, value) ->
+ parameter(name, value)
+ }
+ }
+
+ override fun get(url: String, headers: Map, 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, body: ByteArray) = playRequest(
+ HttpMethod.Post, url, headers
+ ) {
+ setBody(body)
+ }
+
+ override fun post(
+ url: String,
+ headers: Map,
+ params: Map
+ ) = playRequest(HttpMethod.Post, url, headers) {
+ params.forEach { (name, value) ->
+ parameter(name, value)
+ }
+ }
+
+ override fun postAuth(url: String, body: ByteArray) = getAuth(url)
+}
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/PropertiesProvider.kt b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/PropertiesProvider.kt
new file mode 100644
index 0000000..13777d9
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/data/PropertiesProvider.kt
@@ -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()!!.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.commaSeparated() = joinToString(separator = ",")
+ private fun Iterable.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(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
+ ) {
+ 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
+ )
+}
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/service/CredentialProviderService.kt b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/service/CredentialProviderService.kt
new file mode 100644
index 0000000..59730f5
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/service/CredentialProviderService.kt
@@ -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()
+}
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/ui/AuthActivity.kt b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/ui/AuthActivity.kt
new file mode 100644
index 0000000..a830b2d
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/ui/AuthActivity.kt
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/ui/AuthActivityViewModel.kt b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/ui/AuthActivityViewModel.kt
new file mode 100644
index 0000000..c45ffe9
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/java/app/revanced/manager/plugin/downloader/play/store/ui/AuthActivityViewModel.kt
@@ -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>(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()
+ 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 {
+ 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()
+ }
+}
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/res/drawable/ic_launcher_background.xml b/downloaders/play-store-downloader/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/downloaders/play-store-downloader/src/main/res/drawable/ic_launcher_foreground.xml b/downloaders/play-store-downloader/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/res/mipmap-anydpi/ic_launcher.xml b/downloaders/play-store-downloader/src/main/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/downloaders/play-store-downloader/src/main/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/res/values/strings.xml b/downloaders/play-store-downloader/src/main/res/values/strings.xml
new file mode 100644
index 0000000..22872d6
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ ReVanced Manager: Play Store downloader
+
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/res/xml/backup_rules.xml b/downloaders/play-store-downloader/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/downloaders/play-store-downloader/src/main/res/xml/data_extraction_rules.xml b/downloaders/play-store-downloader/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/downloaders/play-store-downloader/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index c36a1e2..a29e533 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -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.nonTransitiveRClass = true
-android.nonFinalResIds = false
+# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style = official
-org.gradle.parallel = true
-org.gradle.caching = true
-version = 1.0.0
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass = true
+version = 1.0.0-dev.1
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index fefb0d6..d0dc4be 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,11 +1,36 @@
[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"
-android-gradle-plugin = "8.7.3"
-kotlin = "2.1.0"
[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" }
+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]
-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-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 2c3521197d7c4586c843d1d3e9090525f1898cde..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644
GIT binary patch
literal 59203
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≪~osG=zezhi=
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