diff --git a/.github/ISSUE_TEMPLATE/bug-issue.yml b/.github/ISSUE_TEMPLATE/bug-issue.yml
deleted file mode 100644
index d8c1a0fe..00000000
--- a/.github/ISSUE_TEMPLATE/bug-issue.yml
+++ /dev/null
@@ -1,73 +0,0 @@
-name: 🐞 Bug report
-description: Report a very clearly broken issue.
-title: 'bug:
'
-labels: [bug]
-body:
- - type: markdown
- attributes:
- value: |
- # ReVanced bug report
-
- Important to note that your issue may have already been reported before. Please check for existing issues [here](https://github.com/revanced/revanced-cli/labels/bug).
-
- - type: dropdown
- attributes:
- label: Type
- options:
- - Error while running the CLI
- - Error at runtime
- - Cosmetic
- - Other
- validations:
- required: true
- - type: textarea
- attributes:
- label: Bug description
- description: How did you find the bug? Any additional details that might help?
- validations:
- required: true
- - type: textarea
- attributes:
- label: Steps to reproduce
- description: Add the steps to reproduce this bug including your environment.
- placeholder: Step 1. Download some files. Step 2. ...
- validations:
- required: true
- - type: textarea
- attributes:
- label: Relevant log output
- description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
- render: shell
- validations:
- required: true
- - type: textarea
- attributes:
- label: Screenshots or videos
- description: Add screenshots or videos that show the bug here.
- placeholder: Drag and drop the screenshots/videos into this box.
- validations:
- required: false
- - type: textarea
- attributes:
- label: Solution
- description: If applicable, add a possible solution.
- validations:
- required: false
- - type: textarea
- attributes:
- label: Additional context
- description: Add additional context here.
- validations:
- required: false
- - type: checkboxes
- id: acknowledgements
- attributes:
- label: Acknowledgements
- description: Your issue will be closed if you haven't done these steps.
- options:
- - label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
- required: true
- - label: I have written a short but informative title.
- required: true
- - label: I filled out all of the requested information in this issue properly.
- required: true
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
new file mode 100644
index 00000000..4a37b91c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -0,0 +1,49 @@
+name: 🐞 Bug report
+description: Report a bug or an issue.
+title: 'bug: '
+labels: ['Bug report']
+body:
+ - type: markdown
+ attributes:
+ value: |
+ # ReVanced CLI bug report
+
+ Please check for existing bug reports
+ [here](https://github.com/ReVanced/revanced-cli/labels/Bug%20report)
+ before creating a new one.
+
+ - type: textarea
+ attributes:
+ label: Bug description
+ description: |
+ - Describe your bug in detail
+ - Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
+ - Add images and videos if possible
+ - List used patches if applicable
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Error logs
+ description: Exceptions can be captured by running `logcat | grep AndroidRuntime` in a shell.
+ render: shell
+ - type: textarea
+ attributes:
+ label: Solution
+ description: If applicable, add a possible solution to the bug.
+ - type: textarea
+ attributes:
+ label: Additional context
+ description: Add additional context here.
+ - type: checkboxes
+ id: acknowledgements
+ attributes:
+ label: Acknowledgements
+ description: Your issue will be closed if you don't follow the checklist below.
+ options:
+ - label: This request is not a duplicate of an existing issue.
+ required: true
+ - label: I have chosen an appropriate title.
+ required: true
+ - label: All requested information has been provided properly.
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 9f537ff8..2482beb0 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,8 +1,5 @@
blank_issues_enabled: false
contact_links:
- - name: 📃 Documentation
- url: https://github.com/revanced/revanced-documentation/
- about: Don't know how or where to start? Check out our documentation!
- name: 🗨 Discussions
url: https://github.com/revanced/revanced-suggestions/discussions
- about: Got something you think should change or be added? Search for or start a new discussion!
\ No newline at end of file
+ about: Have something unspecific to ReVanced CLI in mind? Search for or start a new discussion!
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature-issue.yml b/.github/ISSUE_TEMPLATE/feature-issue.yml
deleted file mode 100644
index 14de6520..00000000
--- a/.github/ISSUE_TEMPLATE/feature-issue.yml
+++ /dev/null
@@ -1,58 +0,0 @@
-name: ⭐ Feature request
-description: Create a detailed feature request.
-title: 'feat: '
-labels: [feature-request]
-body:
- - type: markdown
- attributes:
- value: |
- # ReVanced feature request
-
- Do not submit requests for patches here. Please submit them [here](https://github.com/orgs/revanced/discussions/categories/patches) instead.
- Important to note that your feature request may have already been made before. Please check for existing feature requests [here](https://github.com/revanced/revanced-cli/labels/feature-request).
-
- - type: dropdown
- attributes:
- label: Type
- options:
- - Functionality
- - Cosmetic
- - Other
- validations:
- required: true
- - type: textarea
- attributes:
- label: Issue
- description: What is the current problem. Why does it require a feature request?
- validations:
- required: true
- - type: textarea
- attributes:
- label: Feature
- description: Describe your feature in detail. How does it solve the issue?
- validations:
- required: true
- - type: textarea
- attributes:
- label: Motivation
- description: Why should your feature should be considered?
- validations:
- required: true
- - type: textarea
- attributes:
- label: Additional context
- description: Add additional context here.
- validations:
- required: false
- - type: checkboxes
- id: acknowledgements
- attributes:
- label: Acknowledgements
- description: Your issue will be closed if you haven't done these steps.
- options:
- - label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
- required: true
- - label: I have written a short but informative title.
- required: true
- - label: I filled out all of the requested information in this issue properly.
- required: true
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
new file mode 100644
index 00000000..f7ba3c23
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -0,0 +1,45 @@
+name: ⭐ Feature request
+description: Create a detailed request for a new feature.
+title: 'feat: '
+labels: ['Feature request']
+body:
+ - type: markdown
+ attributes:
+ value: |
+ # ReVanced CLI feature request
+
+ Please check for existing feature requests
+ [here](https://github.com/ReVanced/revanced-cli/labels/Feature%20request)
+ before creating a new one.
+
+ - type: textarea
+ attributes:
+ label: Feature description
+ description: |
+ - Describe your feature in detail
+ - Add images, videos, links, examples, references, etc. if possible
+ - Add the target application name in case you request a new patch
+ - type: textarea
+ attributes:
+ label: Motivation
+ description: |
+ A strong motivation is necessary for a feature request to be considered.
+
+ - Why should this feature be implemented?
+ - What is the explicit use case?
+ - What are the benefits?
+ - What makes this feature important?
+ validations:
+ required: true
+ - type: checkboxes
+ id: acknowledgements
+ attributes:
+ label: Acknowledgements
+ description: Your issue will be closed if you don't follow the checklist below.
+ options:
+ - label: This request is not a duplicate of an existing issue.
+ required: true
+ - label: I have chosen an appropriate title.
+ required: true
+ - label: All requested information has been provided properly.
+ required: true
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 605e7a72..188fb701 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -1,4 +1,4 @@
-name: PR to main
+name: Open a PR to main
on:
push:
@@ -7,7 +7,7 @@ on:
workflow_dispatch:
env:
- MESSAGE: merge branch `${{ github.head_ref || github.ref_name }}` to `main`
+ MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main`
jobs:
pull-request:
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Open pull request
uses: repo-sync/pull-request@v2
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0861dc65..0214112c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
# Make sure the release step uses its own credentials:
# https://github.com/cycjimmy/semantic-release-action#private-packages
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9362b77..635b0bf7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,71 @@
+# [4.0.0-dev.5](https://github.com/ReVanced/revanced-cli/compare/v4.0.0-dev.4...v4.0.0-dev.5) (2023-10-04)
+
+
+### Bug Fixes
+
+* Only set options for filtered patches ([64d9127](https://github.com/ReVanced/revanced-cli/commit/64d9127291ea9a8abe21a0e82721721495094472))
+
+
+### Performance Improvements
+
+* Do not check, if the options file exists twice ([e3c5550](https://github.com/ReVanced/revanced-cli/commit/e3c55507cf52e697b9ce9d59decc1cbe4cfe5b43))
+
+# [4.0.0-dev.4](https://github.com/ReVanced/revanced-cli/compare/v4.0.0-dev.3...v4.0.0-dev.4) (2023-10-01)
+
+# [4.0.0-dev.3](https://github.com/ReVanced/revanced-cli/compare/v4.0.0-dev.2...v4.0.0-dev.3) (2023-09-27)
+
+# [4.0.0-dev.2](https://github.com/ReVanced/revanced-cli/compare/v4.0.0-dev.1...v4.0.0-dev.2) (2023-09-24)
+
+
+### Features
+
+* Improve option descriptions ([d5ea5a0](https://github.com/ReVanced/revanced-cli/commit/d5ea5a0ab1cc015730063e5be94ee18bd88cc906))
+
+# [4.0.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v3.1.2-dev.1...v4.0.0-dev.1) (2023-09-23)
+
+
+### Bug Fixes
+
+* Check, if mounting is possible ([3e13fb5](https://github.com/ReVanced/revanced-cli/commit/3e13fb5d56eb2a90c2a4a1ddfc05852b1f70add5))
+* Delete temporal files if it exists ([a022feb](https://github.com/ReVanced/revanced-cli/commit/a022febd0c70807ddc3fa9948207a2a70d5191da))
+* Do not sign if mounting ([578e16b](https://github.com/ReVanced/revanced-cli/commit/578e16b099fddfd2bb56accb225d04dfcd409b0c))
+* Filter logs correctly ([43fc20d](https://github.com/ReVanced/revanced-cli/commit/43fc20d90e0a694b231b17bb7d9ecfa22bb5d9a0))
+* Log logs with levels over warning to error output stream ([075f6ad](https://github.com/ReVanced/revanced-cli/commit/075f6ad56528a667dca1f0bed704cf7e5381026f))
+* Only open files for reading and writing if writeable ([3846f72](https://github.com/ReVanced/revanced-cli/commit/3846f721ca015ab39a7e4b8d3f3d61163a6f1650))
+
+
+### Features
+
+* Add function to get the most common compatible version ([77d9173](https://github.com/ReVanced/revanced-cli/commit/77d91735ffbbd6e733f08176f535bfd39ece0c29))
+* Add option to filter patches to be listed by package name ([50c0f98](https://github.com/ReVanced/revanced-cli/commit/50c0f98ce5927e07839437a2e550aa85f5a7e62d))
+* Add option to warn about patches not being found in supplied patch bundles ([e46d855](https://github.com/ReVanced/revanced-cli/commit/e46d85564320f46c6faa54b2d3fa7fca3fa60019))
+* Add ReVanced Library subproject ([#265](https://github.com/ReVanced/revanced-cli/issues/265)) ([157278c](https://github.com/ReVanced/revanced-cli/commit/157278c9ba25f0f786c5fe58e3e23f6890107118))
+* Do not format patch names ([80a8d88](https://github.com/ReVanced/revanced-cli/commit/80a8d88406b2b04d13dca4fb0d7d7d62e1910317))
+* Extend signing API ([592dc1c](https://github.com/ReVanced/revanced-cli/commit/592dc1c64ae4078e73bb71eba11380b301c79dea))
+* Log stacktrace in new line ([c67e3c7](https://github.com/ReVanced/revanced-cli/commit/c67e3c70c7eaa514cde1bebe775a2216bc4a6074))
+* Use ReVanced Library in ReVanced CLI ([7794327](https://github.com/ReVanced/revanced-cli/commit/7794327a11e8a0e0f28176cd45fad797b924c45f))
+* Word log message better ([6942b22](https://github.com/ReVanced/revanced-cli/commit/6942b22a68de5e991987ea89882915917aec93a3))
+
+
+### BREAKING CHANGES
+
+* This changes many signatures of existing APIs and adds new functions for signing
+* This changes the log handler signature
+
+# [3.2.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v3.1.2-dev.1...v3.2.0-dev.1) (2023-09-20)
+
+
+### Features
+
+* Log stacktrace in new line ([c67e3c7](https://github.com/ReVanced/revanced-cli/commit/c67e3c70c7eaa514cde1bebe775a2216bc4a6074))
+
+## [3.1.2-dev.1](https://github.com/ReVanced/revanced-cli/compare/v3.1.1...v3.1.2-dev.1) (2023-09-12)
+
+
+### Bug Fixes
+
+* Log correct options command ([#262](https://github.com/ReVanced/revanced-cli/issues/262)) ([96c196d](https://github.com/ReVanced/revanced-cli/commit/96c196dcb14e37ad91b751af61ee8382547c1ca3))
+
## [3.1.1](https://github.com/ReVanced/revanced-cli/compare/v3.1.0...v3.1.1) (2023-09-09)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..8eeb27a5
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Continuing the legacy of Vanced
+
+
+# 📙 ReVanced CLI contribution guidelines
+
+This document describes how to contribute to ReVanced CLI.
+
+## 📖 Resources to help you get started
+
+* The [documentation](/docs) explains how to use ReVanced CLI
+* [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-cli/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-cli/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 CLI.
+> Good motivation has to be provided for a request to be accepted.
+
+## 🐞 Submitting a bug report
+
+If you encounter a bug while using ReVanced CLI, open an issue using the
+[Bug report issue template](https://github.com/ReVanced/revanced-cli/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 CLI. 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 CLI
+
+❤️ Thank you for considering contributing to ReVanced CLI,
+ReVanced
\ No newline at end of file
diff --git a/README.md b/README.md
index 0b6f6f66..f7486d7f 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Continuing the legacy of Vanced
+
+
+
# 💻 ReVanced CLI
+
+
+
Command line application to use ReVanced.
+
+## ❓ About
+
+ReVanced CLI is a command line application to patch apps using ReVanced.
+ReVanced CLI also comes with commands to uninstall or install patched apps and list patches from supplied patch bundles.
+
+## 🚀 Download
+
+You can download the most recent version of ReVanced CLI from
+[here](https://github.com/ReVanced/revanced-cli/releases/latest). Learn how to use ReVanced CLI by following the [documentation](/docs).
+
+## 📚 Everything else
+
+### 📙 Contributing
+
+Thank you for considering contributing to ReVanced CLI.
+You can find the contribution guidelines [here](CONTRIBUTING.md).
+
+### 🛠️ Building
+
+In order to build ReVanced CLI, you can follow the [documentation](/docs).
+
+## 📜 Licence
+
+ReVanced CLI is licensed under the GPLv3 licence. Please see the [licence file](LICENSE) for more information.
+[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced CLI as long as you track changes/dates in source files.
+Any modifications to ReVanced CLI must also be made available under the GPL along with build & install instructions.
diff --git a/build.gradle.kts b/build.gradle.kts
index 9c6b3357..f6de7ca2 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,5 +1,5 @@
plugins {
- kotlin("jvm") version "1.8.20"
+ kotlin("jvm") version "1.9.0"
alias(libs.plugins.shadow)
}
@@ -7,13 +7,10 @@ group = "app.revanced"
dependencies {
implementation(libs.revanced.patcher)
- implementation(libs.kotlin.reflect)
+ implementation(libs.revanced.library)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.picocli)
- implementation(libs.jadb) // Updated fork
- implementation(libs.apksig)
- implementation(libs.bcpkix.jdk15on)
- implementation(libs.jackson.module.kotlin)
+
testImplementation(libs.kotlin.test)
}
@@ -46,12 +43,21 @@ tasks {
dependsOn(shadowJar)
}
- // Dummy task to fix the Gradle semantic-release plugin.
- // Remove this if you forked it to support building only.
- // Tracking issue: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
+ /*
+ Dummy task to hack gradle-semantic-release-plugin to release this project.
+
+ Explanation:
+ SemVer is a standard for versioning libraries.
+ For that reason the semantic-release plugin uses the "publish" task to publish libraries.
+ However, this subproject is not a library, and the "publish" task is not available for this subproject.
+ Because semantic-release is not designed to handle this case, we need to hack it.
+
+ RE: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
+ */
+
register("publish") {
- group = "publish"
- description = "Dummy task"
+ group = "publishing"
+ description = "Dummy task to hack gradle-semantic-release-plugin to release ReVanced CLI"
dependsOn(build)
}
}
diff --git a/gradle.properties b/gradle.properties
index 4fa448da..2c335cd0 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
-version = 3.1.1
+version = 4.0.0-dev.5
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 03784777..eccdb3f7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,25 +1,17 @@
[versions]
shadow = "8.1.1"
-apksig = "8.1.1"
-bcpkix-jdk15on = "1.70"
-jackson-module-kotlin = "2.14.3"
-jadb = "2531a28109"
-kotlin-reflect = "1.9.0"
kotlin-test = "1.8.20-RC"
-kotlinx-coroutines-core = "1.7.1"
+kotlinx-coroutines-core = "1.7.3"
picocli = "4.7.3"
-revanced-patcher = "14.2.1"
+revanced-patcher = "16.0.0"
+revanced-library = "1.1.1"
[libraries]
-apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
-bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" }
-jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" }
-jadb = { module = "com.github.revanced:jadb", version.ref = "jadb" }
-kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
picocli = { module = "info.picocli:picocli", version.ref = "picocli" }
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
+revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" }
[plugins]
-shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
\ No newline at end of file
+shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
diff --git a/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt b/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt
index 28419d9a..c899aa2e 100644
--- a/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt
+++ b/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt
@@ -1,13 +1,8 @@
package app.revanced.cli.command
import app.revanced.patcher.PatchBundleLoader
-import app.revanced.patcher.annotation.Package
-import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
-import app.revanced.patcher.extensions.PatchExtensions.description
-import app.revanced.patcher.extensions.PatchExtensions.options
-import app.revanced.patcher.extensions.PatchExtensions.patchName
-import app.revanced.patcher.patch.PatchClass
-import app.revanced.patcher.patch.PatchOption
+import app.revanced.patcher.patch.Patch
+import app.revanced.patcher.patch.options.PatchOption
import picocli.CommandLine.*
import picocli.CommandLine.Help.Visibility.ALWAYS
import java.io.File
@@ -47,12 +42,17 @@ internal object ListPatchesCommand : Runnable {
)
private var withOptions: Boolean = false
+ @Option(
+ names = ["-f", "--filter-package-name"], description = ["Filter patches by package name"]
+ )
+ private var packageName: String? = null
+
override fun run() {
- fun Package.buildString() = buildString {
- if (withVersions && versions.isNotEmpty()) {
+ fun Patch.CompatiblePackage.buildString() = buildString {
+ if (withVersions && versions != null) {
appendLine("Package name: $name")
appendLine("Compatible versions:")
- append(versions.joinToString("\n") { version -> version }.prependIndent("\t"))
+ append(versions!!.joinToString("\n") { version -> version }.prependIndent("\t"))
} else append("Package name: $name")
}
@@ -66,15 +66,17 @@ internal object ListPatchesCommand : Runnable {
} ?: append("Key: $key")
}
- fun PatchClass.buildString() = buildString {
- append("Name: $patchName")
+ fun Patch<*>.buildString() = buildString {
+ append("Name: $name")
if (withDescriptions) append("\nDescription: $description")
- if (withOptions && options != null) {
+ if (withOptions && options.isNotEmpty()) {
appendLine("\nOptions:")
append(
- options!!.joinToString("\n\n") { option -> option.buildString() }.prependIndent("\t")
+ options.values.joinToString("\n\n") { option ->
+ option.buildString()
+ }.prependIndent("\t")
)
}
@@ -86,6 +88,12 @@ internal object ListPatchesCommand : Runnable {
}
}
- logger.info(PatchBundleLoader.Jar(*patchBundles).joinToString("\n\n") { it.buildString() })
+ fun Patch<*>.anyPackageName(name: String) = compatiblePackages?.any { it.name == name } == true
+
+ val patches = PatchBundleLoader.Jar(*patchBundles)
+
+ val filtered = packageName?.let { patches.filter { patch -> patch.anyPackageName(it) } } ?: patches
+
+ if (filtered.isNotEmpty()) logger.info(filtered.joinToString("\n\n") { it.buildString() })
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/cli/command/MainCommand.kt b/src/main/kotlin/app/revanced/cli/command/MainCommand.kt
index 99ac1b1c..d8bf6d98 100644
--- a/src/main/kotlin/app/revanced/cli/command/MainCommand.kt
+++ b/src/main/kotlin/app/revanced/cli/command/MainCommand.kt
@@ -1,55 +1,27 @@
package app.revanced.cli.command
import app.revanced.cli.command.utility.UtilityCommand
-import app.revanced.patcher.patch.PatchClass
+import app.revanced.library.logging.Logger
import picocli.CommandLine
import picocli.CommandLine.Command
import picocli.CommandLine.IVersionProvider
import java.util.*
-import java.util.logging.*
fun main(args: Array) {
- System.setProperty("java.util.logging.SimpleFormatter.format", "%4\$s: %5\$s %n")
- Logger.getLogger("").apply {
- handlers.forEach {
- it.close()
- removeHandler(it)
- }
-
- object : Handler() {
- override fun publish(record: LogRecord) = formatter.format(record).toByteArray().let {
- if (record.level.intValue() > Level.INFO.intValue())
- System.err.write(it)
- else
- System.out.write(it)
- }
-
- override fun flush() {
- System.out.flush()
- System.err.flush()
- }
-
- override fun close() = flush()
- }.also {
- it.level = Level.ALL
- it.formatter = SimpleFormatter()
- }.let(::addHandler)
- }
-
+ Logger.setDefault()
CommandLine(MainCommand).execute(*args)
}
-internal typealias PatchList = List
-
private object CLIVersionProvider : IVersionProvider {
- override fun getVersion(): Array {
- Properties().apply {
- load(MainCommand::class.java.getResourceAsStream("/app/revanced/cli/version.properties"))
- }.let {
- return arrayOf("ReVanced CLI v${it.getProperty("version")}")
- }
- }
+ override fun getVersion() = arrayOf(
+ MainCommand::class.java.getResourceAsStream(
+ "/app/revanced/cli/version.properties"
+ )?.use { stream ->
+ Properties().apply { load(stream) }.let {
+ "ReVanced CLI v${it.getProperty("version")}"
+ }
+ } ?: "ReVanced CLI")
}
@Command(
diff --git a/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt b/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt
index 863f8d73..82ca0267 100644
--- a/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt
+++ b/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt
@@ -1,8 +1,8 @@
package app.revanced.cli.command
+import app.revanced.library.Options
+import app.revanced.library.Options.setOptions
import app.revanced.patcher.PatchBundleLoader
-import app.revanced.utils.Options
-import app.revanced.utils.Options.setOptions
import picocli.CommandLine
import picocli.CommandLine.Help.Visibility.ALWAYS
import java.io.File
@@ -37,10 +37,18 @@ internal object OptionsCommand : Runnable {
)
private var update: Boolean = false
- override fun run() = if (!filePath.exists() || overwrite) with(PatchBundleLoader.Jar(*patchBundles)) {
- if (update && filePath.exists()) setOptions(filePath)
-
- Options.serialize(this, prettyPrint = true).let(filePath::writeText)
+ override fun run() = try {
+ PatchBundleLoader.Jar(*patchBundles).let { patches ->
+ val exists = filePath.exists()
+ if (!exists || overwrite) {
+ if (exists && update) patches.setOptions(filePath)
+
+ Options.serialize(patches, prettyPrint = true).let(filePath::writeText)
+ } else throw OptionsFileAlreadyExistsException()
+ }
+ } catch (ex: OptionsFileAlreadyExistsException) {
+ logger.severe("Options file already exists, use --overwrite to override it")
}
- else logger.severe("Options file already exists, use --override to override it")
+
+ class OptionsFileAlreadyExistsException : Exception()
}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt b/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt
index 9d3796cc..3d46c31d 100644
--- a/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt
+++ b/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt
@@ -1,23 +1,18 @@
package app.revanced.cli.command
+import app.revanced.library.ApkUtils
+import app.revanced.library.Options
+import app.revanced.library.Options.setOptions
+import app.revanced.library.adb.AdbManager
import app.revanced.patcher.PatchBundleLoader
+import app.revanced.patcher.PatchSet
import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions
-import app.revanced.patcher.PatcherResult
-import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
-import app.revanced.patcher.extensions.PatchExtensions.include
-import app.revanced.patcher.extensions.PatchExtensions.patchName
-import app.revanced.utils.Options
-import app.revanced.utils.Options.setOptions
-import app.revanced.utils.adb.AdbManager
-import app.revanced.utils.align.ZipAligner
-import app.revanced.utils.align.zip.ZipFile
-import app.revanced.utils.align.zip.structures.ZipEntry
-import app.revanced.utils.signing.ApkSigner
-import app.revanced.utils.signing.SigningOptions
import kotlinx.coroutines.runBlocking
import picocli.CommandLine
import picocli.CommandLine.Help.Visibility.ALWAYS
+import picocli.CommandLine.Model.CommandSpec
+import picocli.CommandLine.Spec
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
@@ -30,21 +25,15 @@ import java.util.logging.Logger
internal object PatchCommand : Runnable {
private val logger = Logger.getLogger(PatchCommand::class.java.name)
- @CommandLine.Parameters(
- description = ["APK file to be patched"], arity = "1..1"
- )
- private lateinit var apk: File
+ @Spec
+ lateinit var spec: CommandSpec // injected by picocli
- @CommandLine.Option(
- names = ["-b", "--patch-bundle"], description = ["One or more bundles of patches"], required = true
- )
- private var patchBundles = emptyList()
+ private lateinit var apk: File
- @CommandLine.Option(
- names = ["-m", "--merge"], description = ["One or more DEX files or containers to merge into the APK"]
- )
private var integrations = listOf()
+ private var patchBundles = emptyList()
+
@CommandLine.Option(
names = ["-i", "--include"], description = ["List of patches to include"]
)
@@ -69,7 +58,7 @@ internal object PatchCommand : Runnable {
@CommandLine.Option(
names = ["-f","--force"],
- description = ["Force inclusion of patches that are incompatible with the supplied APK file's version"],
+ description = ["Bypass compatibility checks for the supplied APK's version"],
showDefaultValue = ALWAYS
)
private var force: Boolean = false
@@ -90,22 +79,34 @@ internal object PatchCommand : Runnable {
private var mount: Boolean = false
@CommandLine.Option(
- names = ["--common-name"],
- description = ["The common name of the signer of the patched APK file"],
- showDefaultValue = ALWAYS
+ names = ["--keystore"], description = ["Path to the keystore to sign the patched APK file with"],
+ )
+ private var keystoreFilePath: File? = null
+ // key store password
+ @CommandLine.Option(
+ names = ["--keystore-password"],
+ description = ["The password of the keystore to sign the patched APK file with"],
)
- private var commonName = "ReVanced"
+ private var keyStorePassword: String? = null // Empty password by default
@CommandLine.Option(
- names = ["--keystore"], description = ["Path to the keystore to sign the patched APK file with"]
+ names = ["--alias"], description = ["The alias of the key from the keystore to sign the patched APK file with"],
+ showDefaultValue = ALWAYS
)
- private var keystorePath: String? = null
+ private var alias = "ReVanced Key"
@CommandLine.Option(
- names = ["--password"], description = ["The password of the keystore to sign the patched APK file with"]
+ names = ["--keystore-entry-password"],
+ description = ["The password of the entry from the keystore for the key to sign the patched APK file with"]
)
- private var password = "ReVanced"
+ private var password = "" // Empty password by default
+
+ @CommandLine.Option(
+ names = ["--signer"], description = ["The name of the signer to sign the patched APK file with"],
+ showDefaultValue = ALWAYS
+ )
+ private var signer = "ReVanced"
@CommandLine.Option(
names = ["-r", "--resource-cache"],
@@ -114,10 +115,7 @@ internal object PatchCommand : Runnable {
)
private var resourceCachePath = File("revanced-resource-cache")
- @CommandLine.Option(
- names = ["--custom-aapt2-binary"], description = ["Path to a custom AAPT binary to compile resources with"]
- )
- private var aaptBinaryPath = File("")
+ private var aaptBinaryPath: File? = null
@CommandLine.Option(
names = ["-p", "--purge"],
@@ -126,221 +124,205 @@ internal object PatchCommand : Runnable {
)
private var purge: Boolean = false
- override fun run() {
- // region Prepare
-
- if (!apk.exists()) {
- logger.severe("APK file ${apk.name} does not exist")
- return
- }
+ @CommandLine.Option(
+ names = ["-w", "--warn"],
+ description = ["Warn if a patch can not be found in the supplied patch bundles"],
+ showDefaultValue = ALWAYS
+ )
+ private var warn: Boolean = false
- integrations.filter { !it.exists() }.let {
- if (it.isEmpty()) return@let
+ @CommandLine.Parameters(
+ description = ["APK file to be patched"], arity = "1..1"
+ )
+ @Suppress("unused")
+ private fun setApk(apk: File) {
+ if (!apk.exists()) throw CommandLine.ParameterException(
+ spec.commandLine(),
+ "APK file ${apk.name} does not exist"
+ )
+ this.apk = apk
+ }
- it.forEach { integration ->
- logger.severe("Integration file ${integration.name} does not exist")
- }
- return
+ @CommandLine.Option(
+ names = ["-m", "--merge"], description = ["One or more DEX files or containers to merge into the APK"]
+ )
+ @Suppress("unused")
+ private fun setIntegrations(integrations: Array) {
+ integrations.firstOrNull { !it.exists() }?.let {
+ throw CommandLine.ParameterException(spec.commandLine(), "Integrations file ${it.name} does not exist")
}
+ this.integrations += integrations
+ }
- val adbManager = deviceSerial?.let { serial ->
- if (mount) AdbManager.RootAdbManager(serial)
- else AdbManager.UserAdbManager(serial)
+ @CommandLine.Option(
+ names = ["-b", "--patch-bundle"], description = ["One or more bundles of patches"], required = true
+ )
+ @Suppress("unused")
+ private fun setPatchBundles(patchBundles: Array) {
+ patchBundles.firstOrNull { !it.exists() }?.let {
+ throw CommandLine.ParameterException(spec.commandLine(), "Patch bundle ${it.name} does not exist")
}
+ this.patchBundles = patchBundles.toList()
+ }
- // endregion
+ @CommandLine.Option(
+ names = ["--custom-aapt2-binary"], description = ["Path to a custom AAPT binary to compile resources with"]
+ )
+ @Suppress("unused")
+ private fun setAaptBinaryPath(aaptBinaryPath: File) {
+ if (!aaptBinaryPath.exists()) throw CommandLine.ParameterException(
+ spec.commandLine(),
+ "AAPT binary ${aaptBinaryPath.name} does not exist"
+ )
+ this.aaptBinaryPath = aaptBinaryPath
+ }
+
+ override fun run() {
+ val adbManager = deviceSerial?.let { serial -> AdbManager.getAdbManager(serial, mount) }
// region Load patches
logger.info("Loading patches")
val patches = PatchBundleLoader.Jar(*patchBundles.toTypedArray())
- val integrations = integrations
- logger.info("Setting patch options")
-
- optionsFile.let {
- if (it.exists()) patches.setOptions(it)
- else Options.serialize(patches, prettyPrint = true).let(it::writeText)
+ // Warn if a patch can not be found in the supplied patch bundles.
+ if (warn) patches.map { it.name }.toHashSet().let { availableNames ->
+ arrayOf(*includedPatches, *excludedPatches).filter { name ->
+ !availableNames.contains(name)
+ }
+ }.let { unknownPatches ->
+ if (unknownPatches.isEmpty()) return@let
+ logger.warning("Unknown input of patches:\n${unknownPatches.joinToString("\n")}")
}
// endregion
- // region Patch
-
- val patcher = Patcher(
+ Patcher(
PatcherOptions(
apk,
resourceCachePath,
- aaptBinaryPath.path,
+ aaptBinaryPath?.path,
resourceCachePath.absolutePath,
)
- )
+ ).use { patcher ->
+ val filteredPatches = patcher.filterPatchSelection(patches).also { patches ->
+ logger.info("Setting patch options")
- val result = patcher.apply {
- acceptIntegrations(integrations)
- acceptPatches(filterPatchSelection(patches))
-
- // Execute patches.
- runBlocking {
- apply(false).collect { patchResult ->
- patchResult.exception?.let {
- StringWriter().use { writer ->
- it.printStackTrace(PrintWriter(writer))
- logger.severe("${patchResult.patchName} failed: $writer")
- }
- } ?: logger.info("${patchResult.patchName} succeeded")
- }
+ if (optionsFile.exists()) patches.setOptions(optionsFile)
+ else Options.serialize(patches, prettyPrint = true).let(optionsFile::writeText)
}
- }.get()
- patcher.close()
+ // region Patch
+
+ val patcherResult = patcher.apply {
+ acceptIntegrations(integrations)
+ acceptPatches(filteredPatches.toList())
+
+ // Execute patches.
+ runBlocking {
+ apply(false).collect { patchResult ->
+ patchResult.exception?.let {
+ StringWriter().use { writer ->
+ it.printStackTrace(PrintWriter(writer))
+ logger.severe("${patchResult.patch.name} failed:\n$writer")
+ }
+ } ?: logger.info("${patchResult.patch.name} succeeded")
+ }
+ }
+ }.get()
+
+ // endregion
- // endregion
+ // region Save
- // region Finish
+ val tempFile = resourceCachePath.resolve(apk.name).apply {
+ ApkUtils.copyAligned(apk, this, patcherResult)
+ }
- val alignAndSignedFile = sign(
- apk.newAlignedFile(
- result, resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_aligned.apk")
+ val keystoreFilePath = keystoreFilePath ?: outputFilePath.absoluteFile.parentFile
+ .resolve("${outputFilePath.nameWithoutExtension}.keystore")
+
+ if (!mount) ApkUtils.sign(
+ tempFile,
+ outputFilePath,
+ ApkUtils.SigningOptions(
+ keystoreFilePath,
+ keyStorePassword,
+ alias,
+ password,
+ signer
+ )
)
- )
- logger.info("Copying to ${outputFilePath.name}")
- alignAndSignedFile.copyTo(outputFilePath, overwrite = true)
+ // endregion
+
+ // region Install
- adbManager?.install(AdbManager.Apk(outputFilePath, patcher.context.packageMetadata.packageName))
+ adbManager?.install(AdbManager.Apk(outputFilePath, patcher.context.packageMetadata.packageName))
+
+ // endregion
+ }
if (purge) {
logger.info("Purging temporary files")
purge(resourceCachePath)
}
-
- // endregion
}
/**
* Filter the patches to be added to the patcher. The filter is based on the following:
- * - [includedPatches] (explicitly included)
- * - [excludedPatches] (explicitly excluded)
- * - [exclusive] (only include patches that are explicitly included)
- * - [force] (ignore patches incompatibility to versions)
- * - Package name and version of the input APK file (if [force] is false)
*
* @param patches The patches to filter.
* @return The filtered patches.
*/
- private fun Patcher.filterPatchSelection(patches: PatchList) = buildList {
- // TODO: Remove this eventually because
- // patches named "patch-name" and "patch name" will conflict.
- fun String.format() = lowercase().replace(" ", "-")
-
- val formattedExcludedPatches = excludedPatches.map { it.format() }
- val formattedIncludedPatches = includedPatches.map { it.format() }
-
+ private fun Patcher.filterPatchSelection(patches: PatchSet): PatchSet = buildSet {
val packageName = context.packageMetadata.packageName
val packageVersion = context.packageMetadata.packageVersion
patches.forEach patch@{ patch ->
- val formattedPatchName = patch.patchName.format()
+ val patchName = patch.name!!
- val explicitlyExcluded = formattedExcludedPatches.contains(formattedPatchName)
- if (explicitlyExcluded) return@patch logger.info("Excluding ${patch.patchName}")
+ val explicitlyExcluded = excludedPatches.contains(patchName)
+ if (explicitlyExcluded) return@patch logger.info("Excluding $patchName")
// Make sure the patch is compatible with the supplied APK files package name and version.
patch.compatiblePackages?.let { packages ->
packages.singleOrNull { it.name == packageName }?.let { `package` ->
- val matchesVersion = force || `package`.versions.let {
- it.isEmpty() || it.any { version -> version == packageVersion }
- }
+ val matchesVersion = force || `package`.versions?.let {
+ it.any { version -> version == packageVersion }
+ } ?: true
if (!matchesVersion) return@patch logger.warning(
- "${patch.patchName} is incompatible with version $packageVersion. "
+ "$patchName is incompatible with version $packageVersion. "
+ "This patch is only compatible with version "
+ packages.joinToString(";") { pkg ->
- "${pkg.name}: ${pkg.versions.joinToString(", ")}"
+ "${pkg.name}: ${pkg.versions!!.joinToString(", ")}"
}
)
- } ?: return@patch logger.fine("${patch.patchName} is incompatible with $packageName. "
+ } ?: return@patch logger.fine(
+ "$patchName is incompatible with $packageName. "
+ "This patch is only compatible with "
+ packages.joinToString(", ") { `package` -> `package`.name })
return@let
- } ?: logger.fine("$formattedPatchName: No constraint on packages.")
+ } ?: logger.fine("$patchName has no constraint on packages.")
- // If the patch is implicitly included, it will be only included if [exclusive] is false.
- val implicitlyIncluded = !exclusive && patch.include
- // If the patch is explicitly included, it will be included even if [exclusive] is false.
- val explicitlyIncluded = formattedIncludedPatches.contains(formattedPatchName)
+ // If the patch is implicitly used, it will be only included if [exclusive] is false.
+ val implicitlyIncluded = !exclusive && patch.use
+ // If the patch is explicitly used, it will be included even if [exclusive] is false.
+ val explicitlyIncluded = includedPatches.contains(patchName)
val included = implicitlyIncluded || explicitlyIncluded
- if (!included) return@patch logger.info("${patch.patchName} excluded by default") // Case 1.
+ if (!included) return@patch logger.info("$patchName excluded") // Case 1.
- logger.fine("Adding $formattedPatchName")
+ logger.fine("Adding $patchName")
add(patch)
}
}
- /**
- * Create a new aligned APK file.
- *
- * @param result The result of the patching process.
- * @param outputFile The file to save the aligned APK to.
- */
- private fun File.newAlignedFile(
- result: PatcherResult, outputFile: File
- ): File {
- logger.info("Aligning $name")
-
- if (outputFile.exists()) outputFile.delete()
-
- ZipFile(outputFile).use { file ->
- result.dexFiles.forEach {
- file.addEntryCompressData(
- ZipEntry.createWithName(it.name), it.stream.readBytes()
- )
- }
-
- result.resourceFile?.let {
- file.copyEntriesFromFileAligned(
- ZipFile(it), ZipAligner::getEntryAlignment
- )
- }
-
- // TODO: Do not compress result.doNotCompress
-
- file.copyEntriesFromFileAligned(
- ZipFile(this), ZipAligner::getEntryAlignment
- )
- }
-
- return outputFile
- }
-
- /**
- * Sign the APK file.
- *
- * @param inputFile The APK file to sign.
- * @return The signed APK file. If [mount] is true, the input file will be returned.
- */
- private fun sign(inputFile: File) = if (mount) inputFile
- else {
- logger.info("Signing ${inputFile.name}")
-
- val keyStoreFilePath = keystorePath
- ?: outputFilePath.absoluteFile.parentFile.resolve("${outputFilePath.nameWithoutExtension}.keystore").canonicalPath
-
- val options = SigningOptions(
- commonName, password, keyStoreFilePath
- )
-
- ApkSigner(options).signApk(
- inputFile, resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_signed.apk")
- )
- }
-
private fun purge(resourceCachePath: File) {
val result = if (resourceCachePath.deleteRecursively()) "Purged resource cache directory"
else "Failed to purge resource cache directory"
diff --git a/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt b/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt
index 593680d3..59a83dca 100644
--- a/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt
+++ b/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt
@@ -1,6 +1,6 @@
package app.revanced.cli.command.utility
-import app.revanced.utils.adb.AdbManager
+import app.revanced.library.adb.AdbManager
import picocli.CommandLine.*
import java.io.File
import java.util.logging.Logger
@@ -28,15 +28,11 @@ internal object InstallCommand : Runnable {
)
private var packageName: String? = null
- override fun run() = try {
- deviceSerials.forEach { deviceSerial ->
- if (packageName != null) {
- AdbManager.RootAdbManager(deviceSerial)
- } else {
- AdbManager.UserAdbManager(deviceSerial)
- }.install(AdbManager.Apk(apk, packageName))
+ override fun run() = deviceSerials.forEach { deviceSerial ->
+ try {
+ AdbManager.getAdbManager(deviceSerial, packageName != null).install(AdbManager.Apk(apk, packageName))
+ } catch (e: AdbManager.DeviceNotFoundException) {
+ logger.severe(e.toString())
}
- } catch (e: AdbManager.DeviceNotFoundException) {
- logger.severe(e.toString())
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt b/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt
index e17bec74..d89b050b 100644
--- a/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt
+++ b/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt
@@ -1,6 +1,6 @@
package app.revanced.cli.command.utility
-import app.revanced.utils.adb.AdbManager
+import app.revanced.library.adb.AdbManager
import picocli.CommandLine.*
import picocli.CommandLine.Help.Visibility.ALWAYS
import java.util.logging.Logger
@@ -26,15 +26,11 @@ internal object UninstallCommand : Runnable {
)
private var unmount: Boolean = false
- override fun run() = try {
- deviceSerials.forEach { deviceSerial ->
- if (unmount) {
- AdbManager.RootAdbManager(deviceSerial)
- } else {
- AdbManager.UserAdbManager(deviceSerial)
- }.uninstall(packageName)
+ override fun run() = deviceSerials.forEach { deviceSerial ->
+ try {
+ AdbManager.getAdbManager(deviceSerial, unmount).uninstall(packageName)
+ } catch (e: AdbManager.DeviceNotFoundException) {
+ logger.severe(e.toString())
}
- } catch (e: AdbManager.DeviceNotFoundException) {
- logger.severe(e.toString())
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/utils/Options.kt b/src/main/kotlin/app/revanced/utils/Options.kt
deleted file mode 100644
index 408e2039..00000000
--- a/src/main/kotlin/app/revanced/utils/Options.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-package app.revanced.utils
-
-import app.revanced.cli.command.PatchList
-import app.revanced.patcher.extensions.PatchExtensions.options
-import app.revanced.patcher.extensions.PatchExtensions.patchName
-import app.revanced.patcher.patch.NoSuchOptionException
-import app.revanced.utils.Options.PatchOption.Option
-import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
-import java.io.File
-import java.util.logging.Logger
-
-
-internal object Options {
- private val logger = Logger.getLogger(Options::class.java.name)
-
- private var mapper = jacksonObjectMapper()
-
- /**
- * Serializes the options for the patches in the list.
- *
- * @param patches The list of patches to serialize.
- * @param prettyPrint Whether to pretty print the JSON.
- * @return The JSON string containing the options.
- * @see PatchList
- */
- fun serialize(patches: PatchList, prettyPrint: Boolean = false): String = patches
- .filter { it.options?.any() == true }
- .map { patch ->
- PatchOption(
- patch.patchName,
- patch.options!!.map { option -> Option(option.key, option.value) }
- )
- }
- // See https://github.com/revanced/revanced-patches/pull/2434/commits/60e550550b7641705e81aa72acfc4faaebb225e7.
- .distinctBy { it.patchName }
- .let {
- if (prettyPrint)
- mapper.writerWithDefaultPrettyPrinter().writeValueAsString(it)
- else
- mapper.writeValueAsString(it)
- }
-
- /**
- * Deserializes the options for the patches in the list.
- *
- * @param json The JSON string containing the options.
- * @return The list of [PatchOption]s.
- * @see PatchOption
- * @see PatchList
- */
- @Suppress("MemberVisibilityCanBePrivate")
- fun deserialize(json: String): Array = mapper.readValue(json, Array::class.java)
-
- /**
- * Sets the options for the patches in the list.
- *
- * @param json The JSON string containing the options.
- */
- fun PatchList.setOptions(json: String) {
- filter { it.options?.any() == true }.let { patches ->
- if (patches.isEmpty()) return
-
- val patchOptions = deserialize(json)
-
- patches.forEach patch@{ patch ->
- patchOptions.find { option -> option.patchName == patch.patchName }?.let {
- it.options.forEach { option ->
- try {
- patch.options?.set(option.key, option.value)
- ?: run{
- logger.warning("${patch.patchName} has no options")
- return@patch
- }
- } catch (e: NoSuchOptionException) {
- logger.info(e.toString())
- }
- }
- }
- }
- }
- }
-
- /**
- * Sets the options for the patches in the list.
- *
- * @param file The file containing the JSON string containing the options.
- * @see setOptions
- */
- fun PatchList.setOptions(file: File) = setOptions(file.readText())
-
- /**
- * Data class for a patch and its [Option]s.
- *
- * @property patchName The name of the patch.
- * @property options The [Option]s for the patch.
- */
- internal data class PatchOption(
- val patchName: String,
- val options: List
- ) {
-
- /**
- * Data class for patch option.
- *
- * @property key The name of the option.
- * @property value The value of the option.
- */
- internal data class Option(val key: String, val value: Any?)
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/utils/adb/AdbManager.kt b/src/main/kotlin/app/revanced/utils/adb/AdbManager.kt
deleted file mode 100644
index 1bd00430..00000000
--- a/src/main/kotlin/app/revanced/utils/adb/AdbManager.kt
+++ /dev/null
@@ -1,140 +0,0 @@
-package app.revanced.utils.adb
-
-import app.revanced.utils.adb.AdbManager.Apk
-import app.revanced.utils.adb.Constants.CREATE_DIR
-import app.revanced.utils.adb.Constants.DELETE
-import app.revanced.utils.adb.Constants.INSTALLATION_PATH
-import app.revanced.utils.adb.Constants.INSTALL_MOUNT
-import app.revanced.utils.adb.Constants.INSTALL_PATCHED_APK
-import app.revanced.utils.adb.Constants.MOUNT_PATH
-import app.revanced.utils.adb.Constants.MOUNT_SCRIPT
-import app.revanced.utils.adb.Constants.PATCHED_APK_PATH
-import app.revanced.utils.adb.Constants.PLACEHOLDER
-import app.revanced.utils.adb.Constants.RESTART
-import app.revanced.utils.adb.Constants.TMP_PATH
-import app.revanced.utils.adb.Constants.UMOUNT
-import se.vidstige.jadb.JadbConnection
-import se.vidstige.jadb.managers.Package
-import se.vidstige.jadb.managers.PackageManager
-import java.io.Closeable
-import java.io.File
-import java.util.logging.Logger
-
-/**
- * Adb manager. Used to install and uninstall [Apk] files.
- *
- * @param deviceSerial The serial of the device.
- */
-internal sealed class AdbManager(deviceSerial: String? = null) : Closeable {
- protected val logger: Logger = Logger.getLogger(AdbManager::class.java.name)
-
- protected val device = JadbConnection().devices.find { device -> device.serial == deviceSerial }
- ?: throw DeviceNotFoundException(deviceSerial)
-
- init {
- logger.fine("Established connection to $deviceSerial")
- }
-
- /**
- * Installs the [Apk] file.
- *
- * @param apk The [Apk] file.
- */
- open fun install(apk: Apk) {
- logger.info("Finished installing ${apk.file.name}")
- }
-
- /**
- * Uninstalls the package.
- *
- * @param packageName The package name.
- */
- open fun uninstall(packageName: String) {
- logger.info("Finished uninstalling $packageName")
- }
-
- /**
- * Closes the [AdbManager] instance.
- */
- override fun close() {
- logger.fine("Closed")
- }
-
- class RootAdbManager(deviceSerial: String) : AdbManager(deviceSerial) {
- init {
- if (!device.hasSu()) throw IllegalArgumentException("Root required on $deviceSerial. Task failed")
- }
-
- override fun install(apk: Apk) {
- logger.info("Installing by mounting")
-
- val applyReplacement = getPlaceholderReplacement(
- apk.packageName ?: throw IllegalArgumentException("Package name is required")
- )
-
- device.push(apk.file, TMP_PATH)
-
- device.run("$CREATE_DIR $INSTALLATION_PATH")
- device.run(INSTALL_PATCHED_APK.applyReplacement())
-
- device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement())
-
- device.run(INSTALL_MOUNT.applyReplacement())
- device.run(UMOUNT.applyReplacement()) // Sanity check.
- device.run(MOUNT_PATH.applyReplacement())
- device.run(RESTART.applyReplacement())
- device.run(DELETE.applyReplacement(TMP_PATH).applyReplacement())
-
- super.install(apk)
- }
-
- override fun uninstall(packageName: String) {
- logger.info("Uninstalling $packageName by unmounting")
-
- val applyReplacement = getPlaceholderReplacement(packageName)
-
- device.run(UMOUNT.applyReplacement(packageName))
- device.run(DELETE.applyReplacement(PATCHED_APK_PATH).applyReplacement())
- device.run(DELETE.applyReplacement(MOUNT_PATH).applyReplacement())
- device.run(DELETE.applyReplacement(TMP_PATH).applyReplacement())
-
- super.uninstall(packageName)
- }
-
- companion object Utils {
- private fun getPlaceholderReplacement(with: String): String.() -> String = { replace(PLACEHOLDER, with) }
- private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
- }
- }
-
- class UserAdbManager(deviceSerial: String) : AdbManager(deviceSerial) {
- private val packageManager = PackageManager(device)
-
- override fun install(apk: Apk) {
- PackageManager(device).install(apk.file)
-
- super.install(apk)
- }
-
- override fun uninstall(packageName: String) {
- logger.info("Uninstalling $packageName")
-
- packageManager.uninstall(Package(packageName))
-
- super.uninstall(packageName)
- }
- }
-
- /**
- * Apk file for [AdbManager].
- *
- * @param file The [Apk] file.
- * @param packageName The package name of the [Apk] file.
- */
- internal class Apk(val file: File, val packageName: String? = null)
-
- internal class DeviceNotFoundException(deviceSerial: String?) :
- Exception(deviceSerial?.let {
- "The device with the ADB device serial \"$deviceSerial\" can not be found"
- } ?: "No ADB device found")
-}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/utils/adb/Commands.kt b/src/main/kotlin/app/revanced/utils/adb/Commands.kt
deleted file mode 100644
index 7ec7eb8b..00000000
--- a/src/main/kotlin/app/revanced/utils/adb/Commands.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package app.revanced.utils.adb
-
-import se.vidstige.jadb.JadbDevice
-import se.vidstige.jadb.RemoteFile
-import se.vidstige.jadb.ShellProcessBuilder
-import java.io.File
-
-
-internal fun JadbDevice.buildCommand(command: String, su: Boolean = true): ShellProcessBuilder {
- if (su) return shellProcessBuilder("su -c \'$command\'")
-
- val args = command.split(" ") as ArrayList
- val cmd = args.removeFirst()
-
- return shellProcessBuilder(cmd, *args.toTypedArray())
-}
-
-internal fun JadbDevice.run(command: String, su: Boolean = true): Int {
- return this.buildCommand(command, su).start().waitFor()
-}
-
-internal fun JadbDevice.hasSu() =
- this.startCommand("su -h", false).waitFor() == 0
-
-internal fun JadbDevice.push(file: File, targetFilePath: String) =
- push(file, RemoteFile(targetFilePath))
-
-internal fun JadbDevice.createFile(targetFile: String, content: String) =
- push(content.byteInputStream(), System.currentTimeMillis(), 644, RemoteFile(targetFile))
-
-
-private fun JadbDevice.startCommand(command: String, su: Boolean) =
- shellProcessBuilder(if (su) "su -c '$command'" else command).start()
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/utils/adb/Constants.kt b/src/main/kotlin/app/revanced/utils/adb/Constants.kt
deleted file mode 100644
index ea5095e8..00000000
--- a/src/main/kotlin/app/revanced/utils/adb/Constants.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package app.revanced.utils.adb
-
-internal object Constants {
- internal const val PLACEHOLDER = "PLACEHOLDER"
-
- internal const val TMP_PATH = "/data/local/tmp/revanced.tmp"
- internal const val INSTALLATION_PATH = "/data/adb/revanced/"
- internal const val PATCHED_APK_PATH = "$INSTALLATION_PATH$PLACEHOLDER.apk"
- internal const val MOUNT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
-
- internal const val DELETE = "rm -rf $PLACEHOLDER"
- internal const val CREATE_DIR = "mkdir -p"
- internal const val RESTART = "pm resolve-activity --brief $PLACEHOLDER | tail -n 1 | " +
- "xargs am start -n && kill ${'$'}(pidof -s $PLACEHOLDER)"
-
- internal const val INSTALL_PATCHED_APK = "base_path=\"$PATCHED_APK_PATH\" && " +
- "mv $TMP_PATH ${'$'}base_path && " +
- "chmod 644 ${'$'}base_path && " +
- "chown system:system ${'$'}base_path && " +
- "chcon u:object_r:apk_data_file:s0 ${'$'}base_path"
-
- internal const val UMOUNT =
- "grep $PLACEHOLDER /proc/mounts | while read -r line; do echo ${'$'}line | cut -d \" \" -f 2 | sed 's/apk.*/apk/' | xargs -r umount -l; done"
-
- internal const val INSTALL_MOUNT = "mv $TMP_PATH $MOUNT_PATH && chmod +x $MOUNT_PATH"
-
- internal val MOUNT_SCRIPT =
- """
- #!/system/bin/sh
- MAGISKTMP="${'$'}(magisk --path)" || MAGISKTMP=/sbin
- MIRROR="${'$'}MAGISKTMP/.magisk/mirror"
- while [ "${'$'}(getprop sys.boot_completed | tr -d '\r')" != "1" ]; do sleep 1; done
-
- base_path="$PATCHED_APK_PATH"
- stock_path=${'$'}( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
-
- chcon u:object_r:apk_data_file:s0 ${'$'}base_path
- mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
- """.trimIndent()
-}
diff --git a/src/main/kotlin/app/revanced/utils/align/ZipAligner.kt b/src/main/kotlin/app/revanced/utils/align/ZipAligner.kt
deleted file mode 100644
index 568e20bd..00000000
--- a/src/main/kotlin/app/revanced/utils/align/ZipAligner.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package app.revanced.utils.align
-
-import app.revanced.utils.align.zip.structures.ZipEntry
-
-internal object ZipAligner {
- private const val DEFAULT_ALIGNMENT = 4
- private const val LIBRARY_ALIGNMENT = 4096
-
- fun getEntryAlignment(entry: ZipEntry): Int? =
- if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
-}
diff --git a/src/main/kotlin/app/revanced/utils/align/zip/Extensions.kt b/src/main/kotlin/app/revanced/utils/align/zip/Extensions.kt
deleted file mode 100644
index 330c6898..00000000
--- a/src/main/kotlin/app/revanced/utils/align/zip/Extensions.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package app.revanced.utils.align.zip
-
-import java.io.DataInput
-import java.io.DataOutput
-import java.nio.ByteBuffer
-
-fun UInt.toLittleEndian() =
- (((this.toInt() and 0xff000000.toInt()) shr 24) or ((this.toInt() and 0x00ff0000) shr 8) or ((this.toInt() and 0x0000ff00) shl 8) or (this.toInt() shl 24)).toUInt()
-
-fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort()
-
-fun UInt.toBigEndian() = (((this.toInt() and 0xff) shl 24) or ((this.toInt() and 0xff00) shl 8)
- or ((this.toInt() and 0x00ff0000) ushr 8) or (this.toInt() ushr 24)).toUInt()
-
-fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort()
-
-fun ByteBuffer.getUShort() = this.getShort().toUShort()
-fun ByteBuffer.getUInt() = this.getInt().toUInt()
-
-fun ByteBuffer.putUShort(ushort: UShort) = this.putShort(ushort.toShort())
-fun ByteBuffer.putUInt(uint: UInt) = this.putInt(uint.toInt())
-
-fun DataInput.readUShort() = this.readShort().toUShort()
-fun DataInput.readUInt() = this.readInt().toUInt()
-
-fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt())
-fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt())
-
-fun DataInput.readUShortLE() = this.readUShort().toBigEndian()
-fun DataInput.readUIntLE() = this.readUInt().toBigEndian()
-
-fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian())
-fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian())
diff --git a/src/main/kotlin/app/revanced/utils/align/zip/ZipFile.kt b/src/main/kotlin/app/revanced/utils/align/zip/ZipFile.kt
deleted file mode 100644
index f961488c..00000000
--- a/src/main/kotlin/app/revanced/utils/align/zip/ZipFile.kt
+++ /dev/null
@@ -1,181 +0,0 @@
-package app.revanced.utils.align.zip
-
-import app.revanced.utils.align.zip.structures.ZipEndRecord
-import app.revanced.utils.align.zip.structures.ZipEntry
-import java.io.Closeable
-import java.io.File
-import java.io.RandomAccessFile
-import java.nio.ByteBuffer
-import java.nio.channels.FileChannel
-import java.util.zip.CRC32
-import java.util.zip.Deflater
-
-class ZipFile(file: File) : Closeable {
- private var entries: MutableList = mutableListOf()
-
- private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw")
- private var centralDirectoryNeedsRewrite = false
-
- private val compressionLevel = 5
-
- init {
- // If file isn't empty try to load entries.
- if (file.length() > 0) {
- val endRecord = findEndRecord()
-
- if (endRecord.diskNumber > 0u || endRecord.totalEntries != endRecord.diskEntries)
- throw IllegalArgumentException("Multi-file archives are not supported")
-
- entries = readEntries(endRecord).toMutableList()
- }
-
- // Seek back to start for writing.
- filePointer.seek(0)
- }
-
- private fun findEndRecord(): ZipEndRecord {
- // Look from end to start since end record is at the end.
- for (i in filePointer.length() - 1 downTo 0) {
- filePointer.seek(i)
- // Possible beginning of signature.
- if (filePointer.readByte() == 0x50.toByte()) {
- // Seek back to get the full int.
- filePointer.seek(i)
- val possibleSignature = filePointer.readUIntLE()
- if (possibleSignature == ZipEndRecord.ECD_SIGNATURE) {
- filePointer.seek(i)
- return ZipEndRecord.fromECD(filePointer)
- }
- }
- }
-
- throw Exception("Couldn't find end record")
- }
-
- private fun readEntries(endRecord: ZipEndRecord): List {
- filePointer.seek(endRecord.centralDirectoryStartOffset.toLong())
-
- val numberOfEntries = endRecord.diskEntries.toInt()
-
- return buildList(numberOfEntries) {
- for (i in 1..numberOfEntries) {
- add(
- ZipEntry.fromCDE(filePointer).also
- {
- //for some reason the local extra field can be different from the central one
- it.readLocalExtra(
- filePointer.channel.map(
- FileChannel.MapMode.READ_ONLY,
- it.localHeaderOffset.toLong() + 28,
- 2
- )
- )
- })
- }
- }
- }
-
- private fun writeCD() {
- val centralDirectoryStartOffset = filePointer.channel.position().toUInt()
-
- entries.forEach {
- filePointer.channel.write(it.toCDE())
- }
-
- val entriesCount = entries.size.toUShort()
-
- val endRecord = ZipEndRecord(
- 0u,
- 0u,
- entriesCount,
- entriesCount,
- filePointer.channel.position().toUInt() - centralDirectoryStartOffset,
- centralDirectoryStartOffset,
- ""
- )
-
- filePointer.channel.write(endRecord.toECD())
- }
-
- private fun addEntry(entry: ZipEntry, data: ByteBuffer) {
- centralDirectoryNeedsRewrite = true
-
- entry.localHeaderOffset = filePointer.channel.position().toUInt()
-
- filePointer.channel.write(entry.toLFH())
- filePointer.channel.write(data)
-
- entries.add(entry)
- }
-
- fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
- val compressor = Deflater(compressionLevel, true)
- compressor.setInput(data)
- compressor.finish()
-
- val uncompressedSize = data.size
- val compressedData = ByteArray(uncompressedSize) // I'm guessing compression won't make the data bigger.
-
- val compressedDataLength = compressor.deflate(compressedData)
- val compressedBuffer =
- ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray())
-
- compressor.end()
-
- val crc = CRC32()
- crc.update(data)
-
- entry.compression = 8u // Deflate compression.
- entry.uncompressedSize = uncompressedSize.toUInt()
- entry.compressedSize = compressedDataLength.toUInt()
- entry.crc32 = crc.value.toUInt()
-
- addEntry(entry, compressedBuffer)
- }
-
- private fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) {
- alignment?.let {
- // Calculate where data would end up.
- val dataOffset = filePointer.filePointer + entry.LFHSize
-
- val mod = dataOffset % alignment
-
- // Wrong alignment.
- if (mod != 0L) {
- // Add padding at end of extra field.
- entry.localExtraField =
- entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt())
- }
- }
-
- addEntry(entry, data)
- }
-
- private fun getDataForEntry(entry: ZipEntry): ByteBuffer {
- return filePointer.channel.map(
- FileChannel.MapMode.READ_ONLY,
- entry.dataOffset.toLong(),
- entry.compressedSize.toLong()
- )
- }
-
- /**
- * Copies all entries from [file] to this file but skip already existing entries.
- *
- * @param file The file to copy entries from.
- * @param entryAlignment A function that returns the alignment for a given entry.
- */
- fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) {
- for (entry in file.entries) {
- if (entries.any { it.fileName == entry.fileName }) continue // Skip duplicates
-
- val data = file.getDataForEntry(entry)
- addEntryCopyData(entry, data, entryAlignment(entry))
- }
- }
-
- override fun close() {
- if (centralDirectoryNeedsRewrite) writeCD()
- filePointer.close()
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/utils/align/zip/structures/ZipEndRecord.kt b/src/main/kotlin/app/revanced/utils/align/zip/structures/ZipEndRecord.kt
deleted file mode 100644
index 387679ed..00000000
--- a/src/main/kotlin/app/revanced/utils/align/zip/structures/ZipEndRecord.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-package app.revanced.utils.align.zip.structures
-
-import app.revanced.utils.align.zip.putUInt
-import app.revanced.utils.align.zip.putUShort
-import app.revanced.utils.align.zip.readUIntLE
-import app.revanced.utils.align.zip.readUShortLE
-import java.io.DataInput
-import java.nio.ByteBuffer
-import java.nio.ByteOrder
-
-data class ZipEndRecord(
- val diskNumber: UShort,
- val startingDiskNumber: UShort,
- val diskEntries: UShort,
- val totalEntries: UShort,
- val centralDirectorySize: UInt,
- val centralDirectoryStartOffset: UInt,
- val fileComment: String,
-) {
-
- companion object {
- const val ECD_HEADER_SIZE = 22
- const val ECD_SIGNATURE = 0x06054b50u
-
- fun fromECD(input: DataInput): ZipEndRecord {
- val signature = input.readUIntLE()
-
- if (signature != ECD_SIGNATURE)
- throw IllegalArgumentException("Input doesn't start with end record signature")
-
- val diskNumber = input.readUShortLE()
- val startingDiskNumber = input.readUShortLE()
- val diskEntries = input.readUShortLE()
- val totalEntries = input.readUShortLE()
- val centralDirectorySize = input.readUIntLE()
- val centralDirectoryStartOffset = input.readUIntLE()
- val fileCommentLength = input.readUShortLE()
- var fileComment = ""
-
- if (fileCommentLength > 0u) {
- val fileCommentBytes = ByteArray(fileCommentLength.toInt())
- input.readFully(fileCommentBytes)
- fileComment = fileCommentBytes.toString(Charsets.UTF_8)
- }
-
- return ZipEndRecord(
- diskNumber,
- startingDiskNumber,
- diskEntries,
- totalEntries,
- centralDirectorySize,
- centralDirectoryStartOffset,
- fileComment
- )
- }
- }
-
- fun toECD(): ByteBuffer {
- val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
-
- val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size).also { it.order(ByteOrder.LITTLE_ENDIAN) }
-
- buffer.putUInt(ECD_SIGNATURE)
- buffer.putUShort(diskNumber)
- buffer.putUShort(startingDiskNumber)
- buffer.putUShort(diskEntries)
- buffer.putUShort(totalEntries)
- buffer.putUInt(centralDirectorySize)
- buffer.putUInt(centralDirectoryStartOffset)
- buffer.putUShort(commentBytes.size.toUShort())
-
- buffer.put(commentBytes)
-
- buffer.flip()
- return buffer
- }
-}
diff --git a/src/main/kotlin/app/revanced/utils/align/zip/structures/ZipEntry.kt b/src/main/kotlin/app/revanced/utils/align/zip/structures/ZipEntry.kt
deleted file mode 100644
index 316a8360..00000000
--- a/src/main/kotlin/app/revanced/utils/align/zip/structures/ZipEntry.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-package app.revanced.utils.align.zip.structures
-
-import app.revanced.utils.align.zip.*
-import java.io.DataInput
-import java.nio.ByteBuffer
-import java.nio.ByteOrder
-
-data class ZipEntry(
- val version: UShort,
- val versionNeeded: UShort,
- val flags: UShort,
- var compression: UShort,
- val modificationTime: UShort,
- val modificationDate: UShort,
- var crc32: UInt,
- var compressedSize: UInt,
- var uncompressedSize: UInt,
- val diskNumber: UShort,
- val internalAttributes: UShort,
- val externalAttributes: UInt,
- var localHeaderOffset: UInt,
- val fileName: String,
- val extraField: ByteArray,
- val fileComment: String,
- var localExtraField: ByteArray = ByteArray(0), //separate for alignment
-) {
- val LFHSize: Int
- get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
-
- val dataOffset: UInt
- get() = localHeaderOffset + LFHSize.toUInt()
-
- companion object {
- const val CDE_HEADER_SIZE = 46
- const val CDE_SIGNATURE = 0x02014b50u
-
- const val LFH_HEADER_SIZE = 30
- const val LFH_SIGNATURE = 0x04034b50u
-
- fun createWithName(fileName: String): ZipEntry {
- return ZipEntry(
- 0x1403u, //made by unix, version 20
- 0u,
- 0u,
- 0u,
- 0x0821u, //seems to be static time google uses, no idea
- 0x0221u, //same as above
- 0u,
- 0u,
- 0u,
- 0u,
- 0u,
- 0u,
- 0u,
- fileName,
- ByteArray(0),
- ""
- )
- }
-
- fun fromCDE(input: DataInput): ZipEntry {
- val signature = input.readUIntLE()
-
- if (signature != CDE_SIGNATURE)
- throw IllegalArgumentException("Input doesn't start with central directory entry signature")
-
- val version = input.readUShortLE()
- val versionNeeded = input.readUShortLE()
- var flags = input.readUShortLE()
- val compression = input.readUShortLE()
- val modificationTime = input.readUShortLE()
- val modificationDate = input.readUShortLE()
- val crc32 = input.readUIntLE()
- val compressedSize = input.readUIntLE()
- val uncompressedSize = input.readUIntLE()
- val fileNameLength = input.readUShortLE()
- var fileName = ""
- val extraFieldLength = input.readUShortLE()
- val extraField = ByteArray(extraFieldLength.toInt())
- val fileCommentLength = input.readUShortLE()
- var fileComment = ""
- val diskNumber = input.readUShortLE()
- val internalAttributes = input.readUShortLE()
- val externalAttributes = input.readUIntLE()
- val localHeaderOffset = input.readUIntLE()
-
- val variableFieldsLength =
- fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
-
- if (variableFieldsLength > 0) {
- val fileNameBytes = ByteArray(fileNameLength.toInt())
- input.readFully(fileNameBytes)
- fileName = fileNameBytes.toString(Charsets.UTF_8)
-
- input.readFully(extraField)
-
- val fileCommentBytes = ByteArray(fileCommentLength.toInt())
- input.readFully(fileCommentBytes)
- fileComment = fileCommentBytes.toString(Charsets.UTF_8)
- }
-
- flags = (flags and 0b1000u.inv()
- .toUShort()) //disable data descriptor flag as they are not used
-
- return ZipEntry(
- version,
- versionNeeded,
- flags,
- compression,
- modificationTime,
- modificationDate,
- crc32,
- compressedSize,
- uncompressedSize,
- diskNumber,
- internalAttributes,
- externalAttributes,
- localHeaderOffset,
- fileName,
- extraField,
- fileComment,
- )
- }
- }
-
- fun readLocalExtra(buffer: ByteBuffer) {
- buffer.order(ByteOrder.LITTLE_ENDIAN)
- localExtraField = ByteArray(buffer.getUShort().toInt())
- }
-
- fun toLFH(): ByteBuffer {
- val nameBytes = fileName.toByteArray(Charsets.UTF_8)
-
- val buffer = ByteBuffer.allocate(LFH_HEADER_SIZE + nameBytes.size + localExtraField.size)
- .also { it.order(ByteOrder.LITTLE_ENDIAN) }
-
- buffer.putUInt(LFH_SIGNATURE)
- buffer.putUShort(versionNeeded)
- buffer.putUShort(flags)
- buffer.putUShort(compression)
- buffer.putUShort(modificationTime)
- buffer.putUShort(modificationDate)
- buffer.putUInt(crc32)
- buffer.putUInt(compressedSize)
- buffer.putUInt(uncompressedSize)
- buffer.putUShort(nameBytes.size.toUShort())
- buffer.putUShort(localExtraField.size.toUShort())
-
- buffer.put(nameBytes)
- buffer.put(localExtraField)
-
- buffer.flip()
- return buffer
- }
-
- fun toCDE(): ByteBuffer {
- val nameBytes = fileName.toByteArray(Charsets.UTF_8)
- val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
-
- val buffer =
- ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size)
- .also { it.order(ByteOrder.LITTLE_ENDIAN) }
-
- buffer.putUInt(CDE_SIGNATURE)
- buffer.putUShort(version)
- buffer.putUShort(versionNeeded)
- buffer.putUShort(flags)
- buffer.putUShort(compression)
- buffer.putUShort(modificationTime)
- buffer.putUShort(modificationDate)
- buffer.putUInt(crc32)
- buffer.putUInt(compressedSize)
- buffer.putUInt(uncompressedSize)
- buffer.putUShort(nameBytes.size.toUShort())
- buffer.putUShort(extraField.size.toUShort())
- buffer.putUShort(commentBytes.size.toUShort())
- buffer.putUShort(diskNumber)
- buffer.putUShort(internalAttributes)
- buffer.putUInt(externalAttributes)
- buffer.putUInt(localHeaderOffset)
-
- buffer.put(nameBytes)
- buffer.put(extraField)
- buffer.put(commentBytes)
-
- buffer.flip()
- return buffer
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/utils/signing/ApkSigner.kt b/src/main/kotlin/app/revanced/utils/signing/ApkSigner.kt
deleted file mode 100644
index f864490e..00000000
--- a/src/main/kotlin/app/revanced/utils/signing/ApkSigner.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-package app.revanced.utils.signing
-
-import com.android.apksig.ApkSigner
-import org.bouncycastle.asn1.x500.X500Name
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
-import org.bouncycastle.cert.X509v3CertificateBuilder
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.operator.ContentSigner
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileOutputStream
-import java.math.BigInteger
-import java.security.*
-import java.security.cert.X509Certificate
-import java.util.*
-import java.util.logging.Logger
-
-internal class ApkSigner(
- private val signingOptions: SigningOptions
-) {
- private val logger = Logger.getLogger(ApkSigner::class.java.name)
-
- private val signer: ApkSigner.Builder
- private val passwordCharArray = signingOptions.password.toCharArray()
-
- init {
- Security.addProvider(BouncyCastleProvider())
-
- val keyStore = KeyStore.getInstance("BKS", "BC")
- val alias = keyStore.let { store ->
- FileInputStream(File(signingOptions.keyStoreFilePath).also {
- if (!it.exists()) {
- logger.info("Creating keystore at ${it.absolutePath}")
- newKeystore(it)
- } else {
- logger.info("Using keystore at ${it.absolutePath}")
- }
- }).use { fis -> store.load(fis, null) }
- store.aliases().nextElement()
- }
-
- with(
- ApkSigner.SignerConfig.Builder(
- signingOptions.cn,
- keyStore.getKey(alias, passwordCharArray) as PrivateKey,
- listOf(keyStore.getCertificate(alias) as X509Certificate)
- ).build()
- ) {
- this@ApkSigner.signer = ApkSigner.Builder(listOf(this))
- signer.setCreatedBy(signingOptions.cn)
- }
- }
-
- private fun newKeystore(out: File) {
- val (publicKey, privateKey) = createKey()
- val privateKS = KeyStore.getInstance("BKS", "BC")
- privateKS.load(null, passwordCharArray)
- privateKS.setKeyEntry("alias", privateKey, passwordCharArray, arrayOf(publicKey))
- privateKS.store(FileOutputStream(out), passwordCharArray)
- }
-
- private fun createKey(): Pair {
- val gen = KeyPairGenerator.getInstance("RSA")
- gen.initialize(2048)
- val pair = gen.generateKeyPair()
- var serialNumber: BigInteger
- do serialNumber = BigInteger.valueOf(SecureRandom().nextLong()) while (serialNumber < BigInteger.ZERO)
- val x500Name = X500Name("CN=${signingOptions.cn}")
- val builder = X509v3CertificateBuilder(
- x500Name,
- serialNumber,
- Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L),
- Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 366L * 30L),
- Locale.ENGLISH,
- x500Name,
- SubjectPublicKeyInfo.getInstance(pair.public.encoded)
- )
- val signer: ContentSigner = JcaContentSignerBuilder("SHA256withRSA").build(pair.private)
- return JcaX509CertificateConverter().getCertificate(builder.build(signer)) to pair.private
- }
-
- fun signApk(input: File, output: File): File {
- signer.setInputApk(input)
- signer.setOutputApk(output)
-
- signer.build().sign()
-
- return output
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/utils/signing/SigningOptions.kt b/src/main/kotlin/app/revanced/utils/signing/SigningOptions.kt
deleted file mode 100644
index 9ffdc6df..00000000
--- a/src/main/kotlin/app/revanced/utils/signing/SigningOptions.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package app.revanced.utils.signing
-
-data class SigningOptions(
- val cn: String,
- val password: String,
- val keyStoreFilePath: String
-)
\ No newline at end of file
diff --git a/src/test/kotlin/app/revanced/patcher/options/PatchOptionOptionsTest.kt b/src/test/kotlin/app/revanced/patcher/options/PatchOptionOptionsTest.kt
deleted file mode 100644
index 9abfca55..00000000
--- a/src/test/kotlin/app/revanced/patcher/options/PatchOptionOptionsTest.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package app.revanced.patcher.options
-
-import app.revanced.patcher.data.BytecodeContext
-import app.revanced.patcher.data.Context
-import app.revanced.patcher.patch.BytecodePatch
-import app.revanced.patcher.patch.OptionsContainer
-import app.revanced.patcher.patch.Patch
-import app.revanced.patcher.patch.PatchOption
-import app.revanced.utils.Options
-import app.revanced.utils.Options.setOptions
-import org.junit.jupiter.api.MethodOrderer
-import org.junit.jupiter.api.Order
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.TestMethodOrder
-
-class PatchOptionsTestPatch : BytecodePatch() {
- override fun execute(context: BytecodeContext) {
- // Do nothing
- }
-
- companion object : OptionsContainer() {
- var key1 by option(
- PatchOption.StringOption(
- "key1", null, "title1", "description1"
- )
- )
-
- var key2 by option(
- PatchOption.BooleanOption(
- "key2", true, "title2", "description2"
- )
- )
- }
-}
-
-@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
-internal object PatchOptionOptionsTest {
- private var patches = listOf(PatchOptionsTestPatch::class.java as Class>>)
-
- @Test
- @Order(1)
- fun serializeTest() {
- assert(SERIALIZED_JSON == Options.serialize(patches))
- }
-
- @Test
- @Order(2)
- fun loadOptionsTest() {
- patches.setOptions(CHANGED_JSON)
-
- assert(PatchOptionsTestPatch.key1 == "test")
- assert(PatchOptionsTestPatch.key2 == false)
- }
-
- private const val SERIALIZED_JSON =
- "[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":null},{\"key\":\"key2\",\"value\":true}]}]"
-
- private const val CHANGED_JSON =
- "[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":\"test\"},{\"key\":\"key2\",\"value\":false}]}]"
-}
\ No newline at end of file