mirror of
https://github.com/ReVanced/revanced-library.git
synced 2026-01-18 00:43:56 +00:00
Compare commits
56 Commits
v1.5.1-dev
...
feat/netwo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42de60e95b | ||
|
|
fe3e1c9dc8 | ||
|
|
825e6490fe | ||
|
|
43d655aea5 | ||
|
|
8d0e4b2470 | ||
|
|
4486c59674 | ||
|
|
8e4d5765e1 | ||
|
|
b15efa41f8 | ||
|
|
af0aba4a8e | ||
|
|
4c6a6360cf | ||
|
|
80e35270c4 | ||
|
|
13b6a846d7 | ||
|
|
2df3484b68 | ||
|
|
f2959b610a | ||
|
|
2a30845f61 | ||
|
|
1b4d87e563 | ||
|
|
7f7dfdd5b5 | ||
|
|
56773fa3d3 | ||
|
|
249372c31f | ||
|
|
e7bed8565e | ||
|
|
4a24e2e92d | ||
|
|
fca8a3f4c0 | ||
|
|
f59ecbccd1 | ||
|
|
c92be32607 | ||
|
|
51109c4768 | ||
|
|
f2bd3f5eee | ||
|
|
cc5ee29d14 | ||
|
|
c4bad9a653 | ||
|
|
3f713cf76b | ||
|
|
dfe48a24bf | ||
|
|
132ad13670 | ||
|
|
1670ceff9f | ||
|
|
41805fcb0b | ||
|
|
0e9939f70d | ||
|
|
f28a7ddeec | ||
|
|
3e574e723c | ||
|
|
a73ca721f3 | ||
|
|
393d74b7d9 | ||
|
|
51d6e9976c | ||
|
|
24cecb1e57 | ||
|
|
d4938c57e8 | ||
|
|
1526f98f53 | ||
|
|
244ebc2186 | ||
|
|
486a7acff2 | ||
|
|
f0b8bc5438 | ||
|
|
806f45c77f | ||
|
|
259bcd04c9 | ||
|
|
0d040dcfd0 | ||
|
|
13af960de1 | ||
|
|
d21fd5798c | ||
|
|
1c348f3621 | ||
|
|
0c02680b70 | ||
|
|
b86b8aacea | ||
|
|
3aa6dc223a | ||
|
|
c664a6eaed | ||
|
|
d18e436de1 |
3
.editorconfig
Normal file
3
.editorconfig
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[*.{kt,kts}]
|
||||||
|
ktlint_code_style = intellij_idea
|
||||||
|
ktlint_standard_no-wildcard-imports = disabled
|
||||||
28
.github/workflows/build_pull_request.yml
vendored
Normal file
28
.github/workflows/build_pull_request.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
name: Build pull request
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Cache Gradle
|
||||||
|
uses: burrunan/gradle-cache-action@v1
|
||||||
|
|
||||||
|
- name: Setup Java
|
||||||
|
run: echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: ./gradlew build --no-daemon
|
||||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -6,10 +6,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- dev
|
- dev
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- dev
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@@ -27,6 +23,9 @@ jobs:
|
|||||||
- name: Cache Gradle
|
- name: Cache Gradle
|
||||||
uses: burrunan/gradle-cache-action@v1
|
uses: burrunan/gradle-cache-action@v1
|
||||||
|
|
||||||
|
- name: Setup Java
|
||||||
|
run: echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -41,6 +40,13 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
|
- name: Import GPG key
|
||||||
|
uses: crazy-max/ghaction-import-gpg@v6
|
||||||
|
with:
|
||||||
|
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
|
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
fingerprint: ${{ env.GPG_FINGERPRINT }}
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
name: Dispatch event to documentation repository
|
name: Dispatch event to documentation repository
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
steps:
|
steps:
|
||||||
- uses: peter-evans/repository-dispatch@v2
|
- uses: peter-evans/repository-dispatch@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
||||||
repository: revanced/revanced-documentation
|
repository: revanced/revanced-documentation
|
||||||
123
.gitignore
vendored
123
.gitignore
vendored
@@ -1,118 +1,11 @@
|
|||||||
### Java template
|
|
||||||
# Compiled class file
|
|
||||||
*.class
|
|
||||||
|
|
||||||
# Log file
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# BlueJ files
|
|
||||||
*.ctxt
|
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
|
||||||
.mtj.tmp/
|
|
||||||
|
|
||||||
# Package Files #
|
|
||||||
*.jar
|
|
||||||
*.war
|
|
||||||
*.nar
|
|
||||||
*.ear
|
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
|
||||||
hs_err_pid*
|
|
||||||
|
|
||||||
### JetBrains template
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
.idea/artifacts
|
|
||||||
.idea/compiler.xml
|
|
||||||
.idea/jarRepositories.xml
|
|
||||||
.idea/modules.xml
|
|
||||||
.idea/*.iml
|
|
||||||
.idea/modules
|
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
.idea
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### Gradle template
|
|
||||||
.gradle
|
.gradle
|
||||||
**/build/
|
.idea
|
||||||
!src/**/build/
|
.DS_Store
|
||||||
|
build
|
||||||
# Ignore Gradle GUI config
|
captures
|
||||||
gradle-app.setting
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
local.properties
|
||||||
!gradle-wrapper.jar
|
xcuserdata
|
||||||
|
|
||||||
# Cache of project
|
|
||||||
.gradletasknamecache
|
|
||||||
|
|
||||||
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
|
|
||||||
# gradle/wrapper/gradle-wrapper.properties
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
node_modules/
|
||||||
@@ -32,6 +32,12 @@
|
|||||||
backmergeBranches: [{"from": "main", "to": "dev"}],
|
backmergeBranches: [{"from": "main", "to": "dev"}],
|
||||||
clearWorkspace: true
|
clearWorkspace: true
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@semantic-release/github",
|
||||||
|
{
|
||||||
|
successComment: false
|
||||||
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
152
CHANGELOG.md
152
CHANGELOG.md
@@ -1,3 +1,155 @@
|
|||||||
|
# [2.4.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.3.0...v2.4.0-dev.1) (2024-04-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add local Android installer ([#25](https://github.com/ReVanced/revanced-library/issues/25)) ([43d655a](https://github.com/ReVanced/revanced-library/commit/43d655aea5d86288ae9916630e0f30de219d5cfb))
|
||||||
|
|
||||||
|
# [2.3.0](https://github.com/ReVanced/revanced-library/compare/v2.2.1...v2.3.0) (2024-03-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Support mounting even when Magisk is not installed ([2a30845](https://github.com/ReVanced/revanced-library/commit/2a30845f61d5f77ded7a72ee3d6ab55b4c512d52))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add utility function around key certificate pairs ([2df3484](https://github.com/ReVanced/revanced-library/commit/2df3484b68ed72338a52e76fb4b7ceb9c9c644ed))
|
||||||
|
* Improve exception message ([b15efa4](https://github.com/ReVanced/revanced-library/commit/b15efa41f8dc7d73865d0eab15be274b9ee3d7a3))
|
||||||
|
* Simplify signing utility API ([4c6a636](https://github.com/ReVanced/revanced-library/commit/4c6a6360cf83659d1f5c3a7c5710ac54426e9235))
|
||||||
|
|
||||||
|
# [2.3.0-dev.3](https://github.com/ReVanced/revanced-library/compare/v2.3.0-dev.2...v2.3.0-dev.3) (2024-03-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Improve exception message ([b15efa4](https://github.com/ReVanced/revanced-library/commit/b15efa41f8dc7d73865d0eab15be274b9ee3d7a3))
|
||||||
|
|
||||||
|
# [2.3.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v2.3.0-dev.1...v2.3.0-dev.2) (2024-03-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Simplify signing utility API ([4c6a636](https://github.com/ReVanced/revanced-library/commit/4c6a6360cf83659d1f5c3a7c5710ac54426e9235))
|
||||||
|
|
||||||
|
# [2.3.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.2-dev.1...v2.3.0-dev.1) (2024-03-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add utility function around key certificate pairs ([2df3484](https://github.com/ReVanced/revanced-library/commit/2df3484b68ed72338a52e76fb4b7ceb9c9c644ed))
|
||||||
|
|
||||||
|
## [2.2.2-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.1...v2.2.2-dev.1) (2024-03-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Support mounting even when Magisk is not installed ([2a30845](https://github.com/ReVanced/revanced-library/commit/2a30845f61d5f77ded7a72ee3d6ab55b4c512d52))
|
||||||
|
|
||||||
|
## [2.2.1](https://github.com/ReVanced/revanced-library/compare/v2.2.0...v2.2.1) (2024-03-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Do not specify a provider to automatically select an available one ([249372c](https://github.com/ReVanced/revanced-library/commit/249372c31f7e7975fc9eacb5361bd07dbc5dfb92))
|
||||||
|
|
||||||
|
## [2.2.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.0...v2.2.1-dev.1) (2024-03-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Do not specify a provider to automatically select an available one ([249372c](https://github.com/ReVanced/revanced-library/commit/249372c31f7e7975fc9eacb5361bd07dbc5dfb92))
|
||||||
|
|
||||||
|
# [2.2.0](https://github.com/ReVanced/revanced-library/compare/v2.1.0...v2.2.0) (2024-03-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Make property private ([51109c4](https://github.com/ReVanced/revanced-library/commit/51109c476837828535dcd395a5222d2fcf7fc22c))
|
||||||
|
* Sign APKs using `apksig` ([f59ecbc](https://github.com/ReVanced/revanced-library/commit/f59ecbccd14a08d87d4f18c3c0cc47a884088b99))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Increase default expiration date of certificate ([f2bd3f5](https://github.com/ReVanced/revanced-library/commit/f2bd3f5eeee14ca32094be0d41c32b231a16bcc3))
|
||||||
|
|
||||||
|
# [2.2.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.1.0...v2.2.0-dev.1) (2024-03-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Make property private ([51109c4](https://github.com/ReVanced/revanced-library/commit/51109c476837828535dcd395a5222d2fcf7fc22c))
|
||||||
|
* Sign APKs using `apksig` ([f59ecbc](https://github.com/ReVanced/revanced-library/commit/f59ecbccd14a08d87d4f18c3c0cc47a884088b99))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Increase default expiration date of certificate ([f2bd3f5](https://github.com/ReVanced/revanced-library/commit/f2bd3f5eeee14ca32094be0d41c32b231a16bcc3))
|
||||||
|
|
||||||
|
# [2.1.0](https://github.com/ReVanced/revanced-library/compare/v2.0.0...v2.1.0) (2024-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Use `BKS` instead of default signing provider to fix backwards compatibility ([41805fc](https://github.com/ReVanced/revanced-library/commit/41805fcb0bdc778fe0870427a0a1caa6d4369cee))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Mention APK file name when logging aligning ([244ebc2](https://github.com/ReVanced/revanced-library/commit/244ebc21868c07d1852857f6858c1a53a5561155))
|
||||||
|
|
||||||
|
# [2.1.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v2.1.0-dev.1...v2.1.0-dev.2) (2024-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Use `BKS` instead of default signing provider to fix backwards compatibility ([41805fc](https://github.com/ReVanced/revanced-library/commit/41805fcb0bdc778fe0870427a0a1caa6d4369cee))
|
||||||
|
|
||||||
|
# [2.1.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.0.0...v2.1.0-dev.1) (2024-02-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Mention APK file name when logging aligning ([244ebc2](https://github.com/ReVanced/revanced-library/commit/244ebc21868c07d1852857f6858c1a53a5561155))
|
||||||
|
|
||||||
|
# [2.0.0](https://github.com/ReVanced/revanced-library/compare/v1.5.0...v2.0.0) (2024-02-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Map dependencies from `KClass` into `String` to fix serialization ([57e36ab](https://github.com/ReVanced/revanced-library/commit/57e36ab5c15a5fa7c50fb689ee43ad4eb9a4a515))
|
||||||
|
* Use the JVM name instead of the value from `KClass#toString` ([d18e436](https://github.com/ReVanced/revanced-library/commit/d18e436de1df14452ecaa7d827be5e6596ba8a2d))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Use `apkzlib` instead of own implementations and bump ReVanced Patcher ([3aa6dc2](https://github.com/ReVanced/revanced-library/commit/3aa6dc223a9a1a2f735eda407917548ecbd366aa))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This commit removes deprecated APIs and bumps ReVanced Patcher. Because of it's changes, `apkzlib` is now used instead of own implementations of `ZipFile`
|
||||||
|
|
||||||
|
# [2.0.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.5.1-dev.2...v2.0.0-dev.1) (2024-02-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Use `apkzlib` instead of own implementations and bump ReVanced Patcher ([3aa6dc2](https://github.com/ReVanced/revanced-library/commit/3aa6dc223a9a1a2f735eda407917548ecbd366aa))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This commit removes deprecated APIs and bumps ReVanced Patcher. Because of it's changes, `apkzlib` is now used instead of own implementations of `ZipFile`
|
||||||
|
|
||||||
|
## [1.5.1-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.5.1-dev.1...v1.5.1-dev.2) (2024-02-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Use the JVM name instead of the value from `KClass#toString` ([d18e436](https://github.com/ReVanced/revanced-library/commit/d18e436de1df14452ecaa7d827be5e6596ba8a2d))
|
||||||
|
|
||||||
## [1.5.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.5.0...v1.5.1-dev.1) (2024-02-08)
|
## [1.5.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.5.0...v1.5.1-dev.1) (2024-02-08)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -65,13 +65,14 @@ This document describes how to contribute to ReVanced Library.
|
|||||||
## 📖 Resources to help you get started
|
## 📖 Resources to help you get started
|
||||||
|
|
||||||
<!-- * The [documentation](/docs) explains how to use ReVanced Library -->
|
<!-- * The [documentation](/docs) explains how to use ReVanced Library -->
|
||||||
* [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
|
- [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
|
## 🙏 Submitting a feature request
|
||||||
|
|
||||||
Features can be requested by opening an issue using the
|
Features can be requested by opening an issue using the
|
||||||
[Feature request issue template](https://github.com/ReVanced/revanced-cli/issues/new?assignees=&labels=Feature+request&projects=&template=feature-request.yml&title=feat%3A+).
|
[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]
|
> [!NOTE]
|
||||||
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Library.
|
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Library.
|
||||||
@@ -80,7 +81,7 @@ Features can be requested by opening an issue using the
|
|||||||
## 🐞 Submitting a bug report
|
## 🐞 Submitting a bug report
|
||||||
|
|
||||||
If you encounter a bug while using ReVanced Library, open an issue using the
|
If you encounter a bug while using ReVanced Library, 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+).
|
[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
|
## 📝 How to contribute
|
||||||
|
|
||||||
@@ -88,11 +89,11 @@ If you encounter a bug while using ReVanced Library, open an issue using the
|
|||||||
with the maintainers of ReVanced Library. This will help you determine whether your change is acceptable
|
with the maintainers of ReVanced Library. This will help you determine whether your change is acceptable
|
||||||
and whether it is worth your time to implement it
|
and whether it is worth your time to implement it
|
||||||
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
|
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
|
||||||
3. Commit your changes.
|
3. Commit your changes
|
||||||
4. Submit a pull request to the `dev` branch of the repository and reference issues
|
4. Submit a pull request to the `dev` branch of the repository and reference issues
|
||||||
that your pull request closes in the description of your pull request
|
that your pull request closes in the description of your pull request
|
||||||
5. Our team will review your pull request and provide feedback. Once your pull request is approved,
|
5. Our team will review your pull request and provide feedback. Once your pull request is approved,
|
||||||
it will be merged into the `dev` branch and will be included in the next release of ReVanced Library
|
it will be merged into the `dev` branch and will be included in the next release of ReVanced Library
|
||||||
|
|
||||||
❤️ Thank you for considering contributing to ReVanced Library,
|
❤️ Thank you for considering contributing to ReVanced Library,
|
||||||
ReVanced
|
ReVanced
|
||||||
|
|||||||
117
README.md
Normal file
117
README.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<p align="center">
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
width="256px"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
width="256px"
|
||||||
|
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||||
|
>
|
||||||
|
</picture>
|
||||||
|
<br>
|
||||||
|
<a href="https://revanced.app/">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
<img height="24px" src="assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||||
|
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="http://revanced.app/discord">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/r/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/app_revanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.youtube.com/@ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Continuing the legacy of Vanced
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# 📚 ReVanced Library
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Library containing common utilities for ReVanced.
|
||||||
|
|
||||||
|
## ❓ About
|
||||||
|
|
||||||
|
ReVanced Library powers projects such as [ReVanced Manager](https://github.com/ReVanced/revanced-manager),
|
||||||
|
[ReVanced CLI](https://github.com/ReVanced/revanced-cli) with common utilities and functionalities
|
||||||
|
by providing shared code.
|
||||||
|
|
||||||
|
## 💪 Features
|
||||||
|
|
||||||
|
Some of the features the ReVanced Library provides are:
|
||||||
|
|
||||||
|
- 📝 **Signing APKs**: Read and write keystores, and sign APK files
|
||||||
|
- 🧩 **Common utility functions**: Various APIs for ReVanced patches such as JSON serialization,
|
||||||
|
reading and setting patch options, calculating the most common compatible version for a set of patches and more
|
||||||
|
- 💾 **Install and uninstall APKs**: Install and uninstall APK files via ADB or locally,
|
||||||
|
the Android package manager, or by mounting using root permissions
|
||||||
|
- 📦 **Repackage patched files to an APK**: Apply patched files from
|
||||||
|
[ReVanced Patcher](https://github.com/revanced/revanced-patcher) to an APK file, and align & sign the APK file automatically
|
||||||
|
|
||||||
|
## 🚀 How to get started
|
||||||
|
|
||||||
|
To use ReVanced Library in your project, follow these steps:
|
||||||
|
|
||||||
|
1. [Add the repository](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#using-a-published-package)
|
||||||
|
to your project
|
||||||
|
2. Add the dependency to your project:
|
||||||
|
|
||||||
|
```kt
|
||||||
|
dependencies {
|
||||||
|
implementation("app.revanced:revanced-library:{$version}")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Everything else
|
||||||
|
|
||||||
|
### 📙 Contributing
|
||||||
|
|
||||||
|
Thank you for considering contributing to ReVanced Library.
|
||||||
|
You can find the contribution guidelines [here](CONTRIBUTING.md).
|
||||||
|
|
||||||
|
### 🛠️ Building
|
||||||
|
|
||||||
|
To build ReVanced Library,
|
||||||
|
you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
|
||||||
|
|
||||||
|
## 📜 Licence
|
||||||
|
|
||||||
|
ReVanced Library is licensed under the GPLv3 license. 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 Library as long as you track changes/dates in source files.
|
||||||
|
Any modifications to ReVanced Library must also be made available under the GPL,
|
||||||
|
along with build & install instructions.
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
public final class app/revanced/library/ApkSigner {
|
|
||||||
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
|
|
||||||
public final fun newApkSignerBuilder (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;Ljava/lang/String;Ljava/lang/String;)Lcom/android/apksig/ApkSigner$Builder;
|
|
||||||
public final fun newApkSignerBuilder (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/android/apksig/ApkSigner$Builder;
|
|
||||||
public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/List;)V
|
|
||||||
public final fun newKeyStore (Ljava/util/List;)Ljava/security/KeyStore;
|
|
||||||
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
|
||||||
public static synthetic fun newPrivateKeyCertificatePair$default (Lapp/revanced/library/ApkSigner;Ljava/lang/String;Ljava/util/Date;ILjava/lang/Object;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
|
||||||
public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
|
||||||
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
|
|
||||||
public final fun signApk (Lcom/android/apksig/ApkSigner$Builder;Ljava/io/File;Ljava/io/File;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
|
|
||||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
|
|
||||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
|
||||||
public final fun getAlias ()Ljava/lang/String;
|
|
||||||
public final fun getPassword ()Ljava/lang/String;
|
|
||||||
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
|
|
||||||
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
|
|
||||||
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
|
|
||||||
public final fun getPrivateKey ()Ljava/security/PrivateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/ApkUtils {
|
|
||||||
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
|
|
||||||
public final fun copyAligned (Ljava/io/File;Ljava/io/File;Lapp/revanced/patcher/PatcherResult;)V
|
|
||||||
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/ApkUtils$SigningOptions {
|
|
||||||
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
|
||||||
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
|
||||||
public final fun getAlias ()Ljava/lang/String;
|
|
||||||
public final fun getKeyStore ()Ljava/io/File;
|
|
||||||
public final fun getKeyStorePassword ()Ljava/lang/String;
|
|
||||||
public final fun getPassword ()Ljava/lang/String;
|
|
||||||
public final fun getSigner ()Ljava/lang/String;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/Options {
|
|
||||||
public static final field INSTANCE Lapp/revanced/library/Options;
|
|
||||||
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch;
|
|
||||||
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
|
|
||||||
public static synthetic fun serialize$default (Lapp/revanced/library/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String;
|
|
||||||
public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V
|
|
||||||
public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/Options$Patch {
|
|
||||||
public final fun getOptions ()Ljava/util/List;
|
|
||||||
public final fun getPatchName ()Ljava/lang/String;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/Options$Patch$Option {
|
|
||||||
public final fun getKey ()Ljava/lang/String;
|
|
||||||
public final fun getValue ()Ljava/lang/Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/PatchUtils {
|
|
||||||
public static final field INSTANCE Lapp/revanced/library/PatchUtils;
|
|
||||||
public final fun getMostCommonCompatibleVersion (Ljava/util/Set;Ljava/lang/String;)Ljava/lang/String;
|
|
||||||
public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
|
|
||||||
public static synthetic fun getMostCommonCompatibleVersions$default (Lapp/revanced/library/PatchUtils;Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/PatchUtils$Json {
|
|
||||||
public static final field INSTANCE Lapp/revanced/library/PatchUtils$Json;
|
|
||||||
public final fun deserialize (Ljava/io/InputStream;Ljava/lang/Class;)Ljava/util/Set;
|
|
||||||
public final fun serialize (Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;)V
|
|
||||||
public static synthetic fun serialize$default (Lapp/revanced/library/PatchUtils$Json;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;ILjava/lang/Object;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch : app/revanced/library/PatchUtils$Json$JsonPatch {
|
|
||||||
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$Companion;
|
|
||||||
public final fun getCompatiblePackages ()Ljava/util/Set;
|
|
||||||
public final fun getDependencies ()Ljava/util/Set;
|
|
||||||
public final fun getDescription ()Ljava/lang/String;
|
|
||||||
public final fun getName ()Ljava/lang/String;
|
|
||||||
public final fun getOptions ()Ljava/util/Map;
|
|
||||||
public final fun getRequiresIntegrations ()Z
|
|
||||||
public final fun getUse ()Z
|
|
||||||
public final fun setRequiresIntegrations (Z)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$Companion {
|
|
||||||
public final fun fromPatch (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption {
|
|
||||||
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion;
|
|
||||||
public final fun getDefault ()Ljava/lang/Object;
|
|
||||||
public final fun getDescription ()Ljava/lang/String;
|
|
||||||
public final fun getKey ()Ljava/lang/String;
|
|
||||||
public final fun getRequired ()Z
|
|
||||||
public final fun getTitle ()Ljava/lang/String;
|
|
||||||
public final fun getValueType ()Ljava/lang/String;
|
|
||||||
public final fun getValues ()Ljava/util/Map;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion {
|
|
||||||
public final fun fromPatchOption (Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch {
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class app/revanced/library/adb/AdbManager {
|
|
||||||
public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion;
|
|
||||||
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
|
||||||
protected final fun getDevice ()Lse/vidstige/jadb/JadbDevice;
|
|
||||||
protected final fun getLogger ()Ljava/util/logging/Logger;
|
|
||||||
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)V
|
|
||||||
public fun uninstall (Ljava/lang/String;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/adb/AdbManager$Apk {
|
|
||||||
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
|
|
||||||
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
|
||||||
public final fun getFile ()Ljava/io/File;
|
|
||||||
public final fun getPackageName ()Ljava/lang/String;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/adb/AdbManager$Companion {
|
|
||||||
public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/library/adb/AdbManager;
|
|
||||||
public static synthetic fun getAdbManager$default (Lapp/revanced/library/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/library/adb/AdbManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/adb/AdbManager$DeviceNotFoundException : java/lang/Exception {
|
|
||||||
public fun <init> ()V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager {
|
|
||||||
public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils;
|
|
||||||
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)V
|
|
||||||
public fun uninstall (Ljava/lang/String;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils {
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager {
|
|
||||||
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)V
|
|
||||||
public fun uninstall (Ljava/lang/String;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/logging/Logger {
|
|
||||||
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
|
|
||||||
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
|
|
||||||
public final fun removeAllHandlers ()V
|
|
||||||
public final fun setDefault ()V
|
|
||||||
public final fun setFormat (Ljava/lang/String;)V
|
|
||||||
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/zip/ZipFile : java/io/Closeable {
|
|
||||||
public static final field ApkZipFile Lapp/revanced/library/zip/ZipFile$ApkZipFile;
|
|
||||||
public fun <init> (Ljava/io/File;)V
|
|
||||||
public final fun addEntryCompressData (Lapp/revanced/library/zip/structures/ZipEntry;[B)V
|
|
||||||
public fun close ()V
|
|
||||||
public final fun copyEntriesFromFileAligned (Lapp/revanced/library/zip/ZipFile;Lkotlin/jvm/functions/Function1;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/zip/ZipFile$ApkZipFile {
|
|
||||||
public final fun getApkZipEntryAlignment ()Lkotlin/jvm/functions/Function1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/zip/structures/ZipEntry {
|
|
||||||
public static final field Companion Lapp/revanced/library/zip/structures/ZipEntry$Companion;
|
|
||||||
public fun <init> (Ljava/lang/String;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/library/zip/structures/ZipEntry$Companion {
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,78 +1,30 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.9.10"
|
alias(libs.plugins.kotlin.jvm) apply false
|
||||||
alias(libs.plugins.binary.compatibility.validator)
|
alias(libs.plugins.kotlin.multiplatform) apply false
|
||||||
`maven-publish`
|
alias(libs.plugins.kotlin.serialization) apply false
|
||||||
|
alias(libs.plugins.android.library) apply false
|
||||||
|
alias(libs.plugins.binary.compatibility.validator) apply false
|
||||||
|
alias(libs.plugins.ktor) apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "app.revanced"
|
group = "app.revanced"
|
||||||
|
|
||||||
repositories {
|
subprojects {
|
||||||
mavenCentral()
|
// Because access to the project is necessary to authenticate with GitHub,
|
||||||
mavenLocal()
|
// the following block must be placed in the root build.gradle.kts file
|
||||||
maven { url = uri("https://jitpack.io") }
|
// instead of the settings.gradle.kts file inside the dependencyResolutionManagement block.
|
||||||
google()
|
repositories {
|
||||||
}
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
dependencies {
|
google()
|
||||||
implementation(libs.revanced.patcher)
|
maven {
|
||||||
implementation(libs.kotlin.reflect)
|
// A repository must be specified for some reason. "registry" is a dummy.
|
||||||
implementation(libs.jadb) // Updated fork
|
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||||
implementation(libs.apksig)
|
credentials {
|
||||||
implementation(libs.bcpkix.jdk18on)
|
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
|
||||||
implementation(libs.jackson.module.kotlin)
|
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
|
||||||
|
|
||||||
testImplementation(libs.revanced.patcher)
|
|
||||||
testImplementation(libs.kotlin.test)
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
testLogging {
|
|
||||||
events("PASSED", "SKIPPED", "FAILED")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin { jvmToolchain(11) }
|
|
||||||
|
|
||||||
java {
|
|
||||||
withSourcesJar()
|
|
||||||
}
|
|
||||||
|
|
||||||
publishing {
|
|
||||||
publications {
|
|
||||||
create<MavenPublication>("gpr") {
|
|
||||||
from(components["java"])
|
|
||||||
|
|
||||||
version = project.version.toString()
|
|
||||||
|
|
||||||
pom {
|
|
||||||
name = "ReVanced Library"
|
|
||||||
description = "Library containing common utilities for ReVanced"
|
|
||||||
url = "https://revanced.app"
|
|
||||||
|
|
||||||
licenses {
|
|
||||||
license {
|
|
||||||
name = "GNU General Public License v3.0"
|
|
||||||
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
developers {
|
|
||||||
developer {
|
|
||||||
id = "ReVanced"
|
|
||||||
name = "ReVanced"
|
|
||||||
email = "contact@revanced.app"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scm {
|
|
||||||
connection = "scm:git:git://github.com/revanced/revanced-library.git"
|
|
||||||
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
|
|
||||||
url = "https://github.com/revanced/revanced-library"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
org.gradle.parallel = true
|
version = 2.4.0-dev.1
|
||||||
|
#Gradle
|
||||||
|
org.gradle.jvmargs = -Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options="-Xmx2048M"
|
||||||
org.gradle.caching = true
|
org.gradle.caching = true
|
||||||
|
org.gradle.configuration-cache = true
|
||||||
|
org.gradle.parallel = true
|
||||||
|
#Kotlin
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 1.5.1-dev.1
|
#Android
|
||||||
|
android.useAndroidX = true
|
||||||
|
android.nonTransitiveRClass = true
|
||||||
|
|||||||
@@ -1,21 +1,54 @@
|
|||||||
[versions]
|
[versions]
|
||||||
apksig = "8.1.4"
|
jackson-module-kotlin = "2.15.0"
|
||||||
bcpkix-jdk18on = "1.76"
|
|
||||||
jackson-module-kotlin = "2.14.3"
|
|
||||||
jadb = "1.2.1"
|
jadb = "1.2.1"
|
||||||
kotlin-reflect = "1.9.20"
|
kotlin = "1.9.22"
|
||||||
kotlin-test = "1.9.20"
|
ktor-client = "2.3.10"
|
||||||
revanced-patcher = "19.2.0"
|
ktor-server-test-host = "2.3.9"
|
||||||
binary-compatibility-validator = "0.13.2"
|
revanced-patcher = "19.3.1"
|
||||||
|
binary-compatibility-validator = "0.14.0"
|
||||||
|
android = "8.3.2"
|
||||||
|
bcpkix-jdk15on = "1.70"
|
||||||
|
guava = "33.0.0-jre"
|
||||||
|
libsu = "5.2.2"
|
||||||
|
core-ktx = "1.12.0"
|
||||||
|
ktor = "2.3.9"
|
||||||
|
koin = "3.5.3"
|
||||||
|
logback = "1.4.14"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
|
||||||
bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bcpkix-jdk18on" }
|
|
||||||
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" }
|
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" }
|
||||||
jadb = { module = "app.revanced:jadb", version.ref = "jadb" }
|
jadb = { module = "app.revanced:jadb", version.ref = "jadb" }
|
||||||
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
|
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
|
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||||
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
||||||
|
apkzlib = { module = "com.android.tools.build:apkzlib", version.ref = "android" }
|
||||||
|
apksig = { module = "com.android.tools.build:apksig", version.ref = "android" }
|
||||||
|
bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" }
|
||||||
|
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||||
|
libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
|
||||||
|
libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" }
|
||||||
|
libsu-service = { module = "com.github.topjohnwu.libsu:service", version.ref = "libsu" }
|
||||||
|
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
|
||||||
|
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||||
|
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor-client" }
|
||||||
|
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor-client" }
|
||||||
|
ktor-server-conditional-headers = { module = "io.ktor:ktor-server-conditional-headers" }
|
||||||
|
ktor-server-core = { module = "io.ktor:ktor-server-core" }
|
||||||
|
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation" }
|
||||||
|
ktor-server-auth = { module = "io.ktor:ktor-server-auth" }
|
||||||
|
ktor-server-auth-jwt = { module = "io.ktor:ktor-server-auth-jwt" }
|
||||||
|
ktor-server-cors = { module = "io.ktor:ktor-server-cors" }
|
||||||
|
ktor-server-caching-headers = { module = "io.ktor:ktor-server-caching-headers" }
|
||||||
|
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common" }
|
||||||
|
ktor-server-netty = { module = "io.ktor:ktor-server-netty" }
|
||||||
|
ktor-server-websockets = { module = "io.ktor:ktor-server-websockets" }
|
||||||
|
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json" }
|
||||||
|
koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
|
||||||
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
||||||
|
android-library = { id = "com.android.library", version.ref = "android" }
|
||||||
|
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
|
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||||
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||||
distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae
|
distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dist
|
zipStorePath=wrapper/dist
|
||||||
281
gradlew
vendored
281
gradlew
vendored
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright 2015 the original author or authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -17,98 +17,67 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
##
|
||||||
# Gradle start up script for POSIX generated by Gradle.
|
## Gradle start up script for UN*X
|
||||||
#
|
##
|
||||||
# Important for running:
|
|
||||||
#
|
|
||||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
|
||||||
# noncompliant, but you have some other compliant shell such as ksh or
|
|
||||||
# bash, then to run this script, type that shell name before the whole
|
|
||||||
# command line, like:
|
|
||||||
#
|
|
||||||
# ksh Gradle
|
|
||||||
#
|
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
|
||||||
# requires all of these POSIX shell features:
|
|
||||||
# * functions;
|
|
||||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
|
||||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
|
||||||
# * compound commands having a testable exit status, especially «case»;
|
|
||||||
# * various built-in commands including «command», «set», and «ulimit».
|
|
||||||
#
|
|
||||||
# Important for patching:
|
|
||||||
#
|
|
||||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
|
||||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
|
||||||
#
|
|
||||||
# The "traditional" practice of packing multiple parameters into a
|
|
||||||
# space-separated string is a well documented source of bugs and security
|
|
||||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
|
||||||
# options in "$@", and eventually passing that to Java.
|
|
||||||
#
|
|
||||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
|
||||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
|
||||||
# see the in-line comments for details.
|
|
||||||
#
|
|
||||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
|
||||||
# Darwin, MinGW, and NonStop.
|
|
||||||
#
|
|
||||||
# (3) This script is generated from the Groovy template
|
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
|
||||||
# within the Gradle project.
|
|
||||||
#
|
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
|
||||||
#
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
app_path=$0
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
# Need this for daisy-chained symlinks.
|
while [ -h "$PRG" ] ; do
|
||||||
while
|
ls=`ls -ld "$PRG"`
|
||||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
[ -h "$app_path" ]
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
do
|
PRG="$link"
|
||||||
ls=$( ls -ld "$app_path" )
|
else
|
||||||
link=${ls#*' -> '}
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
case $link in #(
|
fi
|
||||||
/*) app_path=$link ;; #(
|
|
||||||
*) app_path=$APP_HOME$link ;;
|
|
||||||
esac
|
|
||||||
done
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
# This is normally unused
|
APP_NAME="Gradle"
|
||||||
# shellcheck disable=SC2034
|
APP_BASE_NAME=`basename "$0"`
|
||||||
APP_BASE_NAME=${0##*/}
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD="maximum"
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
} >&2
|
}
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
} >&2
|
}
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "$( uname )" in #(
|
case "`uname`" in
|
||||||
CYGWIN* ) cygwin=true ;; #(
|
CYGWIN* )
|
||||||
Darwin* ) darwin=true ;; #(
|
cygwin=true
|
||||||
MSYS* | MINGW* ) msys=true ;; #(
|
;;
|
||||||
NONSTOP* ) nonstop=true ;;
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
@@ -118,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
else
|
else
|
||||||
JAVACMD=$JAVA_HOME/bin/java
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
@@ -129,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD="java"
|
||||||
if ! command -v java >/dev/null 2>&1
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
then
|
|
||||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
case $MAX_FD in #(
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
max*)
|
if [ $? -eq 0 ] ; then
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
# shellcheck disable=SC3045
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
fi
|
||||||
warn "Could not query maximum file descriptor limit"
|
ulimit -n $MAX_FD
|
||||||
esac
|
if [ $? -ne 0 ] ; then
|
||||||
case $MAX_FD in #(
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
'' | soft) :;; #(
|
fi
|
||||||
*)
|
else
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
# shellcheck disable=SC3045
|
fi
|
||||||
ulimit -n "$MAX_FD" ||
|
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
|
||||||
esac
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command, stacking in reverse order:
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
# * args from the command line
|
if $darwin; then
|
||||||
# * the main class name
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
# * -classpath
|
fi
|
||||||
# * -D...appname settings
|
|
||||||
# * --module-path (only if needed)
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
for arg do
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
if
|
SEP=""
|
||||||
case $arg in #(
|
for dir in $ROOTDIRSRAW ; do
|
||||||
-*) false ;; # don't mess with options #(
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
SEP="|"
|
||||||
[ -e "$t" ] ;; #(
|
|
||||||
*) false ;;
|
|
||||||
esac
|
|
||||||
then
|
|
||||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
|
||||||
fi
|
|
||||||
# Roll the args list around exactly as many times as the number of
|
|
||||||
# args, so each arg winds up back in the position where it started, but
|
|
||||||
# possibly modified.
|
|
||||||
#
|
|
||||||
# NB: a `for` loop captures its iteration list before it begins, so
|
|
||||||
# changing the positional parameters here affects neither the number of
|
|
||||||
# iterations, nor the values presented in `arg`.
|
|
||||||
shift # remove old arg
|
|
||||||
set -- "$@" "$arg" # push replacement arg
|
|
||||||
done
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
|
||||||
|
|
||||||
set -- \
|
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
|
||||||
-classpath "$CLASSPATH" \
|
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
|
||||||
if ! command -v xargs >/dev/null 2>&1
|
|
||||||
then
|
|
||||||
die "xargs is not available"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
|
||||||
#
|
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
|
||||||
#
|
|
||||||
# In Bash we could simply go:
|
|
||||||
#
|
|
||||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
|
||||||
# set -- "${ARGS[@]}" "$@"
|
|
||||||
#
|
|
||||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
|
||||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
|
||||||
# character that might be a shell metacharacter, then use eval to reverse
|
|
||||||
# that process (while maintaining the separation between arguments), and wrap
|
|
||||||
# the whole thing up as a single "set" statement.
|
|
||||||
#
|
|
||||||
# This will of course break if any of these variables contains a newline or
|
|
||||||
# an unmatched quote.
|
|
||||||
#
|
|
||||||
|
|
||||||
eval "set -- $(
|
|
||||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
|
||||||
xargs -n1 |
|
|
||||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
|
||||||
tr '\n' ' '
|
|
||||||
)" '"$@"'
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
15
gradlew.bat
vendored
15
gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -25,8 +25,7 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
@rem This is normally unused
|
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
exit /b 1
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
143
library-networking/api/library-networking.api
Normal file
143
library-networking/api/library-networking.api
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
public final class app/revanced/library/networking/Server {
|
||||||
|
public final fun start ()Lio/ktor/server/engine/ApplicationEngine;
|
||||||
|
public final fun stop ()V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/Server$DependenciesConfiguration {
|
||||||
|
public fun <init> (Lapp/revanced/library/networking/configuration/repository/StorageRepository;Lapp/revanced/library/networking/configuration/repository/PatchSetRepository;Lapp/revanced/library/networking/configuration/repository/AppRepository;Lapp/revanced/library/networking/configuration/repository/InstallerRepository;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/Server$SecurityConfiguration {
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/Server$SerializersConfiguration {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun <init> (Ljava/util/Map;)V
|
||||||
|
public synthetic fun <init> (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/ServerBuilder {
|
||||||
|
public fun <init> ()V
|
||||||
|
public final fun configureDependencies (Lkotlin/jvm/functions/Function1;)Lapp/revanced/library/networking/ServerBuilder;
|
||||||
|
public final fun configureSecurity (Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/networking/ServerBuilder;
|
||||||
|
public final fun configureSerializers (Lkotlin/jvm/functions/Function1;)Lapp/revanced/library/networking/ServerBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder {
|
||||||
|
public final fun build ()Lapp/revanced/library/networking/Server$DependenciesConfiguration;
|
||||||
|
public final fun configureAppRepository (Lapp/revanced/library/networking/configuration/repository/AppRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
|
||||||
|
public final fun configureInstallerRepository (Lapp/revanced/library/networking/configuration/repository/InstallerRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
|
||||||
|
public final fun configurePatchSetRepository (Lapp/revanced/library/networking/configuration/repository/PatchSetRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
|
||||||
|
public final fun configureStorageRepository (Lapp/revanced/library/networking/configuration/repository/StorageRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/ServerBuilder$SerializersConfigurationBuilder {
|
||||||
|
public final fun build ()Lapp/revanced/library/networking/Server$SerializersConfiguration;
|
||||||
|
public final fun configurePatchOptionSerializers ([Lkotlin/Pair;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/ServerKt {
|
||||||
|
public static final fun main ()V
|
||||||
|
public static synthetic fun main ([Ljava/lang/String;)V
|
||||||
|
public static final fun server (Ljava/lang/String;ILio/ktor/server/engine/ApplicationEngineFactory;Lkotlin/jvm/functions/Function1;)Lapp/revanced/library/networking/Server;
|
||||||
|
public static synthetic fun server$default (Ljava/lang/String;ILio/ktor/server/engine/ApplicationEngineFactory;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/library/networking/Server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/configuration/SerializationKt {
|
||||||
|
public static final fun configureSerialization (Lio/ktor/server/application/Application;Lapp/revanced/library/networking/Server$SerializersConfiguration;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/networking/configuration/repository/AppRepository {
|
||||||
|
public fun <init> ()V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/networking/configuration/repository/InstallerRepository {
|
||||||
|
public fun <init> ()V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/networking/configuration/repository/PatchSetRepository {
|
||||||
|
public fun <init> (Lapp/revanced/library/networking/configuration/repository/StorageRepository;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/networking/configuration/repository/StorageRepository {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/io/File;)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getAaptBinaryPath ()Ljava/io/File;
|
||||||
|
public final fun getKeystoreFilePath ()Ljava/io/File;
|
||||||
|
public final fun getOutputFilePath ()Ljava/io/File;
|
||||||
|
public final fun getTemporaryFilesPath ()Ljava/io/File;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class app/revanced/library/networking/models/App {
|
||||||
|
public static final field Companion Lapp/revanced/library/networking/models/App$Companion;
|
||||||
|
public synthetic fun <init> (ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
public static final synthetic fun write$Self (Lapp/revanced/library/networking/models/App;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/models/App$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/networking/models/App$$serializer;
|
||||||
|
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||||
|
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lapp/revanced/library/networking/models/App;
|
||||||
|
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||||
|
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||||
|
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lapp/revanced/library/networking/models/App;)V
|
||||||
|
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||||
|
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/models/App$Companion {
|
||||||
|
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/models/Patch {
|
||||||
|
public static final field Companion Lapp/revanced/library/networking/models/Patch$Companion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/models/Patch$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/networking/models/Patch$$serializer;
|
||||||
|
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||||
|
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lapp/revanced/library/networking/models/Patch;
|
||||||
|
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||||
|
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||||
|
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lapp/revanced/library/networking/models/Patch;)V
|
||||||
|
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||||
|
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/models/Patch$Companion {
|
||||||
|
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/models/Patch$KeyValuePatchOption {
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;)V
|
||||||
|
public final fun getKey ()Ljava/lang/String;
|
||||||
|
public final fun getValue ()Ljava/lang/Object;
|
||||||
|
public final fun getValueType ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/models/Patch$PatchOption {
|
||||||
|
public static final field Companion Lapp/revanced/library/networking/models/Patch$PatchOption$Companion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/models/Patch$PatchOption$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||||
|
public synthetic fun <init> (Lkotlinx/serialization/KSerializer;)V
|
||||||
|
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||||
|
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lapp/revanced/library/networking/models/Patch$PatchOption;
|
||||||
|
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||||
|
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||||
|
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lapp/revanced/library/networking/models/Patch$PatchOption;)V
|
||||||
|
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||||
|
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/models/Patch$PatchOption$Companion {
|
||||||
|
public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/networking/models/PatchBundle {
|
||||||
|
public final fun getPatchBundleFile ()Ljava/io/File;
|
||||||
|
public final fun getPatchBundleIntegrationsFile ()Ljava/io/File;
|
||||||
|
}
|
||||||
|
|
||||||
97
library-networking/build.gradle.kts
Normal file
97
library-networking/build.gradle.kts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlin.jvm)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
|
alias(libs.plugins.binary.compatibility.validator)
|
||||||
|
alias(libs.plugins.ktor)
|
||||||
|
`maven-publish`
|
||||||
|
signing
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":library"))
|
||||||
|
implementation(libs.revanced.patcher)
|
||||||
|
implementation(libs.ktor.client.core)
|
||||||
|
implementation(libs.ktor.client.cio)
|
||||||
|
implementation(libs.ktor.server.core)
|
||||||
|
implementation(libs.ktor.server.content.negotiation)
|
||||||
|
implementation(libs.ktor.server.auth)
|
||||||
|
implementation(libs.ktor.server.auth.jwt)
|
||||||
|
implementation(libs.ktor.server.cors)
|
||||||
|
implementation(libs.ktor.server.caching.headers)
|
||||||
|
implementation(libs.ktor.server.host.common)
|
||||||
|
implementation(libs.ktor.server.netty)
|
||||||
|
implementation(libs.ktor.server.conditional.headers)
|
||||||
|
implementation(libs.ktor.server.websockets)
|
||||||
|
implementation(libs.ktor.serialization.kotlinx.json)
|
||||||
|
implementation(libs.koin.ktor)
|
||||||
|
implementation(libs.logback.classic)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
processResources {
|
||||||
|
expand("projectVersion" to project.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget.set(JvmTarget.JVM_11)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = "GitHubPackages"
|
||||||
|
url = uri("https://maven.pkg.github.com/revanced/revanced-library")
|
||||||
|
credentials {
|
||||||
|
username = System.getenv("GITHUB_ACTOR")
|
||||||
|
password = System.getenv("GITHUB_TOKEN")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
publications {
|
||||||
|
create<MavenPublication>("revanced-library-networking-publication") {
|
||||||
|
version = project.version.toString()
|
||||||
|
|
||||||
|
pom {
|
||||||
|
name = "ReVanced Networking Library"
|
||||||
|
description = "Library to interface to common utilities for ReVanced over a network."
|
||||||
|
url = "https://revanced.app"
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = "GNU General Public License v3.0"
|
||||||
|
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id = "ReVanced"
|
||||||
|
name = "ReVanced"
|
||||||
|
email = "contact@revanced.app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scm {
|
||||||
|
connection = "scm:git:git://github.com/revanced/revanced-library.git"
|
||||||
|
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
|
||||||
|
url = "https://github.com/revanced/revanced-library"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signing {
|
||||||
|
useGpgCmd()
|
||||||
|
sign(publishing.publications["revanced-library-networking-publication"])
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package app.revanced.library.networking
|
||||||
|
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.util.pipeline.*
|
||||||
|
|
||||||
|
internal val PipelineContext<*, ApplicationCall>.parameters get() = call.parameters
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package app.revanced.library.networking
|
||||||
|
|
||||||
|
import app.revanced.library.installation.installer.AdbInstaller
|
||||||
|
import app.revanced.library.networking.configuration.configureDependencies
|
||||||
|
import app.revanced.library.networking.configuration.configureHTTP
|
||||||
|
import app.revanced.library.networking.configuration.configureSecurity
|
||||||
|
import app.revanced.library.networking.configuration.configureSerialization
|
||||||
|
import app.revanced.library.networking.configuration.repository.AppRepository
|
||||||
|
import app.revanced.library.networking.configuration.repository.InstallerRepository
|
||||||
|
import app.revanced.library.networking.configuration.repository.PatchSetRepository
|
||||||
|
import app.revanced.library.networking.configuration.repository.StorageRepository
|
||||||
|
import app.revanced.library.networking.configuration.routing.configureRouting
|
||||||
|
import app.revanced.library.networking.models.App
|
||||||
|
import app.revanced.library.networking.models.PatchBundle
|
||||||
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
|
import app.revanced.patcher.PatchSet
|
||||||
|
import app.revanced.patcher.patch.options.PatchOption
|
||||||
|
import io.ktor.server.engine.*
|
||||||
|
import io.ktor.server.netty.*
|
||||||
|
import java.io.File
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A server.
|
||||||
|
*
|
||||||
|
* @param host The host.
|
||||||
|
* @param port The port.
|
||||||
|
* @param engineFactory The engine factory.
|
||||||
|
* @param securityConfiguration The security configuration.
|
||||||
|
* @param dependenciesConfiguration The dependencies configuration.
|
||||||
|
* @param serializersConfiguration The serializers configuration.
|
||||||
|
*/
|
||||||
|
class Server internal constructor(
|
||||||
|
host: String,
|
||||||
|
port: Int,
|
||||||
|
engineFactory: ApplicationEngineFactory<*, *>,
|
||||||
|
securityConfiguration: SecurityConfiguration,
|
||||||
|
dependenciesConfiguration: DependenciesConfiguration,
|
||||||
|
serializersConfiguration: SerializersConfiguration,
|
||||||
|
) {
|
||||||
|
private val applicationEngine = embeddedServer(engineFactory, port, host) {
|
||||||
|
configureHTTP(allowedHost = host)
|
||||||
|
configureSecurity(securityConfiguration)
|
||||||
|
configureDependencies(dependenciesConfiguration)
|
||||||
|
configureSerialization(serializersConfiguration)
|
||||||
|
configureRouting()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the server and blocks the current thread.
|
||||||
|
*/
|
||||||
|
fun start() = applicationEngine.start(wait = true)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the server.
|
||||||
|
*/
|
||||||
|
fun stop() = applicationEngine.stop()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The security configuration.
|
||||||
|
*
|
||||||
|
* @property username The username.
|
||||||
|
* @property password The password.
|
||||||
|
*/
|
||||||
|
class SecurityConfiguration(
|
||||||
|
internal val username: String,
|
||||||
|
internal val password: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dependencies configuration.
|
||||||
|
*
|
||||||
|
* @property storageRepository The storage repository.
|
||||||
|
* @property patchSetRepository The patch set repository.
|
||||||
|
* @property appRepository The app repository.
|
||||||
|
* @property installerRepository The installer repository.
|
||||||
|
*/
|
||||||
|
class DependenciesConfiguration(
|
||||||
|
internal val storageRepository: StorageRepository,
|
||||||
|
internal val patchSetRepository: PatchSetRepository,
|
||||||
|
internal val appRepository: AppRepository,
|
||||||
|
internal val installerRepository: InstallerRepository,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The serializers configuration.
|
||||||
|
*
|
||||||
|
* @property patchOptionValueTypes A map of [PatchOption.valueType] to [KType] to add serializers for patch options
|
||||||
|
* additional to the default ones.
|
||||||
|
*/
|
||||||
|
class SerializersConfiguration(
|
||||||
|
internal val patchOptionValueTypes: Map<String, KType> = emptyMap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A server builder.
|
||||||
|
*
|
||||||
|
* @property host The host.
|
||||||
|
* @property port The port.
|
||||||
|
* @property engineFactory The engine factory.
|
||||||
|
* @property securityConfiguration The security configuration.
|
||||||
|
* @property dependenciesConfiguration The dependencies configuration.
|
||||||
|
*/
|
||||||
|
class ServerBuilder internal constructor(
|
||||||
|
private val host: String = "localhost",
|
||||||
|
private val port: Int = 8080,
|
||||||
|
private val engineFactory: ApplicationEngineFactory<*, *> = Netty,
|
||||||
|
) {
|
||||||
|
private lateinit var securityConfiguration: Server.SecurityConfiguration
|
||||||
|
private lateinit var dependenciesConfiguration: Server.DependenciesConfiguration
|
||||||
|
private var serializersConfiguration = Server.SerializersConfiguration()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the security.
|
||||||
|
*
|
||||||
|
* @param basicUsername The basic username.
|
||||||
|
* @param basicPassword The basic password.
|
||||||
|
*
|
||||||
|
* @return The server builder.
|
||||||
|
*/
|
||||||
|
fun configureSecurity(
|
||||||
|
basicUsername: String,
|
||||||
|
basicPassword: String,
|
||||||
|
) = apply {
|
||||||
|
securityConfiguration = Server.SecurityConfiguration(
|
||||||
|
username = basicUsername,
|
||||||
|
password = basicPassword,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the dependencies.
|
||||||
|
*
|
||||||
|
* @param block The block to configure the dependencies.
|
||||||
|
*
|
||||||
|
* @return The server builder.
|
||||||
|
*/
|
||||||
|
fun configureDependencies(block: DependenciesConfigurationBuilder.() -> Unit) = apply {
|
||||||
|
dependenciesConfiguration = DependenciesConfigurationBuilder().apply(block).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the serializers.
|
||||||
|
*
|
||||||
|
* @param block The block to configure the serializers.
|
||||||
|
*
|
||||||
|
* @return The server builder.
|
||||||
|
*/
|
||||||
|
fun configureSerializers(block: SerializersConfigurationBuilder.() -> Unit) = apply {
|
||||||
|
serializersConfiguration = SerializersConfigurationBuilder().apply(block).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DependenciesConfigurationBuilder internal constructor() {
|
||||||
|
private lateinit var storageRepository: StorageRepository
|
||||||
|
private lateinit var patchSetRepository: PatchSetRepository
|
||||||
|
private lateinit var appRepository: AppRepository
|
||||||
|
private lateinit var installerRepository: InstallerRepository
|
||||||
|
|
||||||
|
fun configureStorageRepository(storageRepository: StorageRepository) = apply {
|
||||||
|
this.storageRepository = storageRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
fun configurePatchSetRepository(patchSetRepository: PatchSetRepository) = apply {
|
||||||
|
this.patchSetRepository = patchSetRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
fun configureAppRepository(appRepository: AppRepository) = apply {
|
||||||
|
this.appRepository = appRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
fun configureInstallerRepository(installerRepository: InstallerRepository) = apply {
|
||||||
|
this.installerRepository = installerRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build() = Server.DependenciesConfiguration(
|
||||||
|
storageRepository,
|
||||||
|
patchSetRepository,
|
||||||
|
appRepository,
|
||||||
|
installerRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SerializersConfigurationBuilder internal constructor() {
|
||||||
|
private lateinit var patchOptionValueTypes: Map<String, KType>
|
||||||
|
|
||||||
|
fun configurePatchOptionSerializers(vararg pairs: Pair<String, KType>) {
|
||||||
|
this.patchOptionValueTypes = mapOf(*pairs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build() = Server.SerializersConfiguration(patchOptionValueTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the server.
|
||||||
|
*
|
||||||
|
* @return The server.
|
||||||
|
*/
|
||||||
|
internal fun build() = Server(
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
engineFactory,
|
||||||
|
securityConfiguration,
|
||||||
|
dependenciesConfiguration,
|
||||||
|
serializersConfiguration,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a server.
|
||||||
|
*
|
||||||
|
* @param host The host.
|
||||||
|
* @param port The port.
|
||||||
|
* @param engineFactory The engine factory.
|
||||||
|
* @param block The block to build the server.
|
||||||
|
*
|
||||||
|
* @return The server.
|
||||||
|
*/
|
||||||
|
fun server(
|
||||||
|
host: String = "localhost",
|
||||||
|
port: Int = 8080,
|
||||||
|
engineFactory: ApplicationEngineFactory<*, *> = Netty,
|
||||||
|
block: ServerBuilder.() -> Unit = {},
|
||||||
|
) = ServerBuilder(host, port, engineFactory).apply(block).build()
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
server {
|
||||||
|
configureSecurity("username", "password")
|
||||||
|
|
||||||
|
val storageRepository = object : StorageRepository(
|
||||||
|
temporaryFilesPath = File("temp"),
|
||||||
|
keystoreFilePath = File("keystore.jks"),
|
||||||
|
) {
|
||||||
|
override fun readPatchBundles() = setOf(
|
||||||
|
PatchBundle(
|
||||||
|
"ReVanced Patches",
|
||||||
|
File("D:\\ReVanced\\revanced-patches\\build\\libs\\revanced-patches-4.7.0-dev.2.jar"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun writePatchBundles(patchBundles: Set<PatchBundle>) {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newPatchBundle(patchBundleName: String, withIntegrations: Boolean): PatchBundle {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val patchSetRepository = object : PatchSetRepository(storageRepository) {
|
||||||
|
override fun readPatchSet(patchBundles: Set<PatchBundle>): PatchSet {
|
||||||
|
return PatchBundleLoader.Jar(*patchBundles.map { it.patchBundleFile }.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val appRepository = object : AppRepository() {
|
||||||
|
override fun readInstalledApps() = emptySet<App>()
|
||||||
|
}
|
||||||
|
|
||||||
|
val installerRepository = object : InstallerRepository() {
|
||||||
|
override val installer = AdbInstaller("127.0.0.1:58526")
|
||||||
|
}
|
||||||
|
|
||||||
|
configureDependencies {
|
||||||
|
configureStorageRepository(storageRepository)
|
||||||
|
configurePatchSetRepository(patchSetRepository)
|
||||||
|
configureAppRepository(appRepository)
|
||||||
|
configureInstallerRepository(installerRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
configureSerializers {
|
||||||
|
configurePatchOptionSerializers(
|
||||||
|
"LocalDateTime" to typeOf<PatchOption<LocalDateTime>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package app.revanced.library.networking.configuration
|
||||||
|
|
||||||
|
import app.revanced.library.networking.Server
|
||||||
|
import app.revanced.library.networking.services.HttpClientService
|
||||||
|
import app.revanced.library.networking.services.PatchBundleService
|
||||||
|
import app.revanced.library.networking.services.PatcherService
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import org.koin.ktor.plugin.Koin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the dependencies for the application.
|
||||||
|
*
|
||||||
|
* @param dependenciesConfiguration The dependencies configuration.
|
||||||
|
*/
|
||||||
|
internal fun Application.configureDependencies(
|
||||||
|
dependenciesConfiguration: Server.DependenciesConfiguration,
|
||||||
|
) {
|
||||||
|
val globalModule = module {
|
||||||
|
single { dependenciesConfiguration.storageRepository }
|
||||||
|
single { dependenciesConfiguration.patchSetRepository }
|
||||||
|
single { dependenciesConfiguration.appRepository }
|
||||||
|
single { dependenciesConfiguration.installerRepository }
|
||||||
|
}
|
||||||
|
|
||||||
|
val patchBundleModule = module {
|
||||||
|
single { HttpClientService() }
|
||||||
|
singleOf(::PatchBundleService)
|
||||||
|
}
|
||||||
|
|
||||||
|
val patcherModule = module {
|
||||||
|
singleOf(::PatcherService)
|
||||||
|
}
|
||||||
|
|
||||||
|
install(Koin) {
|
||||||
|
modules(
|
||||||
|
globalModule,
|
||||||
|
patchBundleModule,
|
||||||
|
patcherModule,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package app.revanced.library.networking.configuration
|
||||||
|
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.http.content.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.plugins.cachingheaders.*
|
||||||
|
import io.ktor.server.plugins.conditionalheaders.*
|
||||||
|
import io.ktor.server.plugins.cors.routing.*
|
||||||
|
import io.ktor.server.websocket.*
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures HTTP for the application.
|
||||||
|
*
|
||||||
|
* @param allowedHost The allowed host for the application.
|
||||||
|
*/
|
||||||
|
internal fun Application.configureHTTP(
|
||||||
|
allowedHost: String,
|
||||||
|
) {
|
||||||
|
install(ConditionalHeaders)
|
||||||
|
install(CORS) {
|
||||||
|
allowMethod(HttpMethod.Options)
|
||||||
|
allowMethod(HttpMethod.Put)
|
||||||
|
allowMethod(HttpMethod.Delete)
|
||||||
|
allowMethod(HttpMethod.Patch)
|
||||||
|
allowHeader(HttpHeaders.Authorization)
|
||||||
|
allowHost(allowedHost)
|
||||||
|
}
|
||||||
|
install(WebSockets)
|
||||||
|
install(CachingHeaders) {
|
||||||
|
options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package app.revanced.library.networking.configuration
|
||||||
|
|
||||||
|
import app.revanced.library.networking.Server
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the security for the application.
|
||||||
|
*
|
||||||
|
* @param securityConfiguration The security configuration.
|
||||||
|
*/
|
||||||
|
internal fun Application.configureSecurity(
|
||||||
|
securityConfiguration: Server.SecurityConfiguration,
|
||||||
|
) {
|
||||||
|
install(Authentication) {
|
||||||
|
basic {
|
||||||
|
validate { credentials ->
|
||||||
|
if (credentials.name == securityConfiguration.username &&
|
||||||
|
credentials.password == securityConfiguration.password
|
||||||
|
) {
|
||||||
|
UserIdPrincipal(credentials.name)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package app.revanced.library.networking.configuration
|
||||||
|
|
||||||
|
import app.revanced.library.networking.Server
|
||||||
|
import app.revanced.library.networking.models.Patch
|
||||||
|
import app.revanced.patcher.patch.options.PatchOption
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.*
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.builtins.SetSerializer
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import kotlinx.serialization.encoding.decodeStructure
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
import kotlinx.serialization.modules.SerializersModuleBuilder
|
||||||
|
import kotlinx.serialization.modules.contextual
|
||||||
|
import kotlinx.serialization.serializer
|
||||||
|
import java.io.Serializable
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the serialization for the application.
|
||||||
|
*
|
||||||
|
* @param serializersConfiguration The serializers configuration.
|
||||||
|
*/
|
||||||
|
fun Application.configureSerialization(serializersConfiguration: Server.SerializersConfiguration) {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(
|
||||||
|
Json {
|
||||||
|
serializersModule = SerializersModule {
|
||||||
|
configurePatchOptionSerializers(serializersConfiguration.patchOptionValueTypes)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the patch option serializers.
|
||||||
|
*
|
||||||
|
* @param patchOptionValueTypes A map of [PatchOption.valueType] to [KType] to add serializers for patch options
|
||||||
|
* additional to the default ones.
|
||||||
|
*/
|
||||||
|
private fun SerializersModuleBuilder.configurePatchOptionSerializers(patchOptionValueTypes: Map<String, KType>) {
|
||||||
|
val knownPatchOptionValueTypes = mapOf(
|
||||||
|
"String" to typeOf<Patch.PatchOption<String>>(),
|
||||||
|
"Int" to typeOf<Patch.PatchOption<Int>>(),
|
||||||
|
"Boolean" to typeOf<Patch.PatchOption<Boolean>>(),
|
||||||
|
"Long" to typeOf<Patch.PatchOption<Long>>(),
|
||||||
|
"Float" to typeOf<Patch.PatchOption<Float>>(),
|
||||||
|
"StringArray" to typeOf<Patch.PatchOption<Array<String>>>(),
|
||||||
|
"IntArray" to typeOf<Patch.PatchOption<IntArray>>(),
|
||||||
|
"BooleanArray" to typeOf<Patch.PatchOption<BooleanArray>>(),
|
||||||
|
"LongArray" to typeOf<Patch.PatchOption<LongArray>>(),
|
||||||
|
"FloatArray" to typeOf<Patch.PatchOption<FloatArray>>(),
|
||||||
|
) + patchOptionValueTypes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the [KType] for a patch option value type.
|
||||||
|
*
|
||||||
|
* @param valueType The value type of the patch option.
|
||||||
|
*
|
||||||
|
* @return The [KType] for the patch option value type.
|
||||||
|
*/
|
||||||
|
fun patchOptionTypeOf(valueType: String) = knownPatchOptionValueTypes[valueType]
|
||||||
|
?: error("Unknown patch option value type: $valueType")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializer for [Patch.PatchOption].
|
||||||
|
* Uses the [Patch.PatchOption.valueType] to determine the serializer for the generic type.
|
||||||
|
*/
|
||||||
|
val patchOptionSerializer = object : KSerializer<Patch.PatchOption<*>> {
|
||||||
|
override val descriptor = serializer(typeOf<Patch.PatchOption<Serializable>>()).descriptor
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: Patch.PatchOption<*>) = serializer(
|
||||||
|
patchOptionTypeOf(value.valueType),
|
||||||
|
).serialize(encoder, value)
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder) = serializer(
|
||||||
|
patchOptionTypeOf(
|
||||||
|
decoder.decodeStructure(descriptor) {
|
||||||
|
decodeStringElement(descriptor, descriptor.getElementIndex("valueType"))
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).deserialize(decoder) as Patch.PatchOption<*>
|
||||||
|
}
|
||||||
|
|
||||||
|
contextual(patchOptionSerializer)
|
||||||
|
contextual(SetSerializer(patchOptionSerializer))
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package app.revanced.library.networking.configuration.repository
|
||||||
|
|
||||||
|
import app.revanced.library.networking.models.App
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A repository for apps and installers.
|
||||||
|
*/
|
||||||
|
abstract class AppRepository {
|
||||||
|
/**
|
||||||
|
* The set of [App] installed.
|
||||||
|
*/
|
||||||
|
internal lateinit var installedApps: Set<App>
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
readAndSetInstalledApps()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a set of [App] from a storage.
|
||||||
|
*
|
||||||
|
* @return The set of [App] read.
|
||||||
|
*/
|
||||||
|
internal abstract fun readInstalledApps(): Set<App>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a set of [App] using [readInstalledApps] and set [installedApps] to it.
|
||||||
|
*/
|
||||||
|
internal fun readAndSetInstalledApps() {
|
||||||
|
this.installedApps = readInstalledApps()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.library.networking.configuration.repository
|
||||||
|
|
||||||
|
import app.revanced.library.installation.installer.Installer
|
||||||
|
import app.revanced.library.installation.installer.MountInstaller
|
||||||
|
import app.revanced.library.networking.models.App
|
||||||
|
|
||||||
|
abstract class InstallerRepository {
|
||||||
|
/**
|
||||||
|
* The installer to use for installing and uninstalling [App]s.
|
||||||
|
*/
|
||||||
|
internal abstract val installer: Installer<*, *>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root installer to use for mounting and unmounting [App]s.
|
||||||
|
*/
|
||||||
|
internal open val mountInstaller: MountInstaller? = null
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||||
|
|
||||||
|
package app.revanced.library.networking.configuration.repository
|
||||||
|
|
||||||
|
import app.revanced.library.networking.models.PatchBundle
|
||||||
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
|
import app.revanced.patcher.PatchSet
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A repository for patches from a set of [PatchBundle]s.
|
||||||
|
*
|
||||||
|
* @param storageRepository The [StorageRepository] to read the [PatchBundle]s from.
|
||||||
|
*/
|
||||||
|
abstract class PatchSetRepository(
|
||||||
|
private val storageRepository: StorageRepository,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* The set of [Patch]es loaded from [StorageRepository.patchBundles].
|
||||||
|
*/
|
||||||
|
internal lateinit var patchSet: PatchSet
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
readAndSetPatchSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a [PatchSet] from a set of [patchBundles] using a [PatchBundleLoader].
|
||||||
|
*
|
||||||
|
* @param patchBundles The set of [PatchBundle]s to read the [PatchSet] from.
|
||||||
|
*/
|
||||||
|
internal abstract fun readPatchSet(patchBundles: Set<PatchBundle>): PatchSet
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a [PatchSet] from patch bundles from [storageRepository] using [readPatchSet] and set [patchSet] to it.
|
||||||
|
*/
|
||||||
|
internal fun readAndSetPatchSet() {
|
||||||
|
this.patchSet = readPatchSet(storageRepository.patchBundles.values.toSet())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package app.revanced.library.networking.configuration.repository
|
||||||
|
|
||||||
|
import app.revanced.library.networking.models.PatchBundle
|
||||||
|
import app.revanced.patcher.Patcher
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A repository for storage.
|
||||||
|
*
|
||||||
|
* @param temporaryFilesPath The path to the temporary files for [Patcher].
|
||||||
|
* @param outputFilePath The path to the output file to save patched APKs to.
|
||||||
|
* @param keystoreFilePath The path to the keystore file to sign patched APKs with.
|
||||||
|
* @param aaptBinaryPath The path to the aapt binary to use by [Patcher].
|
||||||
|
*/
|
||||||
|
abstract class StorageRepository(
|
||||||
|
val temporaryFilesPath: File,
|
||||||
|
val outputFilePath: File = File(temporaryFilesPath, "output.apk"),
|
||||||
|
val keystoreFilePath: File,
|
||||||
|
val aaptBinaryPath: File? = null,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* The stored [PatchBundle]s mapped by their name.
|
||||||
|
*/
|
||||||
|
internal lateinit var patchBundles: MutableMap<String, PatchBundle>
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to save the patched, but unsigned APK to.
|
||||||
|
*/
|
||||||
|
internal val unsignedApkFilePath = File(temporaryFilesPath, "unsigned.apk")
|
||||||
|
|
||||||
|
init {
|
||||||
|
readAndSetPatchBundles()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a set of [patchBundles] from a storage.
|
||||||
|
*
|
||||||
|
* @return The set of [PatchBundle] read.
|
||||||
|
*/
|
||||||
|
internal abstract fun readPatchBundles(): Set<PatchBundle>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a set of [patchBundles] to a storage.
|
||||||
|
*
|
||||||
|
* @param patchBundles The set of patch bundles to write.
|
||||||
|
*/
|
||||||
|
internal abstract fun writePatchBundles(patchBundles: Set<PatchBundle>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [PatchBundle] in a storage to write to.
|
||||||
|
*
|
||||||
|
* @param patchBundleName The name of the patch bundle.
|
||||||
|
* @param withIntegrations Whether the patch bundle also has integrations.
|
||||||
|
*
|
||||||
|
* @return The new [PatchBundle] created.
|
||||||
|
*/
|
||||||
|
internal abstract fun newPatchBundle(patchBundleName: String, withIntegrations: Boolean): PatchBundle
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the set of [patchBundles] stored and set it to [patchBundles].
|
||||||
|
*/
|
||||||
|
internal fun readAndSetPatchBundles() {
|
||||||
|
patchBundles = readPatchBundles().associateBy { it.name }.toMutableMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a [patchBundle] to the map of the stored [patchBundles] and write the set to a storage using [writePatchBundles].
|
||||||
|
*
|
||||||
|
* @param patchBundle The patch bundle to add.
|
||||||
|
*/
|
||||||
|
internal fun addPersistentlyPatchBundle(patchBundle: PatchBundle) {
|
||||||
|
patchBundles[patchBundle.name] = patchBundle
|
||||||
|
writePatchBundles(patchBundles.values.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a path bundle from the map of [patchBundles] stored and write the set to a storage using [writePatchBundles].
|
||||||
|
*
|
||||||
|
* @param patchBundleName The name of the patch bundle to remove.
|
||||||
|
*/
|
||||||
|
internal fun removePersistentlyPatchBundle(patchBundleName: String) {
|
||||||
|
patchBundles.remove(patchBundleName)
|
||||||
|
writePatchBundles(patchBundles.values.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the temporary files.
|
||||||
|
*/
|
||||||
|
internal fun deleteTemporaryFiles() {
|
||||||
|
temporaryFilesPath.deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package app.revanced.library.networking.configuration.routing
|
||||||
|
|
||||||
|
import app.revanced.library.networking.configuration.routing.routes.configurePatchBundlesRoute
|
||||||
|
import app.revanced.library.networking.configuration.routing.routes.configurePatcherRoute
|
||||||
|
import app.revanced.library.networking.configuration.routing.routes.configurePingRoute
|
||||||
|
import app.revanced.library.networking.configuration.routing.routes.configureRootRoute
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the routing for the application.
|
||||||
|
*/
|
||||||
|
internal fun Application.configureRouting() {
|
||||||
|
routing {
|
||||||
|
authenticate {
|
||||||
|
configureRootRoute()
|
||||||
|
configurePingRoute()
|
||||||
|
configurePatchBundlesRoute()
|
||||||
|
configurePatcherRoute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package app.revanced.library.networking.configuration.routing.routes
|
||||||
|
|
||||||
|
import app.revanced.library.networking.parameters
|
||||||
|
import app.revanced.library.networking.services.PatchBundleService
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.util.*
|
||||||
|
import org.koin.ktor.ext.get
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to handle all patch bundle related requests such as creating, reading, updating and deleting patch bundles.
|
||||||
|
*/
|
||||||
|
internal fun Route.configurePatchBundlesRoute() {
|
||||||
|
val patchBundleService = get<PatchBundleService>()
|
||||||
|
|
||||||
|
route("/patch-bundles") {
|
||||||
|
get {
|
||||||
|
call.respond(patchBundleService.patchBundleNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/add") {
|
||||||
|
val patchBundleName: String by parameters
|
||||||
|
val patchBundleFilePath = parameters["patchBundleFilePath"]
|
||||||
|
|
||||||
|
if (patchBundleFilePath != null) {
|
||||||
|
val patchBundleIntegrationsFilePath = parameters["patchBundleIntegrationsFilePath"]
|
||||||
|
|
||||||
|
patchBundleService.addPersistentlyLocalPatchBundle(
|
||||||
|
patchBundleName,
|
||||||
|
patchBundleFilePath,
|
||||||
|
patchBundleIntegrationsFilePath,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val patchBundleDownloadLink: String by parameters
|
||||||
|
val patchBundleIntegrationsDownloadLink = parameters["patchBundleIntegrationsDownloadLink"]
|
||||||
|
|
||||||
|
patchBundleService.addPersistentlyDownloadPatchBundle(
|
||||||
|
patchBundleName,
|
||||||
|
patchBundleDownloadLink,
|
||||||
|
patchBundleIntegrationsDownloadLink,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/remove") {
|
||||||
|
val patchBundleName: String by parameters
|
||||||
|
|
||||||
|
patchBundleService.removePersistentlyPatchBundle(patchBundleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/refresh") {
|
||||||
|
patchBundleService.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package app.revanced.library.networking.configuration.routing.routes
|
||||||
|
|
||||||
|
import app.revanced.library.networking.configuration.repository.InstallerRepository
|
||||||
|
import app.revanced.library.networking.models.Patch
|
||||||
|
import app.revanced.library.networking.parameters
|
||||||
|
import app.revanced.library.networking.services.PatcherService
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.util.*
|
||||||
|
import org.koin.ktor.ext.get
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to the patcher to handles all patcher related requests such as patching, signing and installing patched apps.
|
||||||
|
*/
|
||||||
|
internal fun Route.configurePatcherRoute() {
|
||||||
|
route("/patcher") {
|
||||||
|
configureAppsRoute()
|
||||||
|
configurePatchesRoute()
|
||||||
|
configurePatchOptionsRoute()
|
||||||
|
configurePatchRoute()
|
||||||
|
configureSignRoute()
|
||||||
|
configureInstallationRoute()
|
||||||
|
configureCleanRoute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to list all patchable apps that can be patched.
|
||||||
|
*/
|
||||||
|
private fun Route.configureAppsRoute() {
|
||||||
|
val patcherService = get<PatcherService>()
|
||||||
|
|
||||||
|
get("/apps") {
|
||||||
|
val universal = parameters.contains("universal")
|
||||||
|
|
||||||
|
call.respond(patcherService.getInstalledApps(universal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to get all patches for a specific app and version.
|
||||||
|
*/
|
||||||
|
private fun Route.configurePatchesRoute() {
|
||||||
|
val patcherService = get<PatcherService>()
|
||||||
|
|
||||||
|
get("/patches") {
|
||||||
|
val app = parameters["app"]
|
||||||
|
val version = parameters["version"]
|
||||||
|
val universal = "universal" in parameters
|
||||||
|
|
||||||
|
call.respond(patcherService.getPatches(app, version, universal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to get and set patch options.
|
||||||
|
*/
|
||||||
|
private fun Route.configurePatchOptionsRoute() {
|
||||||
|
val patcherService = get<PatcherService>()
|
||||||
|
|
||||||
|
route("/options") {
|
||||||
|
get {
|
||||||
|
val app: String by parameters
|
||||||
|
val patch: String by parameters
|
||||||
|
|
||||||
|
call.respond(patcherService.getPatchOptions(patchName = patch, app))
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
// Abuse serialization capabilities of Patch.PatchOption
|
||||||
|
// because Patch.KeyValuePatchOption isn't serializable.
|
||||||
|
// ONLY the Patch.PatchOption.key and Patch.PatchOption.value properties are used here.
|
||||||
|
val patchOptions: Set<Patch.PatchOption<*>> by call.receive()
|
||||||
|
val patch: String by parameters
|
||||||
|
val app: String by parameters
|
||||||
|
|
||||||
|
patcherService.setPatchOptions(
|
||||||
|
// Use Patch.PatchOption.default for Patch.KeyValuePatchOption.value.
|
||||||
|
patchOptions = patchOptions.map { Patch.KeyValuePatchOption(it) }.toSet(),
|
||||||
|
patchName = patch,
|
||||||
|
app,
|
||||||
|
)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete {
|
||||||
|
val patch: String by parameters
|
||||||
|
val app: String by parameters
|
||||||
|
|
||||||
|
patcherService.resetPatchOptions(patchName = patch, app)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to patch an app with a set of patches.
|
||||||
|
*/
|
||||||
|
private fun Route.configurePatchRoute() {
|
||||||
|
val installerRepository = get<InstallerRepository>()
|
||||||
|
val patcherService = get<PatcherService>()
|
||||||
|
|
||||||
|
post("/patch") {
|
||||||
|
val patchNames = parameters.getAll("patch")?.toSet() ?: emptySet()
|
||||||
|
val multithreading = "multithreading" in parameters
|
||||||
|
|
||||||
|
// TODO: The path to the APK must be local to the server, otherwise it will not work.
|
||||||
|
val apkPath = parameters["app"]?.let {
|
||||||
|
installerRepository.installer.getInstallation(it)?.apkFilePath
|
||||||
|
} ?: parameters["apkPath"]
|
||||||
|
val apkFile = File(apkPath ?: return@post call.respond(HttpStatusCode.BadRequest))
|
||||||
|
|
||||||
|
patcherService.patch(patchNames, multithreading, apkFile)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to sign the patched APK.
|
||||||
|
*/
|
||||||
|
private fun Route.configureSignRoute() {
|
||||||
|
val patcherService = get<PatcherService>()
|
||||||
|
|
||||||
|
post("/sign") {
|
||||||
|
val signer: String by parameters
|
||||||
|
val keyStorePassword = parameters["keyStorePassword"]
|
||||||
|
val keyStoreEntryAlias: String by parameters
|
||||||
|
val keyStoreEntryPassword: String by parameters
|
||||||
|
|
||||||
|
patcherService.sign(signer, keyStorePassword, keyStoreEntryAlias, keyStoreEntryPassword)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to install or uninstall a patched APK.
|
||||||
|
*/
|
||||||
|
private fun Route.configureInstallationRoute() {
|
||||||
|
val patcherService = get<PatcherService>()
|
||||||
|
|
||||||
|
post("/install") {
|
||||||
|
val mount = parameters["mount"]
|
||||||
|
|
||||||
|
patcherService.install(mount)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/uninstall") {
|
||||||
|
val packageName: String by parameters
|
||||||
|
val unmount = "unmount" in parameters
|
||||||
|
|
||||||
|
patcherService.uninstall(packageName, unmount)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to delete temporary files produced by the patcher.
|
||||||
|
*/
|
||||||
|
private fun Route.configureCleanRoute() {
|
||||||
|
val patcherService = get<PatcherService>()
|
||||||
|
|
||||||
|
post("/clean") {
|
||||||
|
patcherService.deleteTemporaryFiles()
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package app.revanced.library.networking.configuration.routing.routes
|
||||||
|
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to check if the server is up.
|
||||||
|
*/
|
||||||
|
internal fun Route.configurePingRoute() {
|
||||||
|
head("/ping") {
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package app.revanced.library.networking.configuration.routing.routes
|
||||||
|
|
||||||
|
import app.revanced.library.logging.Logger
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import io.ktor.server.websocket.*
|
||||||
|
import io.ktor.websocket.*
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
internal fun Route.configureRootRoute() {
|
||||||
|
route("/") {
|
||||||
|
configureAboutRoute()
|
||||||
|
configureLoggingRoute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to get information about the server.
|
||||||
|
*/
|
||||||
|
private fun Route.configureAboutRoute() {
|
||||||
|
val name = this::class.java.getResourceAsStream(
|
||||||
|
"/app/revanced/library/networking/version.properties",
|
||||||
|
)?.use { stream ->
|
||||||
|
Properties().apply {
|
||||||
|
load(stream)
|
||||||
|
}.let {
|
||||||
|
"ReVanced Networking Library v${it.getProperty("version")}"
|
||||||
|
}
|
||||||
|
} ?: "ReVanced Networking Library"
|
||||||
|
|
||||||
|
handle {
|
||||||
|
call.respondText(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Fix clients disconnecting from the server.
|
||||||
|
/**
|
||||||
|
* Route to get logs from the server.
|
||||||
|
*/
|
||||||
|
private fun Route.configureLoggingRoute() {
|
||||||
|
val sessions = Collections.synchronizedSet<DefaultWebSocketSession?>(LinkedHashSet())
|
||||||
|
|
||||||
|
Logger.addHandler({ log: String, level: java.util.logging.Level, loggerName: String? ->
|
||||||
|
runBlocking {
|
||||||
|
sessions.forEach {
|
||||||
|
try {
|
||||||
|
it.send("[$loggerName] $level: $log")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sessions -= it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {}, {})
|
||||||
|
|
||||||
|
webSocket("/logs") {
|
||||||
|
sessions += this
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package app.revanced.library.networking.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
private typealias PackageName = String
|
||||||
|
private typealias PackageVersion = String
|
||||||
|
private typealias PackageVersions = Set<PackageVersion>
|
||||||
|
private typealias CompatiblePackages = Map<PackageName, PackageVersions?>
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
open class App(
|
||||||
|
internal val name: String,
|
||||||
|
internal val version: String,
|
||||||
|
internal val packageName: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Patch internal constructor(
|
||||||
|
internal val name: String,
|
||||||
|
internal val description: String?,
|
||||||
|
internal val use: Boolean,
|
||||||
|
internal val compatiblePackages: CompatiblePackages?,
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
class PatchOption<T> internal constructor(
|
||||||
|
internal val key: String,
|
||||||
|
internal val default: T?,
|
||||||
|
internal val values: Map<String, T?>?,
|
||||||
|
internal val title: String?,
|
||||||
|
internal val description: String?,
|
||||||
|
internal val required: Boolean,
|
||||||
|
internal val valueType: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
class KeyValuePatchOption<T>(
|
||||||
|
val key: String,
|
||||||
|
val value: T?,
|
||||||
|
val valueType: String,
|
||||||
|
) {
|
||||||
|
// Abuse serialization capabilities of Patch.PatchOption which is used in request bodies.
|
||||||
|
// Use Patch.PatchOption.default as Patch.KeyValuePatchOption.value.
|
||||||
|
internal constructor(patchOption: PatchOption<T>) : this(
|
||||||
|
patchOption.key,
|
||||||
|
patchOption.default,
|
||||||
|
patchOption.valueType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PatchBundle internal constructor(
|
||||||
|
val name: String,
|
||||||
|
val patchBundleFile: File,
|
||||||
|
val patchBundleIntegrationsFile: File? = null,
|
||||||
|
)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.library.networking.services
|
||||||
|
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.call.*
|
||||||
|
import io.ktor.client.engine.cio.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.utils.io.*
|
||||||
|
import io.ktor.utils.io.jvm.javaio.*
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for HTTP client.
|
||||||
|
*/
|
||||||
|
internal class HttpClientService {
|
||||||
|
private val client by lazy { HttpClient(CIO) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a file from a URL to a file.
|
||||||
|
*
|
||||||
|
* @param file The file to download to.
|
||||||
|
* @param url The URL to download from.
|
||||||
|
*/
|
||||||
|
internal suspend fun downloadToFile(file: File, url: String) {
|
||||||
|
client.get(url).body<ByteReadChannel>().copyTo(file.outputStream())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package app.revanced.library.networking.services
|
||||||
|
|
||||||
|
import app.revanced.library.networking.configuration.repository.PatchSetRepository
|
||||||
|
import app.revanced.library.networking.configuration.repository.StorageRepository
|
||||||
|
import app.revanced.library.networking.models.PatchBundle
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for patch bundles.
|
||||||
|
*
|
||||||
|
* @property storageRepository The storage repository to get storage paths from.
|
||||||
|
* @property patchSetRepository The patch set repository to get patches from.
|
||||||
|
* @property httpClientService The HTTP client service to download patch bundles with.
|
||||||
|
*/
|
||||||
|
internal class PatchBundleService(
|
||||||
|
private val storageRepository: StorageRepository,
|
||||||
|
private val patchSetRepository: PatchSetRepository,
|
||||||
|
private val httpClientService: HttpClientService,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Get the names of the patch bundles stored.
|
||||||
|
*
|
||||||
|
* @return The set of patch bundle names.
|
||||||
|
*/
|
||||||
|
internal val patchBundleNames: Set<String>
|
||||||
|
get() = storageRepository.patchBundles.keys.toSet()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a local patch bundle to storage persistently.
|
||||||
|
*
|
||||||
|
* @param patchBundleName The name of the patch bundle.
|
||||||
|
* @param patchBundleFilePath The path to the patch bundle file.
|
||||||
|
* @param patchBundleIntegrationsFilePath The path to the patch bundle integrations file.
|
||||||
|
*/
|
||||||
|
internal fun addPersistentlyLocalPatchBundle(
|
||||||
|
patchBundleName: String,
|
||||||
|
patchBundleFilePath: String,
|
||||||
|
patchBundleIntegrationsFilePath: String?,
|
||||||
|
) = storageRepository.addPersistentlyPatchBundle(
|
||||||
|
PatchBundle(
|
||||||
|
name = patchBundleName,
|
||||||
|
patchBundleFile = File(patchBundleFilePath),
|
||||||
|
patchBundleIntegrationsFile = patchBundleIntegrationsFilePath?.let { File(it) },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a patch bundle that needs to be downloaded to storage persistently.
|
||||||
|
*
|
||||||
|
* @param patchBundleName The name of the patch bundle.
|
||||||
|
* @param patchBundleDownloadLink The download link to the patch bundle.
|
||||||
|
* @param patchBundleIntegrationsDownloadLink The download link to the patch bundle integrations.
|
||||||
|
*/
|
||||||
|
internal suspend fun addPersistentlyDownloadPatchBundle(
|
||||||
|
patchBundleName: String,
|
||||||
|
patchBundleDownloadLink: String,
|
||||||
|
patchBundleIntegrationsDownloadLink: String?,
|
||||||
|
) {
|
||||||
|
val withIntegrations = patchBundleIntegrationsDownloadLink != null
|
||||||
|
|
||||||
|
storageRepository.newPatchBundle(patchBundleName, withIntegrations).apply {
|
||||||
|
httpClientService.downloadToFile(patchBundleFile, patchBundleDownloadLink)
|
||||||
|
|
||||||
|
if (withIntegrations) {
|
||||||
|
httpClientService.downloadToFile(patchBundleIntegrationsFile!!, patchBundleIntegrationsDownloadLink!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a patch bundle from storage persistently.
|
||||||
|
*
|
||||||
|
* @param name The name of the patch bundle to remove.
|
||||||
|
*/
|
||||||
|
internal fun removePersistentlyPatchBundle(name: String) =
|
||||||
|
storageRepository.removePersistentlyPatchBundle(name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the patch bundles from storage and read the patch set from them.
|
||||||
|
*/
|
||||||
|
internal fun refresh() {
|
||||||
|
storageRepository.readAndSetPatchBundles()
|
||||||
|
patchSetRepository.readAndSetPatchSet()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
package app.revanced.library.networking.services
|
||||||
|
|
||||||
|
import app.revanced.library.ApkUtils
|
||||||
|
import app.revanced.library.ApkUtils.applyTo
|
||||||
|
import app.revanced.library.installation.installer.Installer
|
||||||
|
import app.revanced.library.networking.configuration.repository.AppRepository
|
||||||
|
import app.revanced.library.networking.configuration.repository.InstallerRepository
|
||||||
|
import app.revanced.library.networking.configuration.repository.PatchSetRepository
|
||||||
|
import app.revanced.library.networking.configuration.repository.StorageRepository
|
||||||
|
import app.revanced.library.networking.models.App
|
||||||
|
import app.revanced.library.networking.models.Patch
|
||||||
|
import app.revanced.patcher.Patcher
|
||||||
|
import app.revanced.patcher.PatcherConfig
|
||||||
|
import java.io.File
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.io.StringWriter
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for patching and installing apps.
|
||||||
|
*
|
||||||
|
* @property storageRepository The storage repository to get storage paths from.
|
||||||
|
* @property patchSetRepository The patch set repository to get patches from.
|
||||||
|
* @property appRepository The app repository to get installed apps from.
|
||||||
|
* @property installerRepository The installer repository to install apps with.
|
||||||
|
*/
|
||||||
|
internal class PatcherService(
|
||||||
|
private val storageRepository: StorageRepository,
|
||||||
|
private val patchSetRepository: PatchSetRepository,
|
||||||
|
private val appRepository: AppRepository,
|
||||||
|
private val installerRepository: InstallerRepository,
|
||||||
|
) {
|
||||||
|
private val logger = Logger.getLogger(PatcherService::class.simpleName)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get installed apps.
|
||||||
|
*
|
||||||
|
* @param universal Whether to show apps that only have universal patches.
|
||||||
|
*
|
||||||
|
* @return The installed apps.
|
||||||
|
*/
|
||||||
|
internal fun getInstalledApps(universal: Boolean = true): Set<App> {
|
||||||
|
// TODO: Show apps, that only have universal patches, only if universal is true.
|
||||||
|
return appRepository.installedApps
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get patches.
|
||||||
|
*
|
||||||
|
* @param app The app to get patches for.
|
||||||
|
* @param version The version of the app to get patches for.
|
||||||
|
* @param universal Whether to show patches that are compatible with all apps.
|
||||||
|
*
|
||||||
|
* @return The patches.
|
||||||
|
*/
|
||||||
|
internal fun getPatches(
|
||||||
|
app: String? = null,
|
||||||
|
version: String? = null,
|
||||||
|
universal: Boolean = true,
|
||||||
|
) = if (app != null) {
|
||||||
|
patchSetRepository.patchSet.filter { patch ->
|
||||||
|
patch.compatiblePackages?.any { pkg ->
|
||||||
|
pkg.name == app && (version == null || pkg.versions?.contains(version) ?: false)
|
||||||
|
} ?: universal
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
patchSetRepository.patchSet.filter { patch ->
|
||||||
|
patch.compatiblePackages != null || universal
|
||||||
|
}
|
||||||
|
}.map { patch ->
|
||||||
|
Patch(
|
||||||
|
patch.name!!,
|
||||||
|
patch.description,
|
||||||
|
patch.use,
|
||||||
|
patch.compatiblePackages?.associate { pkg -> pkg.name to pkg.versions },
|
||||||
|
)
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch an app.
|
||||||
|
* Due to the likely-hood, that patches for the same app have the same name, duplicates are unhandled.
|
||||||
|
*
|
||||||
|
* @param patchNames The names of the patches to apply.
|
||||||
|
* @param multithreading Whether to use multi-threading for dex file writing.
|
||||||
|
* @param apkFile The APK file to patch.
|
||||||
|
*/
|
||||||
|
internal suspend fun patch(
|
||||||
|
patchNames: Set<String>,
|
||||||
|
multithreading: Boolean = false,
|
||||||
|
apkFile: File,
|
||||||
|
) = Patcher(
|
||||||
|
PatcherConfig(
|
||||||
|
apkFile = apkFile,
|
||||||
|
temporaryFilesPath = storageRepository.temporaryFilesPath,
|
||||||
|
aaptBinaryPath = storageRepository.aaptBinaryPath?.absolutePath,
|
||||||
|
frameworkFileDirectory = storageRepository.temporaryFilesPath.absolutePath,
|
||||||
|
multithreadingDexFileWriter = multithreading,
|
||||||
|
),
|
||||||
|
).use { patcher ->
|
||||||
|
val packageName = patcher.context.packageMetadata.packageName
|
||||||
|
|
||||||
|
patcher.apply {
|
||||||
|
acceptPatches(
|
||||||
|
patchSetRepository.patchSet.filter { patch ->
|
||||||
|
patch.name in patchNames && patch.compatiblePackages?.any { it.name == packageName } ?: true
|
||||||
|
}.toSet(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Only accept integrations from patch bundles that contain selected patches.
|
||||||
|
acceptIntegrations(
|
||||||
|
storageRepository.patchBundles.values.mapNotNull {
|
||||||
|
it.patchBundleIntegrationsFile
|
||||||
|
}.toSet(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
patcher.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")
|
||||||
|
}
|
||||||
|
|
||||||
|
patcher.get()
|
||||||
|
}.let { patcherResult ->
|
||||||
|
apkFile.copyTo(storageRepository.unsignedApkFilePath, overwrite = true).apply {
|
||||||
|
patcherResult.applyTo(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign an APK.
|
||||||
|
*
|
||||||
|
* @param signer The signer to use.
|
||||||
|
* @param keyStorePassword The password of the keystore.
|
||||||
|
* @param keyStoreEntryAlias The alias of the keystore entry.
|
||||||
|
* @param keyStoreEntryPassword The password of the keystore entry.
|
||||||
|
*/
|
||||||
|
internal fun sign(
|
||||||
|
signer: String,
|
||||||
|
keyStorePassword: String?,
|
||||||
|
keyStoreEntryAlias: String,
|
||||||
|
keyStoreEntryPassword: String,
|
||||||
|
) = ApkUtils.signApk(
|
||||||
|
storageRepository.unsignedApkFilePath,
|
||||||
|
storageRepository.outputFilePath,
|
||||||
|
signer,
|
||||||
|
ApkUtils.KeyStoreDetails(
|
||||||
|
storageRepository.keystoreFilePath,
|
||||||
|
keyStorePassword,
|
||||||
|
keyStoreEntryAlias,
|
||||||
|
keyStoreEntryPassword,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install an APK.
|
||||||
|
*
|
||||||
|
* @param mount The package name to mount the APK to.
|
||||||
|
*/
|
||||||
|
internal suspend fun install(mount: String?) {
|
||||||
|
if (mount != null) {
|
||||||
|
if (installerRepository.mountInstaller == null) {
|
||||||
|
throw IllegalArgumentException("Mount installer not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
installerRepository.mountInstaller!! to Installer.Apk(
|
||||||
|
storageRepository.unsignedApkFilePath,
|
||||||
|
packageName = mount,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
installerRepository.installer to Installer.Apk(storageRepository.outputFilePath)
|
||||||
|
}.let { (installer, apk) ->
|
||||||
|
installer.install(apk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall an APK.
|
||||||
|
*
|
||||||
|
* @param packageName The package name of the APK to uninstall.
|
||||||
|
* @param unmount Whether to uninstall a mounted APK.
|
||||||
|
*/
|
||||||
|
internal suspend fun uninstall(packageName: String, unmount: Boolean) = if (unmount) {
|
||||||
|
installerRepository.mountInstaller!!
|
||||||
|
} else {
|
||||||
|
installerRepository.installer
|
||||||
|
}.uninstall(packageName)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get patch options from [PatchSetRepository.patchSet].
|
||||||
|
* The [app] parameter is necessary in case there are patches with the same name.
|
||||||
|
* Due to the likely-hood, that patches for the same app have the same name, duplicates are unhandled.
|
||||||
|
*
|
||||||
|
* @param patchName The name of the patch to get options for.
|
||||||
|
* @param app The app to get options for.
|
||||||
|
*
|
||||||
|
* @return The patch options for the patch.
|
||||||
|
*/
|
||||||
|
internal fun getPatchOptions(patchName: String, app: String) = patchSetRepository.patchSet.single { patch ->
|
||||||
|
patch.name == patchName && patch.compatiblePackages?.any { it.name == app } ?: true
|
||||||
|
}.options.map { (key, option) ->
|
||||||
|
Patch.PatchOption(
|
||||||
|
key,
|
||||||
|
option.default,
|
||||||
|
option.values,
|
||||||
|
option.title,
|
||||||
|
option.description,
|
||||||
|
option.required,
|
||||||
|
option.valueType,
|
||||||
|
)
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set patch options.
|
||||||
|
* The [app] parameter is necessary in case there are patches with the same name.
|
||||||
|
* Due to the likely-hood, that patches for the same app have the same name, duplicates are unhandled.
|
||||||
|
*
|
||||||
|
* @param patchOptions The options to set.
|
||||||
|
* @param patchName The name of the patch to set options for.
|
||||||
|
* @param app The app to set options for.
|
||||||
|
*/
|
||||||
|
internal fun setPatchOptions(
|
||||||
|
patchOptions: Set<Patch.KeyValuePatchOption<*>>,
|
||||||
|
patchName: String,
|
||||||
|
app: String,
|
||||||
|
) = patchSetRepository.patchSet.single { patch ->
|
||||||
|
patch.name == patchName && patch.compatiblePackages?.any { it.name == app } ?: true
|
||||||
|
}.options.let { options ->
|
||||||
|
patchOptions.forEach { option ->
|
||||||
|
options[option.key] = option.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset patch options and persist them to the storage.
|
||||||
|
*
|
||||||
|
* @param patchName The name of the patch to reset options for.
|
||||||
|
* @param app The app to reset options for.
|
||||||
|
*/
|
||||||
|
internal fun resetPatchOptions(patchName: String, app: String) {
|
||||||
|
patchSetRepository.patchSet.single { patch ->
|
||||||
|
patch.name == patchName && patch.compatiblePackages?.any { it.name == app } ?: true
|
||||||
|
}.options.forEach { (_, option) -> option.reset() }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun deleteTemporaryFiles() = storageRepository.deleteTemporaryFiles()
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
version=${projectVersion}
|
||||||
337
library/api/android/library.api
Normal file
337
library/api/android/library.api
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
public final class app/revanced/library/ApkSigner {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
|
||||||
|
public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
|
||||||
|
public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
|
||||||
|
public final fun newApkSigner (Ljava/lang/String;Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
|
||||||
|
public final fun newApkSigner (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
|
||||||
|
public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Set;)V
|
||||||
|
public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore;
|
||||||
|
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
|
||||||
|
public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
|
||||||
|
public final fun getAlias ()Ljava/lang/String;
|
||||||
|
public final fun getPassword ()Ljava/lang/String;
|
||||||
|
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
|
||||||
|
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
|
||||||
|
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
|
||||||
|
public final fun getPrivateKey ()Ljava/security/PrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkSigner$Signer {
|
||||||
|
public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V
|
||||||
|
public final fun signApk (Ljava/io/File;)V
|
||||||
|
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkUtils {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
|
||||||
|
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
|
||||||
|
public final fun newPrivateKeyCertificatePair (Lapp/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
public final fun readPrivateKeyCertificatePairFromKeyStore (Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
public final fun sign (Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
|
||||||
|
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
|
||||||
|
public final fun sign (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
|
||||||
|
public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkUtils$KeyStoreDetails {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getAlias ()Ljava/lang/String;
|
||||||
|
public final fun getKeyStore ()Ljava/io/File;
|
||||||
|
public final fun getKeyStorePassword ()Ljava/lang/String;
|
||||||
|
public final fun getPassword ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/util/Date;)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getCommonName ()Ljava/lang/String;
|
||||||
|
public final fun getValidUntil ()Ljava/util/Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkUtils$SigningOptions {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getAlias ()Ljava/lang/String;
|
||||||
|
public final fun getKeyStore ()Ljava/io/File;
|
||||||
|
public final fun getKeyStorePassword ()Ljava/lang/String;
|
||||||
|
public final fun getPassword ()Ljava/lang/String;
|
||||||
|
public final fun getSigner ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/Options {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/Options;
|
||||||
|
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch;
|
||||||
|
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
|
||||||
|
public static synthetic fun serialize$default (Lapp/revanced/library/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String;
|
||||||
|
public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V
|
||||||
|
public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/Options$Patch {
|
||||||
|
public final fun getOptions ()Ljava/util/List;
|
||||||
|
public final fun getPatchName ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/Options$Patch$Option {
|
||||||
|
public final fun getKey ()Ljava/lang/String;
|
||||||
|
public final fun getValue ()Ljava/lang/Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/PatchUtils;
|
||||||
|
public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
|
||||||
|
public static synthetic fun getMostCommonCompatibleVersions$default (Lapp/revanced/library/PatchUtils;Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils$Json {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/PatchUtils$Json;
|
||||||
|
public final fun deserialize (Ljava/io/InputStream;Ljava/lang/Class;)Ljava/util/Set;
|
||||||
|
public final fun serialize (Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;)V
|
||||||
|
public static synthetic fun serialize$default (Lapp/revanced/library/PatchUtils$Json;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;ILjava/lang/Object;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch : app/revanced/library/PatchUtils$Json$JsonPatch {
|
||||||
|
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$Companion;
|
||||||
|
public final fun getCompatiblePackages ()Ljava/util/Set;
|
||||||
|
public final fun getDependencies ()Ljava/util/Set;
|
||||||
|
public final fun getDescription ()Ljava/lang/String;
|
||||||
|
public final fun getName ()Ljava/lang/String;
|
||||||
|
public final fun getOptions ()Ljava/util/Map;
|
||||||
|
public final fun getRequiresIntegrations ()Z
|
||||||
|
public final fun getUse ()Z
|
||||||
|
public final fun setRequiresIntegrations (Z)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$Companion {
|
||||||
|
public final fun fromPatch (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption {
|
||||||
|
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion;
|
||||||
|
public final fun getDefault ()Ljava/lang/Object;
|
||||||
|
public final fun getDescription ()Ljava/lang/String;
|
||||||
|
public final fun getKey ()Ljava/lang/String;
|
||||||
|
public final fun getRequired ()Z
|
||||||
|
public final fun getTitle ()Ljava/lang/String;
|
||||||
|
public final fun getValueType ()Ljava/lang/String;
|
||||||
|
public final fun getValues ()Ljava/util/Map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion {
|
||||||
|
public final fun fromPatchOption (Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/Utils {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/Utils;
|
||||||
|
public final fun isAndroidEnvironment ()Z
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/adb/AdbManager {
|
||||||
|
public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion;
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
protected abstract fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
|
||||||
|
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
|
||||||
|
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$Apk {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getFile ()Ljava/io/File;
|
||||||
|
public final fun getPackageName ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$Companion {
|
||||||
|
public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/library/adb/AdbManager;
|
||||||
|
public static synthetic fun getAdbManager$default (Lapp/revanced/library/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/library/adb/AdbManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$DeviceNotFoundException : java/lang/Exception {
|
||||||
|
public fun <init> ()V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager {
|
||||||
|
public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils;
|
||||||
|
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
|
||||||
|
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
|
||||||
|
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager {
|
||||||
|
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
|
||||||
|
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
|
||||||
|
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner {
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService : android/os/IInterface {
|
||||||
|
public static final field DESCRIPTOR Ljava/lang/String;
|
||||||
|
public abstract fun getFileSystemService ()Landroid/os/IBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Default : app/revanced/library/installation/command/ILocalShellCommandRunnerRootService {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun asBinder ()Landroid/os/IBinder;
|
||||||
|
public fun getFileSystemService ()Landroid/os/IBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Stub : android/os/Binder, app/revanced/library/installation/command/ILocalShellCommandRunnerRootService {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun asBinder ()Landroid/os/IBinder;
|
||||||
|
public static fun asInterface (Landroid/os/IBinder;)Lapp/revanced/library/installation/command/ILocalShellCommandRunnerRootService;
|
||||||
|
public fun onTransact (ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/command/LocalShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner, android/content/ServiceConnection, java/io/Closeable {
|
||||||
|
public fun close ()V
|
||||||
|
public fun onServiceConnected (Landroid/content/ComponentName;Landroid/os/IBinder;)V
|
||||||
|
public fun onServiceDisconnected (Landroid/content/ComponentName;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface class app/revanced/library/installation/command/RunResult {
|
||||||
|
public abstract fun getError ()Ljava/lang/String;
|
||||||
|
public abstract fun getExitCode ()I
|
||||||
|
public abstract fun getOutput ()Ljava/lang/String;
|
||||||
|
public abstract fun waitFor ()V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/command/RunResult$DefaultImpls {
|
||||||
|
public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/installation/command/ShellCommandRunner {
|
||||||
|
protected final fun getLogger ()Ljava/util/logging/Logger;
|
||||||
|
protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun <init> (Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult {
|
||||||
|
public final fun getException ()Ljava/lang/Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/AdbMountInstaller : app/revanced/library/installation/installer/MountInstaller {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun <init> (Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public class app/revanced/library/installation/installer/Installation {
|
||||||
|
public final fun getApkFilePath ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/installation/installer/Installer {
|
||||||
|
public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
protected final fun getLogger ()Ljava/util/logging/Logger;
|
||||||
|
public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/Installer$Apk {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getFile ()Ljava/io/File;
|
||||||
|
public final fun getPackageName ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/LocalInstaller : app/revanced/library/installation/installer/Installer, java/io/Closeable {
|
||||||
|
public static final field Companion Lapp/revanced/library/installation/installer/LocalInstaller$Companion;
|
||||||
|
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
|
||||||
|
public fun close ()V
|
||||||
|
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/LocalInstaller$Companion {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/LocalInstallerResult {
|
||||||
|
public final fun getExtra ()Ljava/lang/String;
|
||||||
|
public final fun getPackageName ()Ljava/lang/String;
|
||||||
|
public final fun getPmStatus ()I
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/LocalInstallerService : android/app/Service {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder;
|
||||||
|
public fun onStartCommand (Landroid/content/Intent;II)I
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/LocalMountInstaller : app/revanced/library/installation/installer/MountInstaller, java/io/Closeable {
|
||||||
|
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
|
||||||
|
public synthetic fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public fun close ()V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/MountInstallation : app/revanced/library/installation/installer/Installation {
|
||||||
|
public final fun getInstalledApkFilePath ()Ljava/lang/String;
|
||||||
|
public final fun getMounted ()Z
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/installation/installer/MountInstaller : app/revanced/library/installation/installer/Installer {
|
||||||
|
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
|
||||||
|
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
|
||||||
|
protected final fun move (Ljava/io/File;Ljava/lang/String;)V
|
||||||
|
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/MountInstallerResult : java/lang/Enum {
|
||||||
|
public static final field FAILURE Lapp/revanced/library/installation/installer/MountInstallerResult;
|
||||||
|
public static final field SUCCESS Lapp/revanced/library/installation/installer/MountInstallerResult;
|
||||||
|
public static fun getEntries ()Lkotlin/enums/EnumEntries;
|
||||||
|
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/MountInstallerResult;
|
||||||
|
public static fun values ()[Lapp/revanced/library/installation/installer/MountInstallerResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/logging/Logger {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
|
||||||
|
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
|
||||||
|
public final fun removeAllHandlers ()V
|
||||||
|
public final fun setDefault ()V
|
||||||
|
public final fun setFormat (Ljava/lang/String;)V
|
||||||
|
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
|
||||||
|
}
|
||||||
|
|
||||||
283
library/api/jvm/library.api
Normal file
283
library/api/jvm/library.api
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
public final class app/revanced/library/ApkSigner {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
|
||||||
|
public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
|
||||||
|
public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
|
||||||
|
public final fun newApkSigner (Ljava/lang/String;Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
|
||||||
|
public final fun newApkSigner (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
|
||||||
|
public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Set;)V
|
||||||
|
public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore;
|
||||||
|
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
|
||||||
|
public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
|
||||||
|
public final fun getAlias ()Ljava/lang/String;
|
||||||
|
public final fun getPassword ()Ljava/lang/String;
|
||||||
|
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
|
||||||
|
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
|
||||||
|
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
|
||||||
|
public final fun getPrivateKey ()Ljava/security/PrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkSigner$Signer {
|
||||||
|
public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V
|
||||||
|
public final fun signApk (Ljava/io/File;)V
|
||||||
|
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkUtils {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
|
||||||
|
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
|
||||||
|
public final fun newPrivateKeyCertificatePair (Lapp/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
public final fun readPrivateKeyCertificatePairFromKeyStore (Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||||
|
public final fun sign (Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
|
||||||
|
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
|
||||||
|
public final fun sign (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
|
||||||
|
public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkUtils$KeyStoreDetails {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getAlias ()Ljava/lang/String;
|
||||||
|
public final fun getKeyStore ()Ljava/io/File;
|
||||||
|
public final fun getKeyStorePassword ()Ljava/lang/String;
|
||||||
|
public final fun getPassword ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/util/Date;)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getCommonName ()Ljava/lang/String;
|
||||||
|
public final fun getValidUntil ()Ljava/util/Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/ApkUtils$SigningOptions {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getAlias ()Ljava/lang/String;
|
||||||
|
public final fun getKeyStore ()Ljava/io/File;
|
||||||
|
public final fun getKeyStorePassword ()Ljava/lang/String;
|
||||||
|
public final fun getPassword ()Ljava/lang/String;
|
||||||
|
public final fun getSigner ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/Options {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/Options;
|
||||||
|
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch;
|
||||||
|
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
|
||||||
|
public static synthetic fun serialize$default (Lapp/revanced/library/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String;
|
||||||
|
public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V
|
||||||
|
public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/Options$Patch {
|
||||||
|
public final fun getOptions ()Ljava/util/List;
|
||||||
|
public final fun getPatchName ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/Options$Patch$Option {
|
||||||
|
public final fun getKey ()Ljava/lang/String;
|
||||||
|
public final fun getValue ()Ljava/lang/Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/PatchUtils;
|
||||||
|
public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
|
||||||
|
public static synthetic fun getMostCommonCompatibleVersions$default (Lapp/revanced/library/PatchUtils;Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils$Json {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/PatchUtils$Json;
|
||||||
|
public final fun deserialize (Ljava/io/InputStream;Ljava/lang/Class;)Ljava/util/Set;
|
||||||
|
public final fun serialize (Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;)V
|
||||||
|
public static synthetic fun serialize$default (Lapp/revanced/library/PatchUtils$Json;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;ILjava/lang/Object;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch : app/revanced/library/PatchUtils$Json$JsonPatch {
|
||||||
|
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$Companion;
|
||||||
|
public final fun getCompatiblePackages ()Ljava/util/Set;
|
||||||
|
public final fun getDependencies ()Ljava/util/Set;
|
||||||
|
public final fun getDescription ()Ljava/lang/String;
|
||||||
|
public final fun getName ()Ljava/lang/String;
|
||||||
|
public final fun getOptions ()Ljava/util/Map;
|
||||||
|
public final fun getRequiresIntegrations ()Z
|
||||||
|
public final fun getUse ()Z
|
||||||
|
public final fun setRequiresIntegrations (Z)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$Companion {
|
||||||
|
public final fun fromPatch (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption {
|
||||||
|
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion;
|
||||||
|
public final fun getDefault ()Ljava/lang/Object;
|
||||||
|
public final fun getDescription ()Ljava/lang/String;
|
||||||
|
public final fun getKey ()Ljava/lang/String;
|
||||||
|
public final fun getRequired ()Z
|
||||||
|
public final fun getTitle ()Ljava/lang/String;
|
||||||
|
public final fun getValueType ()Ljava/lang/String;
|
||||||
|
public final fun getValues ()Ljava/util/Map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion {
|
||||||
|
public final fun fromPatchOption (Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/Utils {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/Utils;
|
||||||
|
public final fun isAndroidEnvironment ()Z
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/adb/AdbManager {
|
||||||
|
public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion;
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
protected abstract fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
|
||||||
|
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
|
||||||
|
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$Apk {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getFile ()Ljava/io/File;
|
||||||
|
public final fun getPackageName ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$Companion {
|
||||||
|
public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/library/adb/AdbManager;
|
||||||
|
public static synthetic fun getAdbManager$default (Lapp/revanced/library/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/library/adb/AdbManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$DeviceNotFoundException : java/lang/Exception {
|
||||||
|
public fun <init> ()V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager {
|
||||||
|
public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils;
|
||||||
|
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
|
||||||
|
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
|
||||||
|
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager {
|
||||||
|
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
|
||||||
|
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
|
||||||
|
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner {
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface class app/revanced/library/installation/command/RunResult {
|
||||||
|
public abstract fun getError ()Ljava/lang/String;
|
||||||
|
public abstract fun getExitCode ()I
|
||||||
|
public abstract fun getOutput ()Ljava/lang/String;
|
||||||
|
public abstract fun waitFor ()V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/command/RunResult$DefaultImpls {
|
||||||
|
public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/installation/command/ShellCommandRunner {
|
||||||
|
protected final fun getLogger ()Ljava/util/logging/Logger;
|
||||||
|
protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun <init> (Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult {
|
||||||
|
public final fun getException ()Ljava/lang/Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/AdbMountInstaller : app/revanced/library/installation/installer/MountInstaller {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun <init> (Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public class app/revanced/library/installation/installer/Installation {
|
||||||
|
public final fun getApkFilePath ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/installation/installer/Installer {
|
||||||
|
public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
protected final fun getLogger ()Ljava/util/logging/Logger;
|
||||||
|
public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/Installer$Apk {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getFile ()Ljava/io/File;
|
||||||
|
public final fun getPackageName ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/MountInstallation : app/revanced/library/installation/installer/Installation {
|
||||||
|
public final fun getInstalledApkFilePath ()Ljava/lang/String;
|
||||||
|
public final fun getMounted ()Z
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/library/installation/installer/MountInstaller : app/revanced/library/installation/installer/Installer {
|
||||||
|
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
|
||||||
|
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
|
||||||
|
protected final fun move (Ljava/io/File;Ljava/lang/String;)V
|
||||||
|
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/installation/installer/MountInstallerResult : java/lang/Enum {
|
||||||
|
public static final field FAILURE Lapp/revanced/library/installation/installer/MountInstallerResult;
|
||||||
|
public static final field SUCCESS Lapp/revanced/library/installation/installer/MountInstallerResult;
|
||||||
|
public static fun getEntries ()Lkotlin/enums/EnumEntries;
|
||||||
|
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/MountInstallerResult;
|
||||||
|
public static fun values ()[Lapp/revanced/library/installation/installer/MountInstallerResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/library/logging/Logger {
|
||||||
|
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
|
||||||
|
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
|
||||||
|
public final fun removeAllHandlers ()V
|
||||||
|
public final fun setDefault ()V
|
||||||
|
public final fun setFormat (Ljava/lang/String;)V
|
||||||
|
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
|
||||||
|
}
|
||||||
|
|
||||||
123
library/build.gradle.kts
Normal file
123
library/build.gradle.kts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlin.multiplatform)
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
alias(libs.plugins.binary.compatibility.validator)
|
||||||
|
`maven-publish`
|
||||||
|
signing
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm {
|
||||||
|
compilations.all {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
androidTarget {
|
||||||
|
compilations.all {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
publishLibraryVariants("release")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
androidMain.dependencies {
|
||||||
|
implementation(libs.libsu.nio)
|
||||||
|
implementation(libs.libsu.service)
|
||||||
|
implementation(libs.core.ktx)
|
||||||
|
}
|
||||||
|
|
||||||
|
commonMain.dependencies {
|
||||||
|
implementation(libs.revanced.patcher)
|
||||||
|
implementation(libs.kotlin.reflect)
|
||||||
|
implementation(libs.jadb) // Fork with Shell v2 support.
|
||||||
|
implementation(libs.bcpkix.jdk15on)
|
||||||
|
implementation(libs.jackson.module.kotlin)
|
||||||
|
implementation(libs.apkzlib)
|
||||||
|
implementation(libs.apksig)
|
||||||
|
implementation(libs.guava)
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTest.dependencies {
|
||||||
|
implementation(libs.revanced.patcher)
|
||||||
|
implementation(libs.kotlin.test.junit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "app.revanced.library"
|
||||||
|
compileSdk = 34
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
aidl = true
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = "GitHubPackages"
|
||||||
|
url = uri("https://maven.pkg.github.com/revanced/revanced-library")
|
||||||
|
credentials {
|
||||||
|
username = System.getenv("GITHUB_ACTOR")
|
||||||
|
password = System.getenv("GITHUB_TOKEN")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
publications {
|
||||||
|
create<MavenPublication>("revanced-library-publication") {
|
||||||
|
version = project.version.toString()
|
||||||
|
|
||||||
|
pom {
|
||||||
|
name = "ReVanced Library"
|
||||||
|
description = "Library containing common utilities for ReVanced"
|
||||||
|
url = "https://revanced.app"
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = "GNU General Public License v3.0"
|
||||||
|
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id = "ReVanced"
|
||||||
|
name = "ReVanced"
|
||||||
|
email = "contact@revanced.app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scm {
|
||||||
|
connection = "scm:git:git://github.com/revanced/revanced-library.git"
|
||||||
|
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
|
||||||
|
url = "https://github.com/revanced/revanced-library"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signing {
|
||||||
|
useGpgCmd()
|
||||||
|
sign(publishing.publications["revanced-library-publication"])
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package app.revanced.library.installation.command;
|
||||||
|
|
||||||
|
interface ILocalShellCommandRunnerRootService {
|
||||||
|
IBinder getFileSystemService();
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package app.revanced.library.installation.command
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.os.IBinder
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.internal.BuilderImpl
|
||||||
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
|
import com.topjohnwu.superuser.nio.FileSystemManager
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [LocalShellCommandRunner] for running commands locally on the device.
|
||||||
|
*
|
||||||
|
* @param context The [Context] to use for binding to the [RootService].
|
||||||
|
* @param onReady A callback to be invoked when [LocalShellCommandRunner] is ready to be used.
|
||||||
|
* @throws IllegalStateException If the main shell was already created
|
||||||
|
*
|
||||||
|
* @see ShellCommandRunner
|
||||||
|
*/
|
||||||
|
class LocalShellCommandRunner internal constructor(
|
||||||
|
private val context: Context,
|
||||||
|
private val onReady: () -> Unit,
|
||||||
|
) : ShellCommandRunner(), ServiceConnection, Closeable {
|
||||||
|
private var fileSystemManager: FileSystemManager? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
logger.info("Binding to RootService")
|
||||||
|
val intent = Intent(context, LocalShellCommandRunnerRootService::class.java)
|
||||||
|
RootService.bind(intent, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun runCommand(command: String) = shell.newJob().add(command).exec().let {
|
||||||
|
object : RunResult {
|
||||||
|
override val exitCode = it.code
|
||||||
|
override val output by lazy { it.out.joinToString("\n") }
|
||||||
|
override val error by lazy { it.err.joinToString("\n") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasRootPermission() = shell.isRoot
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the given [content] to the given [targetFilePath].
|
||||||
|
*
|
||||||
|
* @param content The [InputStream] to write.
|
||||||
|
* @param targetFilePath The path to write to.
|
||||||
|
* @throws NotReadyException If the [LocalShellCommandRunner] is not ready yet.
|
||||||
|
*/
|
||||||
|
override fun write(content: InputStream, targetFilePath: String) {
|
||||||
|
fileSystemManager?.let {
|
||||||
|
it.getFile(targetFilePath).newOutputStream().use { outputStream ->
|
||||||
|
content.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
} ?: throw NotReadyException("FileSystemManager service is not ready yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun move(file: File, targetFilePath: String) {
|
||||||
|
invoke("mv ${file.absolutePath} $targetFilePath")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
|
val ipc = ILocalShellCommandRunnerRootService.Stub.asInterface(service)
|
||||||
|
fileSystemManager = FileSystemManager.getRemote(ipc.fileSystemService)
|
||||||
|
|
||||||
|
logger.info("LocalShellCommandRunner service is ready")
|
||||||
|
|
||||||
|
onReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
|
fileSystemManager = null
|
||||||
|
|
||||||
|
logger.info("LocalShellCommandRunner service is disconnected")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() = RootService.unbind(this)
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private val shell = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class NotReadyException internal constructor(message: String) : Exception(message)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package app.revanced.library.installation.command
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
|
import com.topjohnwu.superuser.nio.FileSystemManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [RootService] for the [LocalShellCommandRunner].
|
||||||
|
*/
|
||||||
|
internal class LocalShellCommandRunnerRootService : RootService() {
|
||||||
|
override fun onBind(intent: Intent) = object : ILocalShellCommandRunnerRootService.Stub() {
|
||||||
|
override fun getFileSystemService() =
|
||||||
|
FileSystemManager.getService()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import app.revanced.library.installation.installer.Installer.Apk
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [LocalInstaller] for installing and uninstalling [Apk] files locally.
|
||||||
|
*
|
||||||
|
* @param context The [Context] to use for installing and uninstalling.
|
||||||
|
* @param onResult The callback to be invoked when the [Apk] is installed or uninstalled.
|
||||||
|
*
|
||||||
|
* @see Installer
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
class LocalInstaller(
|
||||||
|
private val context: Context,
|
||||||
|
onResult: (result: LocalInstallerResult) -> Unit,
|
||||||
|
) : Installer<Unit, Installation>(), Closeable {
|
||||||
|
private val broadcastReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val pmStatus = intent.getIntExtra(LocalInstallerService.EXTRA_STATUS, -999)
|
||||||
|
val extra = intent.getStringExtra(LocalInstallerService.EXTRA_STATUS_MESSAGE)!!
|
||||||
|
val packageName = intent.getStringExtra(LocalInstallerService.EXTRA_PACKAGE_NAME)!!
|
||||||
|
|
||||||
|
onResult.invoke(LocalInstallerResult(pmStatus, extra, packageName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val intentSender
|
||||||
|
get() = PendingIntent.getService(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
Intent(context, LocalInstallerService::class.java),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
|
).intentSender
|
||||||
|
|
||||||
|
init {
|
||||||
|
ContextCompat.registerReceiver(
|
||||||
|
context,
|
||||||
|
broadcastReceiver,
|
||||||
|
IntentFilter().apply {
|
||||||
|
addAction(LocalInstallerService.ACTION)
|
||||||
|
},
|
||||||
|
ContextCompat.RECEIVER_NOT_EXPORTED,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun install(apk: Apk) {
|
||||||
|
logger.info("Installing ${apk.file.name}")
|
||||||
|
|
||||||
|
val packageInstaller = context.packageManager.packageInstaller
|
||||||
|
|
||||||
|
packageInstaller.openSession(packageInstaller.createSession(sessionParams)).use { session ->
|
||||||
|
session.writeApk(apk.file)
|
||||||
|
session.commit(intentSender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
override suspend fun uninstall(packageName: String) {
|
||||||
|
logger.info("Uninstalling $packageName")
|
||||||
|
|
||||||
|
val packageInstaller = context.packageManager.packageInstaller
|
||||||
|
|
||||||
|
packageInstaller.uninstall(packageName, intentSender)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getInstallation(packageName: String) = try {
|
||||||
|
val packageInfo = context.packageManager.getPackageInfo(packageName, 0)
|
||||||
|
|
||||||
|
Installation(packageInfo.applicationInfo.sourceDir)
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() = context.unregisterReceiver(broadcastReceiver)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val sessionParams = PackageInstaller.SessionParams(
|
||||||
|
PackageInstaller.SessionParams.MODE_FULL_INSTALL,
|
||||||
|
).apply {
|
||||||
|
setInstallReason(PackageManager.INSTALL_REASON_USER)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PackageInstaller.Session.writeApk(apk: File) {
|
||||||
|
apk.inputStream().use { inputStream ->
|
||||||
|
openWrite(apk.name, 0, apk.length()).use { outputStream ->
|
||||||
|
inputStream.copyTo(outputStream, 1024 * 1024)
|
||||||
|
fsync(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
import app.revanced.library.installation.installer.Installer.Apk
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of installing or uninstalling an [Apk] locally using [LocalInstaller].
|
||||||
|
*
|
||||||
|
* @param pmStatus The status code returned by the package manager.
|
||||||
|
* @param extra The extra information returned by the package manager.
|
||||||
|
* @param packageName The package name of the installed app.
|
||||||
|
*
|
||||||
|
* @see LocalInstaller
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
class LocalInstallerResult internal constructor(val pmStatus: Int, val extra: String, val packageName: String)
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
|
|
||||||
|
class LocalInstallerService : Service() {
|
||||||
|
override fun onStartCommand(
|
||||||
|
intent: Intent, flags: Int, startId: Int
|
||||||
|
): Int {
|
||||||
|
val extraStatus = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)
|
||||||
|
val extraStatusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||||
|
val extraPackageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
|
||||||
|
|
||||||
|
when (extraStatus) {
|
||||||
|
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||||
|
startActivity(
|
||||||
|
if (Build.VERSION.SDK_INT >= 33) {
|
||||||
|
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
intent.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||||
|
}?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
sendBroadcast(
|
||||||
|
Intent().apply {
|
||||||
|
action = ACTION
|
||||||
|
`package` = packageName
|
||||||
|
|
||||||
|
putExtra(EXTRA_STATUS, extraStatus)
|
||||||
|
putExtra(EXTRA_STATUS_MESSAGE, extraStatusMessage)
|
||||||
|
putExtra(EXTRA_PACKAGE_NAME, extraPackageName)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopSelf()
|
||||||
|
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): IBinder? = null
|
||||||
|
|
||||||
|
internal companion object {
|
||||||
|
internal const val ACTION = "PACKAGE_INSTALLER_ACTION"
|
||||||
|
|
||||||
|
internal const val EXTRA_STATUS = "EXTRA_STATUS"
|
||||||
|
internal const val EXTRA_STATUS_MESSAGE = "EXTRA_STATUS_MESSAGE"
|
||||||
|
internal const val EXTRA_PACKAGE_NAME = "EXTRA_PACKAGE_NAME"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import app.revanced.library.installation.command.LocalShellCommandRunner
|
||||||
|
import app.revanced.library.installation.installer.Installer.Apk
|
||||||
|
import app.revanced.library.installation.installer.MountInstaller.NoRootPermissionException
|
||||||
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [LocalMountInstaller] for installing and uninstalling [Apk] files locally with using root permissions by mounting.
|
||||||
|
*
|
||||||
|
* @param context The [Context] to use for binding to the [RootService].
|
||||||
|
* @param onReady A callback to be invoked when [LocalMountInstaller] is ready to be used.
|
||||||
|
*
|
||||||
|
* @throws NoRootPermissionException If the device does not have root permission.
|
||||||
|
*
|
||||||
|
* @see Installer
|
||||||
|
* @see LocalShellCommandRunner
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
class LocalMountInstaller(
|
||||||
|
context: Context,
|
||||||
|
onReady: LocalMountInstaller.() -> Unit = {},
|
||||||
|
) : MountInstaller(
|
||||||
|
{ installer ->
|
||||||
|
LocalShellCommandRunner(context) {
|
||||||
|
(installer as LocalMountInstaller).onReady()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Closeable {
|
||||||
|
override fun close() = (shellCommandRunner as LocalShellCommandRunner).close()
|
||||||
|
}
|
||||||
369
library/src/commonMain/kotlin/app/revanced/library/ApkSigner.kt
Normal file
369
library/src/commonMain/kotlin/app/revanced/library/ApkSigner.kt
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
package app.revanced.library
|
||||||
|
|
||||||
|
import com.android.apksig.ApkSigner.SignerConfig
|
||||||
|
import com.android.tools.build.apkzlib.sign.SigningExtension
|
||||||
|
import com.android.tools.build.apkzlib.sign.SigningOptions
|
||||||
|
import com.android.tools.build.apkzlib.zip.ZFile
|
||||||
|
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.jcajce.JcaContentSignerBuilder
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.*
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.util.*
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for reading or writing keystore files and entries as well as signing APK files.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||||
|
object ApkSigner {
|
||||||
|
private val logger = Logger.getLogger(ApkSigner::class.java.name)
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
|
||||||
|
Security.addProvider(BouncyCastleProvider())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun newKeyStoreInstance() = KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new keystore with a new keypair.
|
||||||
|
*
|
||||||
|
* @param entries The entries to add to the keystore.
|
||||||
|
*
|
||||||
|
* @return The created keystore.
|
||||||
|
*
|
||||||
|
* @see KeyStoreEntry
|
||||||
|
* @see KeyStore
|
||||||
|
*/
|
||||||
|
fun newKeyStore(entries: Set<KeyStoreEntry>): KeyStore {
|
||||||
|
logger.fine("Creating keystore")
|
||||||
|
|
||||||
|
return newKeyStoreInstance().apply {
|
||||||
|
load(null)
|
||||||
|
|
||||||
|
entries.forEach { entry ->
|
||||||
|
// Add all entries to the keystore.
|
||||||
|
setKeyEntry(
|
||||||
|
entry.alias,
|
||||||
|
entry.privateKeyCertificatePair.privateKey,
|
||||||
|
entry.password.toCharArray(),
|
||||||
|
arrayOf(entry.privateKeyCertificatePair.certificate),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a keystore from the given [keyStoreInputStream].
|
||||||
|
*
|
||||||
|
* @param keyStoreInputStream The stream to read the keystore from.
|
||||||
|
* @param keyStorePassword The password for the keystore.
|
||||||
|
*
|
||||||
|
* @return The keystore.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException If the keystore password is invalid.
|
||||||
|
*
|
||||||
|
* @see KeyStore
|
||||||
|
*/
|
||||||
|
fun readKeyStore(
|
||||||
|
keyStoreInputStream: InputStream,
|
||||||
|
keyStorePassword: String?,
|
||||||
|
): KeyStore {
|
||||||
|
logger.fine("Reading keystore")
|
||||||
|
|
||||||
|
return newKeyStoreInstance().apply {
|
||||||
|
try {
|
||||||
|
load(keyStoreInputStream, keyStorePassword?.toCharArray())
|
||||||
|
} catch (exception: IOException) {
|
||||||
|
if (exception.cause is UnrecoverableKeyException) {
|
||||||
|
throw IllegalArgumentException("Invalid keystore password")
|
||||||
|
} else {
|
||||||
|
throw exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new private key and certificate pair.
|
||||||
|
*
|
||||||
|
* @param commonName The common name of the certificate.
|
||||||
|
* @param validUntil The date until which the certificate is valid.
|
||||||
|
*
|
||||||
|
* @return The newly created private key and certificate pair.
|
||||||
|
*
|
||||||
|
* @see PrivateKeyCertificatePair
|
||||||
|
*/
|
||||||
|
fun newPrivateKeyCertificatePair(
|
||||||
|
commonName: String,
|
||||||
|
validUntil: Date,
|
||||||
|
): PrivateKeyCertificatePair {
|
||||||
|
logger.fine("Creating certificate for $commonName")
|
||||||
|
|
||||||
|
// Generate a new key pair.
|
||||||
|
val keyPair = KeyPairGenerator.getInstance("RSA").apply {
|
||||||
|
initialize(4096)
|
||||||
|
}.generateKeyPair()
|
||||||
|
|
||||||
|
val contentSigner = JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private)
|
||||||
|
|
||||||
|
val name = X500Name("CN=$commonName")
|
||||||
|
val certificateHolder = X509v3CertificateBuilder(
|
||||||
|
name,
|
||||||
|
BigInteger.valueOf(SecureRandom().nextLong()),
|
||||||
|
Date(System.currentTimeMillis()),
|
||||||
|
validUntil,
|
||||||
|
Locale.ENGLISH,
|
||||||
|
name,
|
||||||
|
SubjectPublicKeyInfo.getInstance(keyPair.public.encoded),
|
||||||
|
).build(contentSigner)
|
||||||
|
val certificate = JcaX509CertificateConverter().getCertificate(certificateHolder)
|
||||||
|
|
||||||
|
return PrivateKeyCertificatePair(keyPair.private, certificate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a [PrivateKeyCertificatePair] from a keystore entry.
|
||||||
|
*
|
||||||
|
* @param keyStore The keystore to read the entry from.
|
||||||
|
* @param keyStoreEntryAlias The alias of the key store entry to read.
|
||||||
|
* @param keyStoreEntryPassword The password for recovering the signing key.
|
||||||
|
*
|
||||||
|
* @return The read [PrivateKeyCertificatePair].
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid.
|
||||||
|
*
|
||||||
|
* @see PrivateKeyCertificatePair
|
||||||
|
* @see KeyStore
|
||||||
|
*/
|
||||||
|
fun readPrivateKeyCertificatePair(
|
||||||
|
keyStore: KeyStore,
|
||||||
|
keyStoreEntryAlias: String,
|
||||||
|
keyStoreEntryPassword: String,
|
||||||
|
): PrivateKeyCertificatePair {
|
||||||
|
logger.fine("Reading key and certificate pair from keystore entry $keyStoreEntryAlias")
|
||||||
|
|
||||||
|
if (!keyStore.containsAlias(keyStoreEntryAlias)) {
|
||||||
|
throw IllegalArgumentException("Keystore does not contain entry with alias $keyStoreEntryAlias")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the private key and certificate from the keystore.
|
||||||
|
|
||||||
|
val privateKey =
|
||||||
|
try {
|
||||||
|
keyStore.getKey(keyStoreEntryAlias, keyStoreEntryPassword.toCharArray()) as PrivateKey
|
||||||
|
} catch (exception: UnrecoverableKeyException) {
|
||||||
|
throw IllegalArgumentException("Invalid password for keystore entry $keyStoreEntryAlias")
|
||||||
|
}
|
||||||
|
|
||||||
|
val certificate = keyStore.getCertificate(keyStoreEntryAlias) as X509Certificate
|
||||||
|
|
||||||
|
return PrivateKeyCertificatePair(privateKey, certificate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [Signer].
|
||||||
|
*
|
||||||
|
* @param signer The name of the signer.
|
||||||
|
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
|
||||||
|
*
|
||||||
|
* @return The new [Signer].
|
||||||
|
*
|
||||||
|
* @see PrivateKeyCertificatePair
|
||||||
|
* @see Signer
|
||||||
|
*/
|
||||||
|
fun newApkSigner(
|
||||||
|
signer: String,
|
||||||
|
privateKeyCertificatePair: PrivateKeyCertificatePair,
|
||||||
|
) = Signer(
|
||||||
|
com.android.apksig.ApkSigner.Builder(
|
||||||
|
listOf(
|
||||||
|
SignerConfig.Builder(
|
||||||
|
signer,
|
||||||
|
privateKeyCertificatePair.privateKey,
|
||||||
|
listOf(privateKeyCertificatePair.certificate),
|
||||||
|
).build(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a [PrivateKeyCertificatePair] from a keystore entry.
|
||||||
|
*
|
||||||
|
* @param keyStore The keystore to read the entry from.
|
||||||
|
* @param keyStoreEntryAlias The alias of the key store entry to read.
|
||||||
|
* @param keyStoreEntryPassword The password for recovering the signing key.
|
||||||
|
*
|
||||||
|
* @return The read [PrivateKeyCertificatePair].
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid.
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun readKeyCertificatePair(
|
||||||
|
keyStore: KeyStore,
|
||||||
|
keyStoreEntryAlias: String,
|
||||||
|
keyStoreEntryPassword: String,
|
||||||
|
) = readPrivateKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new keystore with a new keypair and saves it to the given [keyStoreOutputStream].
|
||||||
|
*
|
||||||
|
* @param keyStoreOutputStream The stream to write the keystore to.
|
||||||
|
* @param keyStorePassword The password for the keystore.
|
||||||
|
* @param entries The entries to add to the keystore.
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun newKeyStore(
|
||||||
|
keyStoreOutputStream: OutputStream,
|
||||||
|
keyStorePassword: String?,
|
||||||
|
entries: Set<KeyStoreEntry>,
|
||||||
|
) = newKeyStore(entries).store(
|
||||||
|
keyStoreOutputStream,
|
||||||
|
keyStorePassword?.toCharArray(),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [Signer].
|
||||||
|
*
|
||||||
|
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
|
||||||
|
*
|
||||||
|
* @return The new [Signer].
|
||||||
|
*
|
||||||
|
* @see PrivateKeyCertificatePair
|
||||||
|
* @see Signer
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun newApkSigner(privateKeyCertificatePair: PrivateKeyCertificatePair) =
|
||||||
|
Signer(
|
||||||
|
SigningExtension(
|
||||||
|
SigningOptions.builder()
|
||||||
|
.setMinSdkVersion(21) // TODO: Extracting from the target APK would be ideal.
|
||||||
|
.setV1SigningEnabled(true)
|
||||||
|
.setV2SigningEnabled(true)
|
||||||
|
.setCertificates(privateKeyCertificatePair.certificate)
|
||||||
|
.setKey(privateKeyCertificatePair.privateKey)
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [Signer].
|
||||||
|
*
|
||||||
|
* @param signer The name of the signer.
|
||||||
|
* @param keyStore The keystore to use for signing.
|
||||||
|
* @param keyStoreEntryAlias The alias of the key store entry to use for signing.
|
||||||
|
* @param keyStoreEntryPassword The password for recovering the signing key.
|
||||||
|
*
|
||||||
|
* @return The new [Signer].
|
||||||
|
*
|
||||||
|
* @see KeyStore
|
||||||
|
* @see Signer
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun newApkSigner(
|
||||||
|
signer: String,
|
||||||
|
keyStore: KeyStore,
|
||||||
|
keyStoreEntryAlias: String,
|
||||||
|
keyStoreEntryPassword: String,
|
||||||
|
) = newApkSigner(signer, readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [Signer].
|
||||||
|
*
|
||||||
|
* @param keyStore The keystore to use for signing.
|
||||||
|
* @param keyStoreEntryAlias The alias of the key store entry to use for signing.
|
||||||
|
* @param keyStoreEntryPassword The password for recovering the signing key.
|
||||||
|
*
|
||||||
|
* @return The new [Signer].
|
||||||
|
*
|
||||||
|
* @see KeyStore
|
||||||
|
* @see Signer
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun newApkSigner(
|
||||||
|
keyStore: KeyStore,
|
||||||
|
keyStoreEntryAlias: String,
|
||||||
|
keyStoreEntryPassword: String,
|
||||||
|
) = newApkSigner("ReVanced", readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An entry in a keystore.
|
||||||
|
*
|
||||||
|
* @param alias The alias of the entry.
|
||||||
|
* @param password The password for recovering the signing key.
|
||||||
|
* @param privateKeyCertificatePair The private key and certificate pair.
|
||||||
|
*
|
||||||
|
* @see PrivateKeyCertificatePair
|
||||||
|
*/
|
||||||
|
class KeyStoreEntry(
|
||||||
|
val alias: String,
|
||||||
|
val password: String,
|
||||||
|
val privateKeyCertificatePair: PrivateKeyCertificatePair,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A private key and certificate pair.
|
||||||
|
*
|
||||||
|
* @param privateKey The private key.
|
||||||
|
* @param certificate The certificate.
|
||||||
|
*/
|
||||||
|
class PrivateKeyCertificatePair(
|
||||||
|
val privateKey: PrivateKey,
|
||||||
|
val certificate: X509Certificate,
|
||||||
|
)
|
||||||
|
|
||||||
|
class Signer {
|
||||||
|
private val signerBuilder: com.android.apksig.ApkSigner.Builder?
|
||||||
|
private val signingExtension: SigningExtension?
|
||||||
|
|
||||||
|
internal constructor(signerBuilder: com.android.apksig.ApkSigner.Builder) {
|
||||||
|
this.signerBuilder = signerBuilder
|
||||||
|
signingExtension = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun signApk(inputApkFile: File, outputApkFile: File) {
|
||||||
|
logger.info("Signing APK")
|
||||||
|
|
||||||
|
signerBuilder?.setInputApk(inputApkFile)?.setOutputApk(outputApkFile)?.build()?.sign()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This constructor will be removed in the future.")
|
||||||
|
internal constructor(signingExtension: SigningExtension) {
|
||||||
|
signerBuilder = null
|
||||||
|
this.signingExtension = signingExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign an APK file.
|
||||||
|
*
|
||||||
|
* @param apkFile The APK file to sign.
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun signApk(apkFile: File) = ZFile.openReadWrite(apkFile).use {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
signApk(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign an APK file.
|
||||||
|
*
|
||||||
|
* @param apkZFile The APK [ZFile] to sign.
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun signApk(apkZFile: ZFile) {
|
||||||
|
logger.info("Signing ${apkZFile.file.name}")
|
||||||
|
|
||||||
|
signingExtension?.register(apkZFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
281
library/src/commonMain/kotlin/app/revanced/library/ApkUtils.kt
Normal file
281
library/src/commonMain/kotlin/app/revanced/library/ApkUtils.kt
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
package app.revanced.library
|
||||||
|
|
||||||
|
import app.revanced.library.ApkSigner.newPrivateKeyCertificatePair
|
||||||
|
import app.revanced.patcher.PatcherResult
|
||||||
|
import com.android.tools.build.apkzlib.zip.AlignmentRules
|
||||||
|
import com.android.tools.build.apkzlib.zip.StoredEntry
|
||||||
|
import com.android.tools.build.apkzlib.zip.ZFile
|
||||||
|
import com.android.tools.build.apkzlib.zip.ZFileOptions
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
import java.util.logging.Logger
|
||||||
|
import kotlin.time.Duration.Companion.days
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility functions to work with APK files.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||||
|
object ApkUtils {
|
||||||
|
private val logger = Logger.getLogger(ApkUtils::class.java.name)
|
||||||
|
|
||||||
|
private const val LIBRARY_EXTENSION = ".so"
|
||||||
|
|
||||||
|
// Alignment for native libraries.
|
||||||
|
private const val LIBRARY_ALIGNMENT = 1024 * 4
|
||||||
|
|
||||||
|
// Alignment for all other files.
|
||||||
|
private const val DEFAULT_ALIGNMENT = 4
|
||||||
|
|
||||||
|
// Prefix for resources.
|
||||||
|
private const val RES_PREFIX = "res/"
|
||||||
|
|
||||||
|
private val zFileOptions =
|
||||||
|
ZFileOptions().setAlignmentRule(
|
||||||
|
AlignmentRules.compose(
|
||||||
|
AlignmentRules.constantForSuffix(LIBRARY_EXTENSION, LIBRARY_ALIGNMENT),
|
||||||
|
AlignmentRules.constant(DEFAULT_ALIGNMENT),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the [PatcherResult] to the given [apkFile].
|
||||||
|
*
|
||||||
|
* The order of operation is as follows:
|
||||||
|
* 1. Write patched dex files.
|
||||||
|
* 2. Delete all resources in the target APK
|
||||||
|
* 3. Merge resources.apk compiled by AAPT.
|
||||||
|
* 4. Write raw resources.
|
||||||
|
* 5. Delete resources staged for deletion.
|
||||||
|
* 6. Realign the APK.
|
||||||
|
*
|
||||||
|
* @param apkFile The file to apply the patched files to.
|
||||||
|
*/
|
||||||
|
fun PatcherResult.applyTo(apkFile: File) {
|
||||||
|
ZFile.openReadWrite(apkFile, zFileOptions).use { targetApkZFile ->
|
||||||
|
dexFiles.forEach { dexFile ->
|
||||||
|
targetApkZFile.add(dexFile.name, dexFile.stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
resources?.let { resources ->
|
||||||
|
// Add resources compiled by AAPT.
|
||||||
|
resources.resourcesApk?.let { resourcesApk ->
|
||||||
|
ZFile.openReadOnly(resourcesApk).use { resourcesApkZFile ->
|
||||||
|
// Delete all resources in the target APK before merging the new ones.
|
||||||
|
// This is necessary because the resources.apk renames resources.
|
||||||
|
// So unless, the old resources are deleted, there will be orphaned resources in the APK.
|
||||||
|
// It is not necessary, but for the sake of cleanliness, it is done.
|
||||||
|
targetApkZFile.entries().filter { entry ->
|
||||||
|
entry.centralDirectoryHeader.name.startsWith(RES_PREFIX)
|
||||||
|
}.forEach(StoredEntry::delete)
|
||||||
|
|
||||||
|
targetApkZFile.mergeFrom(resourcesApkZFile) { false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add resources not compiled by AAPT.
|
||||||
|
resources.otherResources?.let { otherResources ->
|
||||||
|
targetApkZFile.addAllRecursively(otherResources) { file ->
|
||||||
|
file.relativeTo(otherResources).invariantSeparatorsPath !in resources.doNotCompress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete resources that were staged for deletion.
|
||||||
|
if (resources.deleteResources.isNotEmpty()) {
|
||||||
|
targetApkZFile.entries().filter { entry ->
|
||||||
|
resources.deleteResources.any { shouldDelete -> shouldDelete(entry.centralDirectoryHeader.name) }
|
||||||
|
}.forEach(StoredEntry::delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Aligning APK")
|
||||||
|
|
||||||
|
targetApkZFile.realign()
|
||||||
|
|
||||||
|
logger.fine("Writing changes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new private key and certificate pair and saves it to the keystore in [keyStoreDetails].
|
||||||
|
*
|
||||||
|
* @param privateKeyCertificatePairDetails The details for the private key and certificate pair.
|
||||||
|
* @param keyStoreDetails The details for the keystore.
|
||||||
|
*
|
||||||
|
* @return The newly created private key and certificate pair.
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun newPrivateKeyCertificatePair(
|
||||||
|
privateKeyCertificatePairDetails: PrivateKeyCertificatePairDetails,
|
||||||
|
keyStoreDetails: KeyStoreDetails,
|
||||||
|
) = newPrivateKeyCertificatePair(
|
||||||
|
privateKeyCertificatePairDetails.commonName,
|
||||||
|
privateKeyCertificatePairDetails.validUntil,
|
||||||
|
).also { privateKeyCertificatePair ->
|
||||||
|
ApkSigner.newKeyStore(
|
||||||
|
setOf(
|
||||||
|
ApkSigner.KeyStoreEntry(
|
||||||
|
keyStoreDetails.alias,
|
||||||
|
keyStoreDetails.password,
|
||||||
|
privateKeyCertificatePair,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).store(
|
||||||
|
keyStoreDetails.keyStore.outputStream(),
|
||||||
|
keyStoreDetails.keyStorePassword?.toCharArray(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the private key and certificate pair from an existing keystore.
|
||||||
|
*
|
||||||
|
* @param keyStoreDetails The details for the keystore.
|
||||||
|
*
|
||||||
|
* @return The private key and certificate pair.
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun readPrivateKeyCertificatePairFromKeyStore(
|
||||||
|
keyStoreDetails: KeyStoreDetails,
|
||||||
|
) = ApkSigner.readPrivateKeyCertificatePair(
|
||||||
|
ApkSigner.readKeyStore(
|
||||||
|
keyStoreDetails.keyStore.inputStream(),
|
||||||
|
keyStoreDetails.keyStorePassword,
|
||||||
|
),
|
||||||
|
keyStoreDetails.alias,
|
||||||
|
keyStoreDetails.password,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
|
||||||
|
* If [KeyStoreDetails.keyStore] does not exist,
|
||||||
|
* a new private key and certificate pair will be created and saved to the keystore.
|
||||||
|
*
|
||||||
|
* @param inputApkFile The apk file to sign.
|
||||||
|
* @param outputApkFile The file to save the signed apk to.
|
||||||
|
* @param signer The name of the signer.
|
||||||
|
* @param keyStoreDetails The details for the keystore.
|
||||||
|
*/
|
||||||
|
fun signApk(
|
||||||
|
inputApkFile: File,
|
||||||
|
outputApkFile: File,
|
||||||
|
signer: String,
|
||||||
|
keyStoreDetails: KeyStoreDetails,
|
||||||
|
) = ApkSigner.newApkSigner(
|
||||||
|
signer,
|
||||||
|
if (keyStoreDetails.keyStore.exists()) {
|
||||||
|
readPrivateKeyCertificatePairFromKeyStore(keyStoreDetails)
|
||||||
|
} else {
|
||||||
|
newPrivateKeyCertificatePair(PrivateKeyCertificatePairDetails(), keyStoreDetails)
|
||||||
|
},
|
||||||
|
).signApk(inputApkFile, outputApkFile)
|
||||||
|
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
private fun readOrNewPrivateKeyCertificatePair(
|
||||||
|
signingOptions: SigningOptions,
|
||||||
|
): ApkSigner.PrivateKeyCertificatePair {
|
||||||
|
val privateKeyCertificatePairDetails = PrivateKeyCertificatePairDetails(
|
||||||
|
signingOptions.alias,
|
||||||
|
PrivateKeyCertificatePairDetails().validUntil,
|
||||||
|
)
|
||||||
|
val keyStoreDetails = KeyStoreDetails(
|
||||||
|
signingOptions.keyStore,
|
||||||
|
signingOptions.keyStorePassword,
|
||||||
|
signingOptions.alias,
|
||||||
|
signingOptions.password,
|
||||||
|
)
|
||||||
|
|
||||||
|
return if (keyStoreDetails.keyStore.exists()) {
|
||||||
|
readPrivateKeyCertificatePairFromKeyStore(keyStoreDetails)
|
||||||
|
} else {
|
||||||
|
newPrivateKeyCertificatePair(privateKeyCertificatePairDetails, keyStoreDetails)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
|
||||||
|
*
|
||||||
|
* @param inputApkFile The apk file to sign.
|
||||||
|
* @param outputApkFile The file to save the signed apk to.
|
||||||
|
* @param signer The name of the signer.
|
||||||
|
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun sign(
|
||||||
|
inputApkFile: File,
|
||||||
|
outputApkFile: File,
|
||||||
|
signer: String,
|
||||||
|
privateKeyCertificatePair: ApkSigner.PrivateKeyCertificatePair,
|
||||||
|
) = ApkSigner.newApkSigner(
|
||||||
|
signer,
|
||||||
|
privateKeyCertificatePair,
|
||||||
|
).signApk(inputApkFile, outputApkFile)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs the apk file with the given options.
|
||||||
|
*
|
||||||
|
* @param signingOptions The options to use for signing.
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun File.sign(signingOptions: SigningOptions) = ApkSigner.newApkSigner(
|
||||||
|
signingOptions.signer,
|
||||||
|
readOrNewPrivateKeyCertificatePair(signingOptions),
|
||||||
|
).signApk(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
|
||||||
|
*
|
||||||
|
* @param inputApkFile The apk file to sign.
|
||||||
|
* @param outputApkFile The file to save the signed apk to.
|
||||||
|
* @param signingOptions The options to use for signing.
|
||||||
|
*/
|
||||||
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun sign(inputApkFile: File, outputApkFile: File, signingOptions: SigningOptions) = sign(
|
||||||
|
inputApkFile,
|
||||||
|
outputApkFile,
|
||||||
|
signingOptions.signer,
|
||||||
|
readOrNewPrivateKeyCertificatePair(signingOptions),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for signing an apk.
|
||||||
|
*
|
||||||
|
* @param keyStore The keystore to use for signing.
|
||||||
|
* @param keyStorePassword The password for the keystore.
|
||||||
|
* @param alias The alias of the key store entry to use for signing.
|
||||||
|
* @param password The password for recovering the signing key.
|
||||||
|
* @param signer The name of the signer.
|
||||||
|
*/
|
||||||
|
@Deprecated("This class will be removed in the future.")
|
||||||
|
class SigningOptions(
|
||||||
|
val keyStore: File,
|
||||||
|
val keyStorePassword: String?,
|
||||||
|
val alias: String = "ReVanced Key",
|
||||||
|
val password: String = "",
|
||||||
|
val signer: String = "ReVanced",
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details for a keystore.
|
||||||
|
*
|
||||||
|
* @param keyStore The file to save the keystore to.
|
||||||
|
* @param keyStorePassword The password for the keystore.
|
||||||
|
* @param alias The alias of the key store entry to use for signing.
|
||||||
|
* @param password The password for recovering the signing key.
|
||||||
|
*/
|
||||||
|
class KeyStoreDetails(
|
||||||
|
val keyStore: File,
|
||||||
|
val keyStorePassword: String? = null,
|
||||||
|
val alias: String = "ReVanced Key",
|
||||||
|
val password: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details for a private key and certificate pair.
|
||||||
|
*
|
||||||
|
* @param commonName The common name for the certificate saved in the keystore.
|
||||||
|
* @param validUntil The date until which the certificate is valid.
|
||||||
|
*/
|
||||||
|
class PrivateKeyCertificatePairDetails(
|
||||||
|
val commonName: String = "ReVanced",
|
||||||
|
val validUntil: Date = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
@file:Suppress("DeprecatedCallableAddReplaceWith")
|
||||||
|
|
||||||
|
package app.revanced.library
|
||||||
|
|
||||||
|
import app.revanced.library.installation.command.AdbShellCommandRunner
|
||||||
|
import se.vidstige.jadb.JadbDevice
|
||||||
|
import se.vidstige.jadb.ShellProcessBuilder
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@Deprecated("Do not use this anymore. Instead use AdbCommandRunner.")
|
||||||
|
internal fun JadbDevice.buildCommand(
|
||||||
|
command: String,
|
||||||
|
su: Boolean = true,
|
||||||
|
): ShellProcessBuilder {
|
||||||
|
if (su) return shellProcessBuilder("su -c \'$command\'")
|
||||||
|
|
||||||
|
val args = command.split(" ") as ArrayList<String>
|
||||||
|
val cmd = args.removeFirst()
|
||||||
|
|
||||||
|
return shellProcessBuilder(cmd, *args.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
@Deprecated("Use AdbShellCommandRunner instead.")
|
||||||
|
internal fun JadbDevice.run(
|
||||||
|
command: String,
|
||||||
|
su: Boolean = true,
|
||||||
|
) = buildCommand(command, su).start()
|
||||||
|
|
||||||
|
@Deprecated("Use AdbShellCommandRunner instead.")
|
||||||
|
internal fun JadbDevice.hasSu() = AdbShellCommandRunner(this).hasRootPermission()
|
||||||
|
|
||||||
|
@Deprecated("Use AdbShellCommandRunner instead.")
|
||||||
|
internal fun JadbDevice.push(
|
||||||
|
file: File,
|
||||||
|
targetFilePath: String,
|
||||||
|
) = AdbShellCommandRunner(this).move(file, targetFilePath)
|
||||||
|
|
||||||
|
@Deprecated("Use AdbShellCommandRunner instead.")
|
||||||
|
internal fun JadbDevice.createFile(
|
||||||
|
targetFile: String,
|
||||||
|
content: String,
|
||||||
|
) = AdbShellCommandRunner(this).write(content.byteInputStream(), targetFile)
|
||||||
@@ -6,6 +6,7 @@ import app.revanced.patcher.patch.options.PatchOption
|
|||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import kotlin.reflect.jvm.jvmName
|
||||||
|
|
||||||
typealias PackageName = String
|
typealias PackageName = String
|
||||||
typealias Version = String
|
typealias Version = String
|
||||||
@@ -19,35 +20,6 @@ typealias PackageNameMap = Map<PackageName, VersionMap>
|
|||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||||
object PatchUtils {
|
object PatchUtils {
|
||||||
/**
|
|
||||||
* Get the version that is most common for [packageName] in the supplied set of [patches].
|
|
||||||
*
|
|
||||||
* @param patches The set of patches to check.
|
|
||||||
* @param packageName The name of the compatible package.
|
|
||||||
* @return The most common version of.
|
|
||||||
*/
|
|
||||||
@Deprecated(
|
|
||||||
"Use getMostCommonCompatibleVersions instead.",
|
|
||||||
ReplaceWith(
|
|
||||||
"getMostCommonCompatibleVersions(patches, setOf(packageName))" +
|
|
||||||
".entries.firstOrNull()?.value?.keys?.firstOrNull()",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
fun getMostCommonCompatibleVersion(
|
|
||||||
patches: PatchSet,
|
|
||||||
packageName: String,
|
|
||||||
) = patches
|
|
||||||
.mapNotNull {
|
|
||||||
// Map all patches to their compatible packages with version constraints.
|
|
||||||
it.compatiblePackages?.firstOrNull { compatiblePackage ->
|
|
||||||
compatiblePackage.name == packageName && compatiblePackage.versions?.isNotEmpty() == true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flatMap { it.versions!! }
|
|
||||||
.groupingBy { it }
|
|
||||||
.eachCount()
|
|
||||||
.maxByOrNull { it.value }?.key
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the count of versions for each compatible package from a supplied set of [patches] ordered by the most common version.
|
* Get the count of versions for each compatible package from a supplied set of [patches] ordered by the most common version.
|
||||||
*
|
*
|
||||||
@@ -159,7 +131,7 @@ object PatchUtils {
|
|||||||
patch.name,
|
patch.name,
|
||||||
patch.description,
|
patch.description,
|
||||||
patch.compatiblePackages,
|
patch.compatiblePackages,
|
||||||
buildSet { patch.dependencies?.forEach { add(it.toString()) } },
|
buildSet { patch.dependencies?.forEach { add(it.jvmName) } },
|
||||||
patch.use,
|
patch.use,
|
||||||
patch.requiresIntegrations,
|
patch.requiresIntegrations,
|
||||||
patch.options.mapValues { FullJsonPatchOption.fromPatchOption(it.value) },
|
patch.options.mapValues { FullJsonPatchOption.fromPatchOption(it.value) },
|
||||||
18
library/src/commonMain/kotlin/app/revanced/library/Utils.kt
Normal file
18
library/src/commonMain/kotlin/app/revanced/library/Utils.kt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package app.revanced.library
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utils for the library.
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
object Utils {
|
||||||
|
/**
|
||||||
|
* True if the environment is Android.
|
||||||
|
*/
|
||||||
|
val isAndroidEnvironment =
|
||||||
|
try {
|
||||||
|
Class.forName("android.app.Application")
|
||||||
|
true
|
||||||
|
} catch (e: ClassNotFoundException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
|
package app.revanced.library.adb
|
||||||
|
|
||||||
|
import app.revanced.library.adb.AdbManager.Apk
|
||||||
|
import app.revanced.library.installation.installer.AdbInstaller
|
||||||
|
import app.revanced.library.installation.installer.AdbMountInstaller
|
||||||
|
import app.revanced.library.installation.installer.Constants.PLACEHOLDER
|
||||||
|
import app.revanced.library.installation.installer.Installer
|
||||||
|
import app.revanced.library.run
|
||||||
|
import se.vidstige.jadb.JadbDevice
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [AdbManager] to install and uninstall [Apk] files.
|
||||||
|
*
|
||||||
|
* @param deviceSerial The serial of the device. If null, the first connected device will be used.
|
||||||
|
*/
|
||||||
|
@Deprecated("Use an implementation of Installer instead.")
|
||||||
|
@Suppress("unused")
|
||||||
|
sealed class AdbManager private constructor(
|
||||||
|
@Suppress("UNUSED_PARAMETER") deviceSerial: String?,
|
||||||
|
) {
|
||||||
|
protected abstract val installer: Installer<*, *>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the [Apk] file.
|
||||||
|
*
|
||||||
|
* @param apk The [Apk] file.
|
||||||
|
*/
|
||||||
|
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||||
|
@Deprecated("Use Installer.install instead.")
|
||||||
|
open fun install(apk: Apk) = suspend {
|
||||||
|
installer.install(Installer.Apk(apk.file, apk.packageName))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls the package.
|
||||||
|
*
|
||||||
|
* @param packageName The package name.
|
||||||
|
*/
|
||||||
|
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||||
|
@Deprecated("Use Installer.uninstall instead.")
|
||||||
|
open fun uninstall(packageName: String) = suspend {
|
||||||
|
installer.uninstall(packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use Installer instead.")
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Gets an [AdbManager] for the supplied device serial.
|
||||||
|
*
|
||||||
|
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
||||||
|
* @param root Whether to use root or not.
|
||||||
|
* @return The [AdbManager].
|
||||||
|
* @throws DeviceNotFoundException If the device can not be found.
|
||||||
|
*/
|
||||||
|
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||||
|
@Deprecated("This is deprecated.")
|
||||||
|
fun getAdbManager(
|
||||||
|
deviceSerial: String? = null,
|
||||||
|
root: Boolean = false,
|
||||||
|
): AdbManager = if (root) RootAdbManager(deviceSerial) else UserAdbManager(deviceSerial)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adb manager for rooted devices.
|
||||||
|
*
|
||||||
|
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
||||||
|
*/
|
||||||
|
@Deprecated("Use AdbMountInstaller instead.", ReplaceWith("AdbMountInstaller(deviceSerial)"))
|
||||||
|
class RootAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) {
|
||||||
|
override val installer = AdbMountInstaller(deviceSerial)
|
||||||
|
|
||||||
|
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||||
|
@Deprecated("Use AdbMountInstaller.install instead.")
|
||||||
|
override fun install(apk: Apk) = suspend {
|
||||||
|
installer.install(Installer.Apk(apk.file, apk.packageName))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||||
|
@Deprecated("Use AdbMountInstaller.uninstall instead.")
|
||||||
|
override fun uninstall(packageName: String) = suspend {
|
||||||
|
installer.uninstall(packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is deprecated.")
|
||||||
|
companion object Utils {
|
||||||
|
private fun JadbDevice.run(
|
||||||
|
command: String,
|
||||||
|
with: String,
|
||||||
|
) = run(command.applyReplacement(with))
|
||||||
|
|
||||||
|
private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adb manager for non-rooted devices.
|
||||||
|
*
|
||||||
|
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
||||||
|
*/
|
||||||
|
@Deprecated("Use AdbInstaller instead.")
|
||||||
|
class UserAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) {
|
||||||
|
override val installer = AdbInstaller(deviceSerial)
|
||||||
|
|
||||||
|
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||||
|
@Deprecated("Use AdbInstaller.install instead.")
|
||||||
|
override fun install(apk: Apk) = suspend {
|
||||||
|
installer.install(Installer.Apk(apk.file, apk.packageName))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||||
|
@Deprecated("Use AdbInstaller.uninstall instead.")
|
||||||
|
override fun uninstall(packageName: String) = suspend {
|
||||||
|
installer.uninstall(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apk file for [AdbManager].
|
||||||
|
*
|
||||||
|
* @param file The [Apk] file.
|
||||||
|
* @param packageName The package name of the [Apk] file.
|
||||||
|
*/
|
||||||
|
@Deprecated("Use Installer.Apk instead.")
|
||||||
|
class Apk(val file: File, val packageName: String? = null)
|
||||||
|
|
||||||
|
@Deprecated("Use AdbCommandRunner.DeviceNotFoundException instead.")
|
||||||
|
class DeviceNotFoundException internal constructor(deviceSerial: String? = null) :
|
||||||
|
Exception(
|
||||||
|
deviceSerial?.let {
|
||||||
|
"The device with the ADB device serial \"$deviceSerial\" can not be found"
|
||||||
|
} ?: "No ADB device found",
|
||||||
|
)
|
||||||
|
|
||||||
|
@Deprecated("Use MountInstaller.FailedToFindInstalledPackageException instead.")
|
||||||
|
class FailedToFindInstalledPackageException internal constructor(packageName: String) :
|
||||||
|
Exception("Failed to find installed package \"$packageName\" because no activity was found")
|
||||||
|
|
||||||
|
@Deprecated("Use MountInstaller.PackageNameRequiredException instead.")
|
||||||
|
class PackageNameRequiredException internal constructor() :
|
||||||
|
Exception("Package name is required")
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package app.revanced.library.installation.command
|
||||||
|
|
||||||
|
import app.revanced.library.installation.installer.Utils
|
||||||
|
import se.vidstige.jadb.JadbDevice
|
||||||
|
import se.vidstige.jadb.RemoteFile
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [AdbShellCommandRunner] for running commands on a device remotely using ADB.
|
||||||
|
*
|
||||||
|
* @see ShellCommandRunner
|
||||||
|
*/
|
||||||
|
class AdbShellCommandRunner : ShellCommandRunner {
|
||||||
|
private val device: JadbDevice
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a [AdbShellCommandRunner] for the given device.
|
||||||
|
*
|
||||||
|
* @param device The device.
|
||||||
|
*/
|
||||||
|
internal constructor(device: JadbDevice) {
|
||||||
|
this.device = device
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a [AdbShellCommandRunner] for the device with the given serial.
|
||||||
|
*
|
||||||
|
* @param deviceSerial deviceSerial The device serial. If null, the first connected device will be used.
|
||||||
|
*/
|
||||||
|
internal constructor(deviceSerial: String?) {
|
||||||
|
device = Utils.getDevice(deviceSerial, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun runCommand(command: String) = device.shellProcessBuilder(command).start().let { process ->
|
||||||
|
object : RunResult {
|
||||||
|
override val exitCode by lazy { process.waitFor() }
|
||||||
|
override val output by lazy { process.inputStream.bufferedReader().readText() }
|
||||||
|
override val error by lazy { process.errorStream.bufferedReader().readText() }
|
||||||
|
|
||||||
|
override fun waitFor() {
|
||||||
|
process.waitFor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasRootPermission(): Boolean = invoke("whoami").exitCode == 0
|
||||||
|
|
||||||
|
override fun write(content: InputStream, targetFilePath: String) =
|
||||||
|
device.push(content, System.currentTimeMillis(), 644, RemoteFile(targetFilePath))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the given [file] from the local to the target file path on the device.
|
||||||
|
*
|
||||||
|
* @param file The file to move.
|
||||||
|
* @param targetFilePath The target file path.
|
||||||
|
*/
|
||||||
|
override fun move(file: File, targetFilePath: String) = device.push(file, RemoteFile(targetFilePath))
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.library.installation.command
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a command execution.
|
||||||
|
*/
|
||||||
|
interface RunResult {
|
||||||
|
/**
|
||||||
|
* The exit code of the command.
|
||||||
|
*/
|
||||||
|
val exitCode: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The output of the command.
|
||||||
|
*/
|
||||||
|
val output: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The error of the command.
|
||||||
|
*/
|
||||||
|
val error: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the command to finish.
|
||||||
|
*/
|
||||||
|
fun waitFor() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package app.revanced.library.installation.command
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [ShellCommandRunner] for running commands on a device.
|
||||||
|
*/
|
||||||
|
abstract class ShellCommandRunner internal constructor() {
|
||||||
|
protected val logger: Logger = Logger.getLogger(this::class.java.name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the given [content] to the file at the given [targetFilePath] path.
|
||||||
|
*
|
||||||
|
* @param content The content of the file.
|
||||||
|
* @param targetFilePath The target file path.
|
||||||
|
*/
|
||||||
|
internal abstract fun write(
|
||||||
|
content: InputStream,
|
||||||
|
targetFilePath: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the given [file] to the given [targetFilePath] path.
|
||||||
|
*
|
||||||
|
* @param file The file to move.
|
||||||
|
* @param targetFilePath The target file path.
|
||||||
|
*/
|
||||||
|
internal abstract fun move(
|
||||||
|
file: File,
|
||||||
|
targetFilePath: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the given [command] on the device as root.
|
||||||
|
*
|
||||||
|
* @param command The command to run.
|
||||||
|
* @return The [RunResult].
|
||||||
|
*/
|
||||||
|
protected abstract fun runCommand(command: String): RunResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the device has root permission.
|
||||||
|
*
|
||||||
|
* @return True if the device has root permission, false otherwise.
|
||||||
|
*/
|
||||||
|
internal abstract fun hasRootPermission(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a command on the device as root.
|
||||||
|
*
|
||||||
|
* @param command The command to run.
|
||||||
|
* @return The [RunResult].
|
||||||
|
*/
|
||||||
|
internal operator fun invoke(
|
||||||
|
command: String,
|
||||||
|
) = runCommand("su -c \'$command\'")
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
import app.revanced.library.installation.command.AdbShellCommandRunner
|
||||||
|
import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH
|
||||||
|
import app.revanced.library.installation.installer.Installer.Apk
|
||||||
|
import se.vidstige.jadb.JadbException
|
||||||
|
import se.vidstige.jadb.managers.Package
|
||||||
|
import se.vidstige.jadb.managers.PackageManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [AdbInstaller] for installing and uninstalling [Apk] files using ADB.
|
||||||
|
*
|
||||||
|
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
||||||
|
*
|
||||||
|
* @see Installer
|
||||||
|
*/
|
||||||
|
class AdbInstaller(
|
||||||
|
deviceSerial: String? = null,
|
||||||
|
) : Installer<AdbInstallerResult, Installation>() {
|
||||||
|
private val device = Utils.getDevice(deviceSerial, logger)
|
||||||
|
private val adbShellCommandRunner = AdbShellCommandRunner(device)
|
||||||
|
private val packageManager = PackageManager(device)
|
||||||
|
|
||||||
|
init {
|
||||||
|
logger.fine("Connected to $deviceSerial")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun install(apk: Apk): AdbInstallerResult {
|
||||||
|
logger.info("Installing ${apk.file.name}")
|
||||||
|
|
||||||
|
return runPackageManager { install(apk.file) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun uninstall(packageName: String): AdbInstallerResult {
|
||||||
|
logger.info("Uninstalling $packageName")
|
||||||
|
|
||||||
|
return runPackageManager { uninstall(Package(packageName)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getInstallation(packageName: String): Installation? = packageManager.packages.find {
|
||||||
|
it.toString() == packageName
|
||||||
|
}?.let { Installation(adbShellCommandRunner(INSTALLED_APK_PATH).output) }
|
||||||
|
|
||||||
|
private fun runPackageManager(block: PackageManager.() -> Unit) = try {
|
||||||
|
packageManager.run(block)
|
||||||
|
|
||||||
|
AdbInstallerResult.Success
|
||||||
|
} catch (e: JadbException) {
|
||||||
|
AdbInstallerResult.Failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
import app.revanced.library.installation.installer.Installer.Apk
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of installing or uninstalling an [Apk] via ADB using [AdbInstaller].
|
||||||
|
*
|
||||||
|
* @see AdbInstaller
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
interface AdbInstallerResult {
|
||||||
|
/**
|
||||||
|
* The result of installing an [Apk] successfully.
|
||||||
|
*/
|
||||||
|
object Success : AdbInstallerResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of installing an [Apk] unsuccessfully.
|
||||||
|
*
|
||||||
|
* @param exception The exception that caused the installation to fail.
|
||||||
|
*/
|
||||||
|
class Failure internal constructor(val exception: Exception) : AdbInstallerResult
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
import app.revanced.library.installation.command.AdbShellCommandRunner
|
||||||
|
import app.revanced.library.installation.installer.Installer.Apk
|
||||||
|
import app.revanced.library.installation.installer.MountInstaller.NoRootPermissionException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [AdbMountInstaller] for installing and uninstalling [Apk] files with using ADB root permissions by mounting.
|
||||||
|
*
|
||||||
|
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
||||||
|
*
|
||||||
|
* @throws NoRootPermissionException If the device does not have root permission.
|
||||||
|
*
|
||||||
|
* @see MountInstaller
|
||||||
|
* @see AdbShellCommandRunner
|
||||||
|
*/
|
||||||
|
class AdbMountInstaller(
|
||||||
|
deviceSerial: String? = null,
|
||||||
|
) : MountInstaller({ AdbShellCommandRunner(deviceSerial) }) {
|
||||||
|
init {
|
||||||
|
logger.fine("Connected to $deviceSerial")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
internal object Constants {
|
||||||
|
const val PLACEHOLDER = "PLACEHOLDER"
|
||||||
|
|
||||||
|
const val TMP_FILE_PATH = "/data/local/tmp/revanced.tmp"
|
||||||
|
const val MOUNT_PATH = "/data/adb/revanced/"
|
||||||
|
const val MOUNTED_APK_PATH = "$MOUNT_PATH$PLACEHOLDER.apk"
|
||||||
|
const val MOUNT_SCRIPT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
|
||||||
|
|
||||||
|
const val EXISTS = "[[ -f $PLACEHOLDER ]] || exit 1"
|
||||||
|
const val MOUNT_GREP = "grep $PLACEHOLDER /proc/mounts"
|
||||||
|
const val DELETE = "rm -rf $PLACEHOLDER"
|
||||||
|
const val CREATE_DIR = "mkdir -p"
|
||||||
|
const val RESTART = "am start -S $PLACEHOLDER"
|
||||||
|
const val KILL = "am force-stop $PLACEHOLDER"
|
||||||
|
const val INSTALLED_APK_PATH = "pm path $PLACEHOLDER"
|
||||||
|
const val CREATE_INSTALLATION_PATH = "$CREATE_DIR $MOUNT_PATH"
|
||||||
|
|
||||||
|
const val MOUNT_APK =
|
||||||
|
"base_path=\"$MOUNTED_APK_PATH\" && " +
|
||||||
|
"mv $TMP_FILE_PATH \$base_path && " +
|
||||||
|
"chmod 644 \$base_path && " +
|
||||||
|
"chown system:system \$base_path && " +
|
||||||
|
"chcon u:object_r:apk_data_file:s0 \$base_path"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
const val INSTALL_MOUNT_SCRIPT = "mv $TMP_FILE_PATH $MOUNT_SCRIPT_PATH && chmod +x $MOUNT_SCRIPT_PATH"
|
||||||
|
|
||||||
|
val MOUNT_SCRIPT =
|
||||||
|
"""
|
||||||
|
#!/system/bin/sh
|
||||||
|
until [ "$( getprop sys.boot_completed )" = 1 ]; do sleep 3; done
|
||||||
|
until [ -d "/sdcard/Android" ]; do sleep 1; done
|
||||||
|
|
||||||
|
stock_path=$( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
|
||||||
|
|
||||||
|
# Make sure the app is installed.
|
||||||
|
if [ -z "${'$'}stock_path" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unmount any existing installations to prevent multiple unnecessary mounts.
|
||||||
|
$UMOUNT
|
||||||
|
|
||||||
|
base_path="$MOUNTED_APK_PATH"
|
||||||
|
|
||||||
|
chcon u:object_r:apk_data_file:s0 ${'$'}base_path
|
||||||
|
|
||||||
|
# Use Magisk mirror, if possible.
|
||||||
|
if command -v magisk &> /dev/null; then
|
||||||
|
MIRROR="${'$'}(magisk --path)/.magisk/mirror"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
|
||||||
|
|
||||||
|
# Kill the app to force it to restart the mounted APK in case it's currently running.
|
||||||
|
$KILL
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the [PLACEHOLDER] with the given [replacement].
|
||||||
|
*
|
||||||
|
* @param replacement The replacement to use.
|
||||||
|
* @return The replaced string.
|
||||||
|
*/
|
||||||
|
operator fun String.invoke(replacement: String) = replace(PLACEHOLDER, replacement)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Installation] of an apk file.
|
||||||
|
*
|
||||||
|
* @param apkFilePath The apk file path.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
open class Installation internal constructor(
|
||||||
|
val apkFilePath: String,
|
||||||
|
)
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
import app.revanced.library.installation.installer.Installer.Apk
|
||||||
|
import java.io.File
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Installer] for installing and uninstalling [Apk] files.
|
||||||
|
*
|
||||||
|
* @param TInstallerResult The type of the result of the installation.
|
||||||
|
* @param TInstallation The type of the installation.
|
||||||
|
*/
|
||||||
|
abstract class Installer<TInstallerResult, TInstallation : Installation> internal constructor() {
|
||||||
|
/**
|
||||||
|
* The [Logger].
|
||||||
|
*/
|
||||||
|
protected val logger: Logger = Logger.getLogger(this::class.java.name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the [Apk] file.
|
||||||
|
*
|
||||||
|
* @param apk The [Apk] file.
|
||||||
|
*
|
||||||
|
* @return The result of the installation.
|
||||||
|
*/
|
||||||
|
abstract suspend fun install(apk: Apk): TInstallerResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls the package.
|
||||||
|
*
|
||||||
|
* @param packageName The package name.
|
||||||
|
*
|
||||||
|
* @return The result of the uninstallation.
|
||||||
|
*/
|
||||||
|
abstract suspend fun uninstall(packageName: String): TInstallerResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current installation or null if not installed.
|
||||||
|
*
|
||||||
|
* @param packageName The package name.
|
||||||
|
*
|
||||||
|
* @return The installation.
|
||||||
|
*/
|
||||||
|
abstract suspend fun getInstallation(packageName: String): TInstallation?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apk file for [Installer].
|
||||||
|
*
|
||||||
|
* @param file The [Apk] file.
|
||||||
|
* @param packageName The package name of the [Apk] file.
|
||||||
|
*/
|
||||||
|
class Apk(val file: File, val packageName: String? = null)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MountInstallation] of the apk file that is mounted to [installedApkFilePath] with root permissions.
|
||||||
|
*
|
||||||
|
* @param installedApkFilePath The installed apk file path or null if the apk is not installed.
|
||||||
|
* @param apkFilePath The mounting apk file path.
|
||||||
|
* @param mounted Whether the apk is mounted to [installedApkFilePath].
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
class MountInstallation internal constructor(
|
||||||
|
val installedApkFilePath: String?,
|
||||||
|
apkFilePath: String,
|
||||||
|
val mounted: Boolean,
|
||||||
|
) : Installation(apkFilePath)
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
import app.revanced.library.installation.command.ShellCommandRunner
|
||||||
|
import app.revanced.library.installation.installer.Constants.CREATE_INSTALLATION_PATH
|
||||||
|
import app.revanced.library.installation.installer.Constants.DELETE
|
||||||
|
import app.revanced.library.installation.installer.Constants.EXISTS
|
||||||
|
import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH
|
||||||
|
import app.revanced.library.installation.installer.Constants.INSTALL_MOUNT_SCRIPT
|
||||||
|
import app.revanced.library.installation.installer.Constants.KILL
|
||||||
|
import app.revanced.library.installation.installer.Constants.MOUNTED_APK_PATH
|
||||||
|
import app.revanced.library.installation.installer.Constants.MOUNT_APK
|
||||||
|
import app.revanced.library.installation.installer.Constants.MOUNT_GREP
|
||||||
|
import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT
|
||||||
|
import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT_PATH
|
||||||
|
import app.revanced.library.installation.installer.Constants.RESTART
|
||||||
|
import app.revanced.library.installation.installer.Constants.TMP_FILE_PATH
|
||||||
|
import app.revanced.library.installation.installer.Constants.UMOUNT
|
||||||
|
import app.revanced.library.installation.installer.Constants.invoke
|
||||||
|
import app.revanced.library.installation.installer.Installer.Apk
|
||||||
|
import app.revanced.library.installation.installer.MountInstaller.NoRootPermissionException
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MountInstaller] for installing and uninstalling [Apk] files using root permissions by mounting.
|
||||||
|
*
|
||||||
|
* @param shellCommandRunnerSupplier A supplier for the [ShellCommandRunner] to use.
|
||||||
|
*
|
||||||
|
* @throws NoRootPermissionException If the device does not have root permission.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
abstract class MountInstaller internal constructor(
|
||||||
|
shellCommandRunnerSupplier: (MountInstaller) -> ShellCommandRunner,
|
||||||
|
) : Installer<MountInstallerResult, MountInstallation>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command runner used to run commands on the device.
|
||||||
|
*/
|
||||||
|
@Suppress("LeakingThis")
|
||||||
|
protected val shellCommandRunner = shellCommandRunnerSupplier(this)
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (!shellCommandRunner.hasRootPermission()) throw NoRootPermissionException()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the given [apk] by mounting.
|
||||||
|
*
|
||||||
|
* @param apk The [Apk] to install.
|
||||||
|
*
|
||||||
|
* @throws PackageNameRequiredException If the [Apk] does not have a package name.
|
||||||
|
*/
|
||||||
|
override suspend fun install(apk: Apk): MountInstallerResult {
|
||||||
|
logger.info("Installing ${apk.packageName} by mounting")
|
||||||
|
|
||||||
|
val packageName = apk.packageName?.also { it.assertInstalled() } ?: throw PackageNameRequiredException()
|
||||||
|
|
||||||
|
// Setup files.
|
||||||
|
apk.file.move(TMP_FILE_PATH)
|
||||||
|
CREATE_INSTALLATION_PATH().waitFor()
|
||||||
|
MOUNT_APK(packageName)().waitFor()
|
||||||
|
|
||||||
|
// Install and run.
|
||||||
|
TMP_FILE_PATH.write(MOUNT_SCRIPT(packageName))
|
||||||
|
INSTALL_MOUNT_SCRIPT(packageName)().waitFor()
|
||||||
|
MOUNT_SCRIPT_PATH(packageName)().waitFor()
|
||||||
|
RESTART(packageName)()
|
||||||
|
|
||||||
|
DELETE(TMP_FILE_PATH)()
|
||||||
|
|
||||||
|
return MountInstallerResult.SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun uninstall(packageName: String): MountInstallerResult {
|
||||||
|
logger.info("Uninstalling $packageName by unmounting")
|
||||||
|
|
||||||
|
UMOUNT(packageName)()
|
||||||
|
|
||||||
|
DELETE(MOUNTED_APK_PATH)(packageName)()
|
||||||
|
DELETE(MOUNT_SCRIPT_PATH)(packageName)()
|
||||||
|
DELETE(TMP_FILE_PATH)() // Remove residual.
|
||||||
|
|
||||||
|
KILL(packageName)()
|
||||||
|
|
||||||
|
return MountInstallerResult.SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getInstallation(packageName: String): MountInstallation? {
|
||||||
|
val patchedApkPath = MOUNTED_APK_PATH(packageName)
|
||||||
|
|
||||||
|
val patchedApkExists = EXISTS(patchedApkPath)().exitCode == 0
|
||||||
|
if (patchedApkExists) return null
|
||||||
|
|
||||||
|
return MountInstallation(
|
||||||
|
INSTALLED_APK_PATH(packageName)().output.ifEmpty { null },
|
||||||
|
patchedApkPath,
|
||||||
|
MOUNT_GREP(patchedApkPath)().exitCode == 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a command on the device.
|
||||||
|
*/
|
||||||
|
protected operator fun String.invoke() = shellCommandRunner(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the given file to the given [targetFilePath].
|
||||||
|
*
|
||||||
|
* @param targetFilePath The target file path.
|
||||||
|
*/
|
||||||
|
protected fun File.move(targetFilePath: String) = shellCommandRunner.move(this, targetFilePath)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the given [content] to the file.
|
||||||
|
*
|
||||||
|
* @param content The content of the file.
|
||||||
|
*/
|
||||||
|
protected fun String.write(content: String) = shellCommandRunner.write(content.byteInputStream(), this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the package is installed.
|
||||||
|
*
|
||||||
|
* @throws FailedToFindInstalledPackageException If the package is not installed.
|
||||||
|
*/
|
||||||
|
private fun String.assertInstalled() {
|
||||||
|
if (INSTALLED_APK_PATH(this)().output.isNotEmpty()) {
|
||||||
|
throw FailedToFindInstalledPackageException(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class FailedToFindInstalledPackageException internal constructor(packageName: String) :
|
||||||
|
Exception("Failed to find installed package \"$packageName\" because no activity was found")
|
||||||
|
|
||||||
|
internal class PackageNameRequiredException internal constructor() : Exception("Package name is required")
|
||||||
|
internal class NoRootPermissionException internal constructor() : Exception("No root permission")
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
import app.revanced.library.installation.installer.Installer.Apk
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of installing or uninstalling an [Apk] with root permissions using [MountInstaller].
|
||||||
|
*
|
||||||
|
* @see MountInstaller
|
||||||
|
*/
|
||||||
|
enum class MountInstallerResult {
|
||||||
|
/**
|
||||||
|
* The result of installing an [Apk] successfully.
|
||||||
|
*/
|
||||||
|
SUCCESS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of installing an [Apk] unsuccessfully.
|
||||||
|
*/
|
||||||
|
FAILURE,
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package app.revanced.library.installation.installer
|
||||||
|
|
||||||
|
import se.vidstige.jadb.JadbConnection
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility functions for [Installer].
|
||||||
|
*
|
||||||
|
* @see Installer
|
||||||
|
*/
|
||||||
|
internal object Utils {
|
||||||
|
/**
|
||||||
|
* Gets the device with the given serial.
|
||||||
|
*
|
||||||
|
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
||||||
|
* @param logger The logger.
|
||||||
|
* @return The device.
|
||||||
|
* @throws DeviceNotFoundException If no device with the given serial is found.
|
||||||
|
*/
|
||||||
|
internal fun getDevice(
|
||||||
|
deviceSerial: String? = null,
|
||||||
|
logger: Logger,
|
||||||
|
) = with(JadbConnection().devices) {
|
||||||
|
if (isEmpty()) throw DeviceNotFoundException()
|
||||||
|
|
||||||
|
deviceSerial?.let {
|
||||||
|
firstOrNull { it.serial == deviceSerial } ?: throw DeviceNotFoundException(
|
||||||
|
deviceSerial,
|
||||||
|
)
|
||||||
|
} ?: first().also {
|
||||||
|
logger.warning("No device serial supplied. Using device with serial ${it.serial}")
|
||||||
|
}
|
||||||
|
}!!
|
||||||
|
|
||||||
|
class DeviceNotFoundException internal constructor(deviceSerial: String? = null) : Exception(
|
||||||
|
deviceSerial?.let {
|
||||||
|
"The device with the ADB device serial \"$deviceSerial\" can not be found"
|
||||||
|
} ?: "No ADB device found",
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,15 +5,17 @@ import java.util.logging.Level
|
|||||||
import java.util.logging.LogRecord
|
import java.util.logging.LogRecord
|
||||||
import java.util.logging.SimpleFormatter
|
import java.util.logging.SimpleFormatter
|
||||||
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||||
object Logger {
|
object Logger {
|
||||||
/**
|
/**
|
||||||
* Rules for allowed loggers.
|
* Rules for allowed loggers.
|
||||||
*/
|
*/
|
||||||
private val allowedLoggersRules =
|
private val allowedLoggersRules =
|
||||||
arrayOf<String.() -> Boolean>(
|
arrayOf<String.() -> Boolean>(
|
||||||
{ startsWith("app.revanced") }, // ReVanced loggers.
|
// ReVanced loggers.
|
||||||
{ this == "" }, // Logs warnings when compiling resources (Logger in class brut.util.OS).
|
{ startsWith("app.revanced") },
|
||||||
|
// Logs warnings when compiling resources (Logger in class brut.util.OS).
|
||||||
|
{ this == "" },
|
||||||
)
|
)
|
||||||
|
|
||||||
private val rootLogger = java.util.logging.Logger.getLogger("")
|
private val rootLogger = java.util.logging.Logger.getLogger("")
|
||||||
@@ -6,38 +6,31 @@ import app.revanced.patcher.patch.BytecodePatch
|
|||||||
import app.revanced.patcher.patch.annotation.Patch
|
import app.revanced.patcher.patch.annotation.Patch
|
||||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
|
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
|
||||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
|
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
|
||||||
import org.junit.jupiter.api.MethodOrderer
|
import kotlin.test.Test
|
||||||
import org.junit.jupiter.api.Order
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestMethodOrder
|
|
||||||
|
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
class PatchOptionsTest {
|
||||||
internal object PatchOptionsTest {
|
|
||||||
private var patches = setOf(PatchOptionsTestPatch)
|
private var patches = setOf(PatchOptionsTestPatch)
|
||||||
|
|
||||||
@Test
|
private val serializedJson =
|
||||||
@Order(1)
|
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":null},{\"key\":\"key2\"," +
|
||||||
fun serializeTest() {
|
"\"value\":true}]}]"
|
||||||
assert(SERIALIZED_JSON == Options.serialize(patches))
|
|
||||||
}
|
private val changedJson =
|
||||||
|
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":\"test\"},{\"key\":\"key2" +
|
||||||
|
"\",\"value\":false}]}]"
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(2)
|
fun `serializes and deserializes`() {
|
||||||
fun loadOptionsTest() {
|
assert(serializedJson == Options.serialize(patches))
|
||||||
patches.setOptions(CHANGED_JSON)
|
|
||||||
|
patches.setOptions(changedJson)
|
||||||
|
|
||||||
assert(PatchOptionsTestPatch.option1 == "test")
|
assert(PatchOptionsTestPatch.option1 == "test")
|
||||||
assert(PatchOptionsTestPatch.option2 == false)
|
assert(PatchOptionsTestPatch.option2 == 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}]}]"
|
|
||||||
|
|
||||||
@Patch("PatchOptionsTestPatch")
|
@Patch("PatchOptionsTestPatch")
|
||||||
object PatchOptionsTestPatch : BytecodePatch() {
|
object PatchOptionsTestPatch : BytecodePatch(emptySet()) {
|
||||||
var option1 by stringPatchOption("key1", null, null, "title1", "description1")
|
var option1 by stringPatchOption("key1", null, null, "title1", "description1")
|
||||||
var option2 by booleanPatchOption("key2", true, null, "title2", "description2")
|
var option2 by booleanPatchOption("key2", true, null, "title2", "description2")
|
||||||
|
|
||||||
@@ -7,12 +7,12 @@ import app.revanced.patcher.patch.Patch
|
|||||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
|
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
|
||||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.intArrayPatchOption
|
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.intArrayPatchOption
|
||||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
|
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
internal object PatchUtilsTest {
|
internal class PatchUtilsTest {
|
||||||
private val patches =
|
private val patches =
|
||||||
arrayOf(
|
arrayOf(
|
||||||
newPatch("some.package", setOf("a")) { stringPatchOption("string", "value") },
|
newPatch("some.package", setOf("a")) { stringPatchOption("string", "value") },
|
||||||
@@ -61,12 +61,12 @@ internal object PatchUtilsTest {
|
|||||||
fun assertEqualsExpected(compatiblePackageNames: Set<String>?) =
|
fun assertEqualsExpected(compatiblePackageNames: Set<String>?) =
|
||||||
assertEqualsVersions(
|
assertEqualsVersions(
|
||||||
expected =
|
expected =
|
||||||
mapOf(
|
mapOf(
|
||||||
"some.package" to linkedMapOf("a" to 3, "b" to 2, "c" to 1),
|
"some.package" to linkedMapOf("a" to 3, "b" to 2, "c" to 1),
|
||||||
"some.other.package" to linkedMapOf("b" to 3, "c" to 2, "d" to 1),
|
"some.other.package" to linkedMapOf("b" to 3, "c" to 2, "d" to 1),
|
||||||
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
|
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
|
||||||
"some.other.other.other.package" to linkedMapOf(),
|
"some.other.other.other.package" to linkedMapOf(),
|
||||||
),
|
),
|
||||||
patches,
|
patches,
|
||||||
compatiblePackageNames,
|
compatiblePackageNames,
|
||||||
countUnusedPatches = true,
|
countUnusedPatches = true,
|
||||||
@@ -74,12 +74,12 @@ internal object PatchUtilsTest {
|
|||||||
|
|
||||||
assertEqualsExpected(
|
assertEqualsExpected(
|
||||||
compatiblePackageNames =
|
compatiblePackageNames =
|
||||||
setOf(
|
setOf(
|
||||||
"some.package",
|
"some.package",
|
||||||
"some.other.package",
|
"some.other.package",
|
||||||
"some.other.other.package",
|
"some.other.other.package",
|
||||||
"some.other.other.other.package",
|
"some.other.other.other.package",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEqualsExpected(
|
assertEqualsExpected(
|
||||||
@@ -91,19 +91,19 @@ internal object PatchUtilsTest {
|
|||||||
fun `common versions correctly ordered for each package without counting unused patches`() {
|
fun `common versions correctly ordered for each package without counting unused patches`() {
|
||||||
assertEqualsVersions(
|
assertEqualsVersions(
|
||||||
expected =
|
expected =
|
||||||
mapOf(
|
mapOf(
|
||||||
"some.package" to linkedMapOf("a" to 1),
|
"some.package" to linkedMapOf("a" to 1),
|
||||||
"some.other.package" to linkedMapOf("b" to 2, "c" to 2, "d" to 1),
|
"some.other.package" to linkedMapOf("b" to 2, "c" to 2, "d" to 1),
|
||||||
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
|
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
|
||||||
),
|
),
|
||||||
patches,
|
patches,
|
||||||
compatiblePackageNames =
|
compatiblePackageNames =
|
||||||
setOf(
|
setOf(
|
||||||
"some.package",
|
"some.package",
|
||||||
"some.other.package",
|
"some.other.package",
|
||||||
"some.other.other.package",
|
"some.other.other.package",
|
||||||
"some.other.other.other.package",
|
"some.other.other.other.package",
|
||||||
),
|
),
|
||||||
countUnusedPatches = false,
|
countUnusedPatches = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -170,14 +170,6 @@ internal object PatchUtilsTest {
|
|||||||
patches: PatchSet,
|
patches: PatchSet,
|
||||||
compatiblePackageName: String,
|
compatiblePackageName: String,
|
||||||
) {
|
) {
|
||||||
// Test both the deprecated and the new method.
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
assertEquals(
|
|
||||||
expected,
|
|
||||||
PatchUtils.getMostCommonCompatibleVersion(patches, compatiblePackageName),
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
PatchUtils.getMostCommonCompatibleVersions(patches, setOf(compatiblePackageName))
|
PatchUtils.getMostCommonCompatibleVersions(patches, setOf(compatiblePackageName))
|
||||||
282
package-lock.json
generated
282
package-lock.json
generated
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "revanced-patches",
|
"name": "revanced-library",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1",
|
||||||
"gradle-semantic-release-plugin": "^1.9.1",
|
"gradle-semantic-release-plugin": "^1.9.1",
|
||||||
"semantic-release": "^23.0.0"
|
"semantic-release": "^23.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@@ -326,9 +326,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request": {
|
"node_modules/@octokit/request": {
|
||||||
"version": "8.1.6",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.2.0.tgz",
|
||||||
"integrity": "sha512-YhPaGml3ncZC1NfXpP3WZ7iliL1ap6tLkAp6MvbK2fTTPytzVUyUesBBogcdMm86uRYO5rHaM1xIWxigWZ17MQ==",
|
"integrity": "sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/endpoint": "^9.0.0",
|
"@octokit/endpoint": "^9.0.0",
|
||||||
@@ -564,6 +564,26 @@
|
|||||||
"node": ">= 16"
|
"node": ">= 16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@saithodev/semantic-release-backmerge/node_modules/marked-terminal": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-ubWhwcBFHnXsjYNsu+Wndpg0zhY4CahSpPlA70PlO0rR9r2sZpkyU+rkCsOWH+KMEkx847UpALON+HWgxowFtw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-escapes": "^6.2.0",
|
||||||
|
"cardinal": "^2.1.1",
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"cli-table3": "^0.6.3",
|
||||||
|
"node-emoji": "^2.1.3",
|
||||||
|
"supports-hyperlinks": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"marked": ">=1 <12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@saithodev/semantic-release-backmerge/node_modules/mimic-fn": {
|
"node_modules/@saithodev/semantic-release-backmerge/node_modules/mimic-fn": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||||
@@ -1195,9 +1215,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sindresorhus/merge-streams": {
|
"node_modules/@sindresorhus/merge-streams": {
|
||||||
"version": "1.0.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.2.0.tgz",
|
||||||
"integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==",
|
"integrity": "sha512-UTce8mUwUW0RikMb/eseJ7ys0BRkZVFB86orHzrfW12ZmFtym5zua8joZ4L7okH2dDFHkcFjqnZ5GocWBXOFtA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -1282,6 +1302,12 @@
|
|||||||
"integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==",
|
"integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/any-promise": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
@@ -1376,6 +1402,81 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cli-highlight": {
|
||||||
|
"version": "2.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz",
|
||||||
|
"integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^4.0.0",
|
||||||
|
"highlight.js": "^10.7.1",
|
||||||
|
"mz": "^2.4.0",
|
||||||
|
"parse5": "^5.1.1",
|
||||||
|
"parse5-htmlparser2-tree-adapter": "^6.0.0",
|
||||||
|
"yargs": "^16.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"highlight": "bin/highlight"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0",
|
||||||
|
"npm": ">=5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cli-highlight/node_modules/chalk": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cli-highlight/node_modules/cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cli-highlight/node_modules/yargs": {
|
||||||
|
"version": "16.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||||
|
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^20.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cli-highlight/node_modules/yargs-parser": {
|
||||||
|
"version": "20.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||||
|
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cli-table3": {
|
"node_modules/cli-table3": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz",
|
||||||
@@ -1818,9 +1919,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -1888,9 +1989,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fastq": {
|
"node_modules/fastq": {
|
||||||
"version": "1.16.0",
|
"version": "1.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
||||||
"integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==",
|
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
@@ -2052,12 +2153,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/globby": {
|
"node_modules/globby": {
|
||||||
"version": "14.0.0",
|
"version": "14.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz",
|
||||||
"integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==",
|
"integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sindresorhus/merge-streams": "^1.0.0",
|
"@sindresorhus/merge-streams": "^2.1.0",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"ignore": "^5.2.4",
|
"ignore": "^5.2.4",
|
||||||
"path-type": "^5.0.0",
|
"path-type": "^5.0.0",
|
||||||
@@ -2142,9 +2243,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
||||||
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
|
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -2153,6 +2254,15 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/highlight.js": {
|
||||||
|
"version": "10.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
|
||||||
|
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hook-std": {
|
"node_modules/hook-std": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz",
|
||||||
@@ -2178,9 +2288,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/http-proxy-agent": {
|
"node_modules/http-proxy-agent": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.1.tgz",
|
||||||
"integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
|
"integrity": "sha512-My1KCEPs6A0hb4qCVzYp8iEvA8j8YqcvXLZZH8C9OFuTYpYjHE7N2dtG3mRl1HMD4+VGXpF3XcDVcxGBT7yDZQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"agent-base": "^7.1.0",
|
"agent-base": "^7.1.0",
|
||||||
@@ -2191,9 +2301,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/https-proxy-agent": {
|
"node_modules/https-proxy-agent": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.3.tgz",
|
||||||
"integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
|
"integrity": "sha512-kCnwztfX0KZJSLOBrcL0emLeFako55NWMovvyPP2AjsghNk9RB1yjSI+jVumPHYZsNXegNoqupSW9IY3afSH8w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"agent-base": "^7.0.2",
|
"agent-base": "^7.0.2",
|
||||||
@@ -2213,9 +2323,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||||
"integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
|
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
@@ -2629,9 +2739,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/marked": {
|
"node_modules/marked": {
|
||||||
"version": "11.1.1",
|
"version": "12.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-11.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/marked/-/marked-12.0.0.tgz",
|
||||||
"integrity": "sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg==",
|
"integrity": "sha512-Vkwtq9rLqXryZnWaQc86+FHLC6tr/fycMfYAhiOIXkrNmeGAyhSxjqu0Rs1i0bBqw5u0S7+lV9fdH2ZSVaoa0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"marked": "bin/marked.js"
|
"marked": "bin/marked.js"
|
||||||
@@ -2641,14 +2751,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/marked-terminal": {
|
"node_modules/marked-terminal": {
|
||||||
"version": "6.2.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.0.0.tgz",
|
||||||
"integrity": "sha512-ubWhwcBFHnXsjYNsu+Wndpg0zhY4CahSpPlA70PlO0rR9r2sZpkyU+rkCsOWH+KMEkx847UpALON+HWgxowFtw==",
|
"integrity": "sha512-sNEx8nn9Ktcm6pL0TnRz8tnXq/mSS0Q1FRSwJOAqw4lAB4l49UeDf85Gm1n9RPFm5qurCPjwi1StAQT2XExhZw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-escapes": "^6.2.0",
|
"ansi-escapes": "^6.2.0",
|
||||||
"cardinal": "^2.1.1",
|
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
|
"cli-highlight": "^2.1.11",
|
||||||
"cli-table3": "^0.6.3",
|
"cli-table3": "^0.6.3",
|
||||||
"node-emoji": "^2.1.3",
|
"node-emoji": "^2.1.3",
|
||||||
"supports-hyperlinks": "^3.0.0"
|
"supports-hyperlinks": "^3.0.0"
|
||||||
@@ -2657,7 +2767,7 @@
|
|||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"marked": ">=1 <12"
|
"marked": ">=1 <13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/meow": {
|
"node_modules/meow": {
|
||||||
@@ -2739,6 +2849,17 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/mz": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"any-promise": "^1.0.0",
|
||||||
|
"object-assign": "^4.0.1",
|
||||||
|
"thenify-all": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/neo-async": {
|
"node_modules/neo-async": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||||
@@ -5551,6 +5672,15 @@
|
|||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/once": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
@@ -5695,6 +5825,27 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parse5": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/parse5-htmlparser2-tree-adapter": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"parse5": "^6.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/parsimmon": {
|
"node_modules/parsimmon": {
|
||||||
"version": "1.18.1",
|
"version": "1.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz",
|
||||||
@@ -5860,9 +6011,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/read-pkg-up/node_modules/type-fest": {
|
"node_modules/read-pkg-up/node_modules/type-fest": {
|
||||||
"version": "4.10.1",
|
"version": "4.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.2.tgz",
|
||||||
"integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==",
|
"integrity": "sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
@@ -5889,9 +6040,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/read-pkg/node_modules/type-fest": {
|
"node_modules/read-pkg/node_modules/type-fest": {
|
||||||
"version": "4.10.1",
|
"version": "4.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.2.tgz",
|
||||||
"integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==",
|
"integrity": "sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
@@ -5994,9 +6145,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/semantic-release": {
|
"node_modules/semantic-release": {
|
||||||
"version": "23.0.0",
|
"version": "23.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-23.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-23.0.2.tgz",
|
||||||
"integrity": "sha512-Jz7jEWO2igTtske112gC4PPE2whCMVrsgxUPG3/SZI7VE357suIUZFlJd1Yu0g2I6RPc2HxNEfUg7KhmDTjwqg==",
|
"integrity": "sha512-OnVYJ6Xgzwe1x8MKswba7RU9+5djS1MWRTrTn5qsq3xZYpslroZkV9Pt0dA2YcIuieeuSZWJhn+yUWoBUHO5Fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@semantic-release/commit-analyzer": "^11.0.0",
|
"@semantic-release/commit-analyzer": "^11.0.0",
|
||||||
@@ -6017,8 +6168,8 @@
|
|||||||
"hosted-git-info": "^7.0.0",
|
"hosted-git-info": "^7.0.0",
|
||||||
"import-from-esm": "^1.3.1",
|
"import-from-esm": "^1.3.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^11.0.0",
|
"marked": "^12.0.0",
|
||||||
"marked-terminal": "^6.0.0",
|
"marked-terminal": "^7.0.0",
|
||||||
"micromatch": "^4.0.2",
|
"micromatch": "^4.0.2",
|
||||||
"p-each-series": "^3.0.0",
|
"p-each-series": "^3.0.0",
|
||||||
"p-reduce": "^3.0.0",
|
"p-reduce": "^3.0.0",
|
||||||
@@ -6247,9 +6398,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.5.4",
|
"version": "7.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^6.0.0"
|
"lru-cache": "^6.0.0"
|
||||||
@@ -6481,9 +6632,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/spdx-license-ids": {
|
"node_modules/spdx-license-ids": {
|
||||||
"version": "3.0.16",
|
"version": "3.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
|
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
|
||||||
"integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==",
|
"integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/split2": {
|
"node_modules/split2": {
|
||||||
@@ -6655,6 +6806,27 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/thenify": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"any-promise": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/thenify-all": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"thenify": ">= 3.1.0 < 4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/through": {
|
"node_modules/through": {
|
||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1",
|
||||||
"gradle-semantic-release-plugin": "^1.9.1",
|
"gradle-semantic-release-plugin": "^1.9.1",
|
||||||
"semantic-release": "^23.0.0"
|
"semantic-release": "^23.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
rootProject.name = "revanced-library"
|
|
||||||
|
|
||||||
buildCache {
|
buildCache {
|
||||||
local {
|
local {
|
||||||
isEnabled = !System.getenv().containsKey("CI")
|
isEnabled = "CI" !in System.getenv()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
include(":library", ":library-networking")
|
||||||
|
|||||||
@@ -1,273 +0,0 @@
|
|||||||
package app.revanced.library
|
|
||||||
|
|
||||||
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.jcajce.JcaContentSignerBuilder
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.math.BigInteger
|
|
||||||
import java.security.*
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.util.*
|
|
||||||
import java.util.logging.Logger
|
|
||||||
import kotlin.time.Duration.Companion.days
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for writing or reading keystore files and entries as well as signing APK files.
|
|
||||||
*/
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
|
||||||
object ApkSigner {
|
|
||||||
private val logger = Logger.getLogger(app.revanced.library.ApkSigner::class.java.name)
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
|
|
||||||
Security.addProvider(BouncyCastleProvider())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [PrivateKeyCertificatePair].
|
|
||||||
*
|
|
||||||
* @param commonName The common name of the certificate.
|
|
||||||
* @param validUntil The date until the certificate is valid.
|
|
||||||
* @return The created [PrivateKeyCertificatePair].
|
|
||||||
*/
|
|
||||||
fun newPrivateKeyCertificatePair(
|
|
||||||
commonName: String = "ReVanced",
|
|
||||||
validUntil: Date = Date(System.currentTimeMillis() + 356.days.inWholeMilliseconds * 24),
|
|
||||||
): PrivateKeyCertificatePair {
|
|
||||||
logger.fine("Creating certificate for $commonName")
|
|
||||||
|
|
||||||
// Generate a new key pair.
|
|
||||||
val keyPair =
|
|
||||||
KeyPairGenerator.getInstance("RSA").apply {
|
|
||||||
initialize(4096)
|
|
||||||
}.generateKeyPair()
|
|
||||||
|
|
||||||
var serialNumber: BigInteger
|
|
||||||
do serialNumber = BigInteger.valueOf(SecureRandom().nextLong())
|
|
||||||
while (serialNumber < BigInteger.ZERO)
|
|
||||||
|
|
||||||
val name = X500Name("CN=$commonName")
|
|
||||||
|
|
||||||
// Create a new certificate.
|
|
||||||
val certificate =
|
|
||||||
JcaX509CertificateConverter().getCertificate(
|
|
||||||
X509v3CertificateBuilder(
|
|
||||||
name,
|
|
||||||
serialNumber,
|
|
||||||
Date(System.currentTimeMillis()),
|
|
||||||
validUntil,
|
|
||||||
Locale.ENGLISH,
|
|
||||||
name,
|
|
||||||
SubjectPublicKeyInfo.getInstance(keyPair.public.encoded),
|
|
||||||
).build(JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private)),
|
|
||||||
)
|
|
||||||
|
|
||||||
return PrivateKeyCertificatePair(keyPair.private, certificate)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a [PrivateKeyCertificatePair] from a keystore entry.
|
|
||||||
*
|
|
||||||
* @param keyStore The keystore to read the entry from.
|
|
||||||
* @param keyStoreEntryAlias The alias of the key store entry to read.
|
|
||||||
* @param keyStoreEntryPassword The password for recovering the signing key.
|
|
||||||
* @return The read [PrivateKeyCertificatePair].
|
|
||||||
* @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid.
|
|
||||||
*/
|
|
||||||
fun readKeyCertificatePair(
|
|
||||||
keyStore: KeyStore,
|
|
||||||
keyStoreEntryAlias: String,
|
|
||||||
keyStoreEntryPassword: String,
|
|
||||||
): PrivateKeyCertificatePair {
|
|
||||||
logger.fine("Reading key and certificate pair from keystore entry $keyStoreEntryAlias")
|
|
||||||
|
|
||||||
if (!keyStore.containsAlias(keyStoreEntryAlias)) {
|
|
||||||
throw IllegalArgumentException("Keystore does not contain alias $keyStoreEntryAlias")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the private key and certificate from the keystore.
|
|
||||||
|
|
||||||
val privateKey =
|
|
||||||
try {
|
|
||||||
keyStore.getKey(keyStoreEntryAlias, keyStoreEntryPassword.toCharArray()) as PrivateKey
|
|
||||||
} catch (exception: UnrecoverableKeyException) {
|
|
||||||
throw IllegalArgumentException("Invalid password for keystore entry $keyStoreEntryAlias")
|
|
||||||
}
|
|
||||||
|
|
||||||
val certificate = keyStore.getCertificate(keyStoreEntryAlias) as X509Certificate
|
|
||||||
|
|
||||||
return PrivateKeyCertificatePair(privateKey, certificate)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new keystore with a new keypair.
|
|
||||||
*
|
|
||||||
* @param entries The entries to add to the keystore.
|
|
||||||
* @return The created keystore.
|
|
||||||
* @see KeyStoreEntry
|
|
||||||
*/
|
|
||||||
fun newKeyStore(entries: List<KeyStoreEntry>): KeyStore {
|
|
||||||
logger.fine("Creating keystore")
|
|
||||||
|
|
||||||
return KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME).apply {
|
|
||||||
load(null)
|
|
||||||
|
|
||||||
entries.forEach { entry ->
|
|
||||||
// Add all entries to the keystore.
|
|
||||||
setKeyEntry(
|
|
||||||
entry.alias,
|
|
||||||
entry.privateKeyCertificatePair.privateKey,
|
|
||||||
entry.password.toCharArray(),
|
|
||||||
arrayOf(entry.privateKeyCertificatePair.certificate),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new keystore with a new keypair and saves it to the given [keyStoreOutputStream].
|
|
||||||
*
|
|
||||||
* @param keyStoreOutputStream The stream to write the keystore to.
|
|
||||||
* @param keyStorePassword The password for the keystore.
|
|
||||||
* @param entries The entries to add to the keystore.
|
|
||||||
*/
|
|
||||||
fun newKeyStore(
|
|
||||||
keyStoreOutputStream: OutputStream,
|
|
||||||
keyStorePassword: String,
|
|
||||||
entries: List<KeyStoreEntry>,
|
|
||||||
) = newKeyStore(entries).store(
|
|
||||||
keyStoreOutputStream,
|
|
||||||
keyStorePassword.toCharArray(),
|
|
||||||
) // Save the keystore.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a keystore from the given [keyStoreInputStream].
|
|
||||||
*
|
|
||||||
* @param keyStoreInputStream The stream to read the keystore from.
|
|
||||||
* @param keyStorePassword The password for the keystore.
|
|
||||||
* @return The keystore.
|
|
||||||
* @throws IllegalArgumentException If the keystore password is invalid.
|
|
||||||
*/
|
|
||||||
fun readKeyStore(
|
|
||||||
keyStoreInputStream: InputStream,
|
|
||||||
keyStorePassword: String?,
|
|
||||||
): KeyStore {
|
|
||||||
logger.fine("Reading keystore")
|
|
||||||
|
|
||||||
return KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME).apply {
|
|
||||||
try {
|
|
||||||
load(keyStoreInputStream, keyStorePassword?.toCharArray())
|
|
||||||
} catch (exception: IOException) {
|
|
||||||
if (exception.cause is UnrecoverableKeyException) {
|
|
||||||
throw IllegalArgumentException("Invalid keystore password")
|
|
||||||
} else {
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [ApkSigner.Builder].
|
|
||||||
*
|
|
||||||
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
|
|
||||||
* @param signer The name of the signer.
|
|
||||||
* @param createdBy The value for the `Created-By` attribute in the APK's manifest.
|
|
||||||
* @return The created [ApkSigner.Builder] instance.
|
|
||||||
*/
|
|
||||||
fun newApkSignerBuilder(
|
|
||||||
privateKeyCertificatePair: PrivateKeyCertificatePair,
|
|
||||||
signer: String,
|
|
||||||
createdBy: String,
|
|
||||||
): ApkSigner.Builder {
|
|
||||||
logger.fine(
|
|
||||||
"Creating new ApkSigner " +
|
|
||||||
"with $signer as signer and " +
|
|
||||||
"$createdBy as Created-By attribute in the APK's manifest",
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create the signer config.
|
|
||||||
val signerConfig =
|
|
||||||
ApkSigner.SignerConfig.Builder(
|
|
||||||
signer,
|
|
||||||
privateKeyCertificatePair.privateKey,
|
|
||||||
listOf(privateKeyCertificatePair.certificate),
|
|
||||||
).build()
|
|
||||||
|
|
||||||
// Create the signer.
|
|
||||||
return ApkSigner.Builder(listOf(signerConfig)).apply {
|
|
||||||
setCreatedBy(createdBy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [ApkSigner.Builder].
|
|
||||||
*
|
|
||||||
* @param keyStore The keystore to use for signing.
|
|
||||||
* @param keyStoreEntryAlias The alias of the key store entry to use for signing.
|
|
||||||
* @param keyStoreEntryPassword The password for recovering the signing key.
|
|
||||||
* @param signer The name of the signer.
|
|
||||||
* @param createdBy The value for the `Created-By` attribute in the APK's manifest.
|
|
||||||
* @return The created [ApkSigner.Builder] instance.
|
|
||||||
* @see KeyStoreEntry
|
|
||||||
* @see PrivateKeyCertificatePair
|
|
||||||
* @see ApkSigner.Builder.setCreatedBy
|
|
||||||
* @see ApkSigner.Builder.signApk
|
|
||||||
*/
|
|
||||||
fun newApkSignerBuilder(
|
|
||||||
keyStore: KeyStore,
|
|
||||||
keyStoreEntryAlias: String,
|
|
||||||
keyStoreEntryPassword: String,
|
|
||||||
signer: String,
|
|
||||||
createdBy: String,
|
|
||||||
) = newApkSignerBuilder(
|
|
||||||
readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword),
|
|
||||||
signer,
|
|
||||||
createdBy,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun ApkSigner.Builder.signApk(
|
|
||||||
input: File,
|
|
||||||
output: File,
|
|
||||||
) {
|
|
||||||
logger.info("Signing ${input.name}")
|
|
||||||
|
|
||||||
setInputApk(input)
|
|
||||||
setOutputApk(output)
|
|
||||||
|
|
||||||
build().sign()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An entry in a keystore.
|
|
||||||
*
|
|
||||||
* @param alias The alias of the entry.
|
|
||||||
* @param password The password for recovering the signing key.
|
|
||||||
* @param privateKeyCertificatePair The private key and certificate pair.
|
|
||||||
* @see PrivateKeyCertificatePair
|
|
||||||
*/
|
|
||||||
class KeyStoreEntry(
|
|
||||||
val alias: String,
|
|
||||||
val password: String,
|
|
||||||
val privateKeyCertificatePair: PrivateKeyCertificatePair = newPrivateKeyCertificatePair(),
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A private key and certificate pair.
|
|
||||||
*
|
|
||||||
* @param privateKey The private key.
|
|
||||||
* @param certificate The certificate.
|
|
||||||
*/
|
|
||||||
class PrivateKeyCertificatePair(
|
|
||||||
val privateKey: PrivateKey,
|
|
||||||
val certificate: X509Certificate,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package app.revanced.library
|
|
||||||
|
|
||||||
import app.revanced.library.ApkSigner.signApk
|
|
||||||
import app.revanced.library.zip.ZipFile
|
|
||||||
import app.revanced.library.zip.structures.ZipEntry
|
|
||||||
import app.revanced.patcher.PatcherResult
|
|
||||||
import java.io.File
|
|
||||||
import java.util.logging.Logger
|
|
||||||
import kotlin.io.path.deleteIfExists
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility functions for working with apks.
|
|
||||||
*/
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
|
||||||
object ApkUtils {
|
|
||||||
private val logger = Logger.getLogger(ApkUtils::class.java.name)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new apk from [apkFile] and [patchedEntriesSource] and writes it to [outputFile].
|
|
||||||
*
|
|
||||||
* @param apkFile The apk to copy entries from.
|
|
||||||
* @param outputFile The apk to write the new entries to.
|
|
||||||
* @param patchedEntriesSource The result of the patcher to add the patched dex files and resources.
|
|
||||||
*/
|
|
||||||
fun copyAligned(
|
|
||||||
apkFile: File,
|
|
||||||
outputFile: File,
|
|
||||||
patchedEntriesSource: PatcherResult,
|
|
||||||
) {
|
|
||||||
logger.info("Aligning ${apkFile.name}")
|
|
||||||
|
|
||||||
outputFile.toPath().deleteIfExists()
|
|
||||||
|
|
||||||
ZipFile(outputFile).use { file ->
|
|
||||||
patchedEntriesSource.dexFiles.forEach {
|
|
||||||
file.addEntryCompressData(
|
|
||||||
ZipEntry(it.name),
|
|
||||||
it.stream.readBytes(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedEntriesSource.resourceFile?.let {
|
|
||||||
file.copyEntriesFromFileAligned(
|
|
||||||
ZipFile(it),
|
|
||||||
ZipFile.apkZipEntryAlignment,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Do not compress result.doNotCompress
|
|
||||||
|
|
||||||
// TODO: Fix copying resources that are not needed anymore.
|
|
||||||
file.copyEntriesFromFileAligned(
|
|
||||||
ZipFile(apkFile),
|
|
||||||
ZipFile.apkZipEntryAlignment,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signs the [apk] file and writes it to [output].
|
|
||||||
*
|
|
||||||
* @param apk The apk to sign.
|
|
||||||
* @param output The apk to write the signed apk to.
|
|
||||||
* @param signingOptions The options to use for signing.
|
|
||||||
*/
|
|
||||||
fun sign(
|
|
||||||
apk: File,
|
|
||||||
output: File,
|
|
||||||
signingOptions: SigningOptions,
|
|
||||||
) {
|
|
||||||
// Get the keystore from the file or create a new one.
|
|
||||||
val keyStore =
|
|
||||||
if (signingOptions.keyStore.exists()) {
|
|
||||||
ApkSigner.readKeyStore(signingOptions.keyStore.inputStream(), signingOptions.keyStorePassword)
|
|
||||||
} else {
|
|
||||||
val entry = ApkSigner.KeyStoreEntry(signingOptions.alias, signingOptions.password)
|
|
||||||
|
|
||||||
// Create a new keystore with a new keypair and saves it.
|
|
||||||
ApkSigner.newKeyStore(listOf(entry)).also { keyStore ->
|
|
||||||
keyStore.store(
|
|
||||||
signingOptions.keyStore.outputStream(),
|
|
||||||
signingOptions.keyStorePassword?.toCharArray(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApkSigner.newApkSignerBuilder(
|
|
||||||
keyStore,
|
|
||||||
signingOptions.alias,
|
|
||||||
signingOptions.password,
|
|
||||||
signingOptions.signer,
|
|
||||||
signingOptions.signer,
|
|
||||||
).signApk(apk, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for signing an apk.
|
|
||||||
*
|
|
||||||
* @param keyStore The keystore to use for signing.
|
|
||||||
* @param keyStorePassword The password for the keystore.
|
|
||||||
* @param alias The alias of the key store entry to use for signing.
|
|
||||||
* @param password The password for recovering the signing key.
|
|
||||||
* @param signer The name of the signer.
|
|
||||||
*/
|
|
||||||
class SigningOptions(
|
|
||||||
val keyStore: File,
|
|
||||||
val keyStorePassword: String?,
|
|
||||||
val alias: String = "ReVanced Key",
|
|
||||||
val password: String = "",
|
|
||||||
val signer: String = "ReVanced",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
package app.revanced.library.adb
|
|
||||||
|
|
||||||
import app.revanced.library.adb.AdbManager.Apk
|
|
||||||
import app.revanced.library.adb.Constants.CREATE_DIR
|
|
||||||
import app.revanced.library.adb.Constants.DELETE
|
|
||||||
import app.revanced.library.adb.Constants.GET_INSTALLED_PATH
|
|
||||||
import app.revanced.library.adb.Constants.INSTALLATION_PATH
|
|
||||||
import app.revanced.library.adb.Constants.INSTALL_MOUNT_SCRIPT
|
|
||||||
import app.revanced.library.adb.Constants.INSTALL_PATCHED_APK
|
|
||||||
import app.revanced.library.adb.Constants.KILL
|
|
||||||
import app.revanced.library.adb.Constants.MOUNT_SCRIPT
|
|
||||||
import app.revanced.library.adb.Constants.MOUNT_SCRIPT_PATH
|
|
||||||
import app.revanced.library.adb.Constants.PATCHED_APK_PATH
|
|
||||||
import app.revanced.library.adb.Constants.PLACEHOLDER
|
|
||||||
import app.revanced.library.adb.Constants.RESTART
|
|
||||||
import app.revanced.library.adb.Constants.TMP_PATH
|
|
||||||
import app.revanced.library.adb.Constants.UMOUNT
|
|
||||||
import se.vidstige.jadb.JadbConnection
|
|
||||||
import se.vidstige.jadb.JadbDevice
|
|
||||||
import se.vidstige.jadb.managers.Package
|
|
||||||
import se.vidstige.jadb.managers.PackageManager
|
|
||||||
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. If null, the first connected device will be used.
|
|
||||||
*/
|
|
||||||
sealed class AdbManager private constructor(deviceSerial: String?) {
|
|
||||||
protected val logger: Logger = Logger.getLogger(AdbManager::class.java.name)
|
|
||||||
|
|
||||||
protected val device =
|
|
||||||
with(JadbConnection().devices) {
|
|
||||||
if (isEmpty()) throw DeviceNotFoundException()
|
|
||||||
|
|
||||||
deviceSerial?.let {
|
|
||||||
firstOrNull { it.serial == deviceSerial } ?: throw DeviceNotFoundException(deviceSerial)
|
|
||||||
} ?: first().also {
|
|
||||||
logger.warning("No device serial supplied. Using device with serial ${it.serial}")
|
|
||||||
}
|
|
||||||
}!!
|
|
||||||
|
|
||||||
init {
|
|
||||||
logger.fine("Connected to ${device.serial}")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Gets an [AdbManager] for the supplied device serial.
|
|
||||||
*
|
|
||||||
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
|
||||||
* @param root Whether to use root or not.
|
|
||||||
* @return The [AdbManager].
|
|
||||||
* @throws DeviceNotFoundException If the device can not be found.
|
|
||||||
*/
|
|
||||||
fun getAdbManager(
|
|
||||||
deviceSerial: String? = null,
|
|
||||||
root: Boolean = false,
|
|
||||||
): AdbManager = if (root) RootAdbManager(deviceSerial) else UserAdbManager(deviceSerial)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adb manager for rooted devices.
|
|
||||||
*
|
|
||||||
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
|
||||||
*/
|
|
||||||
class RootAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) {
|
|
||||||
init {
|
|
||||||
if (!device.hasSu()) throw IllegalArgumentException("Root required on ${device.serial}. Task failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun install(apk: Apk) {
|
|
||||||
logger.info("Installing by mounting")
|
|
||||||
|
|
||||||
val packageName = apk.packageName ?: throw PackageNameRequiredException()
|
|
||||||
|
|
||||||
device.run(GET_INSTALLED_PATH, packageName).inputStream.bufferedReader().readLine().let { line ->
|
|
||||||
if (line != null) return@let
|
|
||||||
throw throw FailedToFindInstalledPackageException(packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
device.push(apk.file, TMP_PATH)
|
|
||||||
|
|
||||||
device.run("$CREATE_DIR $INSTALLATION_PATH").waitFor()
|
|
||||||
device.run(INSTALL_PATCHED_APK, packageName).waitFor()
|
|
||||||
|
|
||||||
device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement(packageName))
|
|
||||||
|
|
||||||
device.run(INSTALL_MOUNT_SCRIPT, packageName).waitFor()
|
|
||||||
device.run(MOUNT_SCRIPT_PATH, packageName).waitFor()
|
|
||||||
device.run(RESTART, packageName)
|
|
||||||
device.run(DELETE, TMP_PATH)
|
|
||||||
|
|
||||||
super.install(apk)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun uninstall(packageName: String) {
|
|
||||||
logger.info("Uninstalling $packageName by unmounting")
|
|
||||||
|
|
||||||
device.run(UMOUNT, packageName)
|
|
||||||
device.run(DELETE.applyReplacement(PATCHED_APK_PATH), packageName)
|
|
||||||
device.run(DELETE, MOUNT_SCRIPT_PATH.applyReplacement(packageName))
|
|
||||||
device.run(DELETE, TMP_PATH)
|
|
||||||
device.run(KILL, packageName)
|
|
||||||
|
|
||||||
super.uninstall(packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object Utils {
|
|
||||||
private fun JadbDevice.run(
|
|
||||||
command: String,
|
|
||||||
with: String,
|
|
||||||
) = run(command.applyReplacement(with))
|
|
||||||
|
|
||||||
private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adb manager for non-rooted devices.
|
|
||||||
*
|
|
||||||
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
|
||||||
*/
|
|
||||||
class UserAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) {
|
|
||||||
private val packageManager = PackageManager(device)
|
|
||||||
|
|
||||||
override fun install(apk: Apk) {
|
|
||||||
logger.info("Installing ${apk.file.name}")
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
class Apk(val file: File, val packageName: String? = null)
|
|
||||||
|
|
||||||
class DeviceNotFoundException internal constructor(deviceSerial: String? = null) :
|
|
||||||
Exception(
|
|
||||||
deviceSerial?.let {
|
|
||||||
"The device with the ADB device serial \"$deviceSerial\" can not be found"
|
|
||||||
} ?: "No ADB device found",
|
|
||||||
)
|
|
||||||
|
|
||||||
class FailedToFindInstalledPackageException internal constructor(packageName: String) :
|
|
||||||
Exception("Failed to find installed package \"$packageName\" because no activity was found")
|
|
||||||
|
|
||||||
class PackageNameRequiredException internal constructor() :
|
|
||||||
Exception("Package name is required")
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package app.revanced.library.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<String>
|
|
||||||
val cmd = args.removeFirst()
|
|
||||||
|
|
||||||
return shellProcessBuilder(cmd, *args.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun JadbDevice.run(
|
|
||||||
command: String,
|
|
||||||
su: Boolean = true,
|
|
||||||
) = this.buildCommand(command, su).start()
|
|
||||||
|
|
||||||
internal fun JadbDevice.hasSu() = this.run("whoami", true).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))
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package app.revanced.library.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_SCRIPT_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 = "am start -S $PLACEHOLDER"
|
|
||||||
internal const val KILL = "am force-stop $PLACEHOLDER"
|
|
||||||
internal const val GET_INSTALLED_PATH = "pm path $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_SCRIPT = "mv $TMP_PATH $MOUNT_SCRIPT_PATH && chmod +x $MOUNT_SCRIPT_PATH"
|
|
||||||
|
|
||||||
internal val MOUNT_SCRIPT =
|
|
||||||
"""
|
|
||||||
#!/system/bin/sh
|
|
||||||
MAGISKTMP="$( magisk --path )" || MAGISKTMP=/sbin
|
|
||||||
MIRROR="${'$'}MAGISKTMP/.magisk/mirror"
|
|
||||||
|
|
||||||
until [ "$( getprop sys.boot_completed )" = 1 ]; do sleep 3; done
|
|
||||||
until [ -d "/sdcard/Android" ]; do sleep 1; done
|
|
||||||
|
|
||||||
# Unmount any existing mount as a safety measure
|
|
||||||
$UMOUNT
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# Kill the app to force it to restart the mounted APK in case it's already running
|
|
||||||
$KILL
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package app.revanced.library.zip
|
|
||||||
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.io.DataOutput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
internal 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()
|
|
||||||
|
|
||||||
internal fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort()
|
|
||||||
|
|
||||||
internal 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()
|
|
||||||
|
|
||||||
internal fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort()
|
|
||||||
|
|
||||||
internal fun ByteBuffer.getUShort() = this.getShort().toUShort()
|
|
||||||
|
|
||||||
internal fun ByteBuffer.getUInt() = this.getInt().toUInt()
|
|
||||||
|
|
||||||
internal fun ByteBuffer.putUShort(ushort: UShort) = this.putShort(ushort.toShort())
|
|
||||||
|
|
||||||
internal fun ByteBuffer.putUInt(uint: UInt) = this.putInt(uint.toInt())
|
|
||||||
|
|
||||||
internal fun DataInput.readUShort() = this.readShort().toUShort()
|
|
||||||
|
|
||||||
internal fun DataInput.readUInt() = this.readInt().toUInt()
|
|
||||||
|
|
||||||
internal fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt())
|
|
||||||
|
|
||||||
internal fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt())
|
|
||||||
|
|
||||||
internal fun DataInput.readUShortLE() = this.readUShort().toBigEndian()
|
|
||||||
|
|
||||||
internal fun DataInput.readUIntLE() = this.readUInt().toBigEndian()
|
|
||||||
|
|
||||||
internal fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian())
|
|
||||||
|
|
||||||
internal fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian())
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
package app.revanced.library.zip
|
|
||||||
|
|
||||||
import app.revanced.library.zip.structures.ZipEndRecord
|
|
||||||
import app.revanced.library.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<ZipEntry> = mutableListOf()
|
|
||||||
|
|
||||||
// Open file for writing if it doesn't exist (because the intention is to write) or is writable.
|
|
||||||
private val filePointer: RandomAccessFile =
|
|
||||||
RandomAccessFile(
|
|
||||||
file,
|
|
||||||
if (!file.exists() || file.canWrite()) "rw" else "r",
|
|
||||||
)
|
|
||||||
|
|
||||||
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<ZipEntry> {
|
|
||||||
filePointer.seek(endRecord.centralDirectoryStartOffset.toLong())
|
|
||||||
|
|
||||||
val numberOfEntries = endRecord.diskEntries.toInt()
|
|
||||||
|
|
||||||
return buildList(numberOfEntries) {
|
|
||||||
for (i in 1..numberOfEntries) {
|
|
||||||
add(
|
|
||||||
ZipEntry.fromCDE(filePointer).also
|
|
||||||
{
|
|
||||||
// for some reason the local extra field can be different from the central one
|
|
||||||
it.readLocalExtra(
|
|
||||||
filePointer.channel.map(
|
|
||||||
FileChannel.MapMode.READ_ONLY,
|
|
||||||
it.localHeaderOffset.toLong() + 28,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeCD() {
|
|
||||||
val 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object ApkZipFile {
|
|
||||||
private const val DEFAULT_ALIGNMENT = 4
|
|
||||||
private const val LIBRARY_ALIGNMENT = 4096
|
|
||||||
|
|
||||||
val apkZipEntryAlignment = { entry: ZipEntry ->
|
|
||||||
if (entry.compression.toUInt() != 0u) {
|
|
||||||
null
|
|
||||||
} else if (entry.fileName.endsWith(".so")) {
|
|
||||||
LIBRARY_ALIGNMENT
|
|
||||||
} else {
|
|
||||||
DEFAULT_ALIGNMENT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package app.revanced.library.zip.structures
|
|
||||||
|
|
||||||
import app.revanced.library.zip.putUInt
|
|
||||||
import app.revanced.library.zip.putUShort
|
|
||||||
import app.revanced.library.zip.readUIntLE
|
|
||||||
import app.revanced.library.zip.readUShortLE
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
internal 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
package app.revanced.library.zip.structures
|
|
||||||
|
|
||||||
import app.revanced.library.zip.*
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
class ZipEntry private constructor(
|
|
||||||
internal val version: UShort,
|
|
||||||
internal val versionNeeded: UShort,
|
|
||||||
internal val flags: UShort,
|
|
||||||
internal var compression: UShort,
|
|
||||||
internal val modificationTime: UShort,
|
|
||||||
internal val modificationDate: UShort,
|
|
||||||
internal var crc32: UInt,
|
|
||||||
internal var compressedSize: UInt,
|
|
||||||
internal var uncompressedSize: UInt,
|
|
||||||
internal val diskNumber: UShort,
|
|
||||||
internal val internalAttributes: UShort,
|
|
||||||
internal val externalAttributes: UInt,
|
|
||||||
internal var localHeaderOffset: UInt,
|
|
||||||
internal val fileName: String,
|
|
||||||
internal val extraField: ByteArray,
|
|
||||||
internal val fileComment: String,
|
|
||||||
internal var localExtraField: ByteArray = ByteArray(0), // separate for alignment
|
|
||||||
) {
|
|
||||||
internal val LFHSize: Int
|
|
||||||
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
|
||||||
|
|
||||||
internal val dataOffset: UInt
|
|
||||||
get() = localHeaderOffset + LFHSize.toUInt()
|
|
||||||
|
|
||||||
constructor(fileName: String) : this(
|
|
||||||
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),
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
internal const val CDE_HEADER_SIZE = 46
|
|
||||||
internal const val CDE_SIGNATURE = 0x02014b50u
|
|
||||||
|
|
||||||
internal const val LFH_HEADER_SIZE = 30
|
|
||||||
internal const val LFH_SIGNATURE = 0x04034b50u
|
|
||||||
|
|
||||||
internal 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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun readLocalExtra(buffer: ByteBuffer) {
|
|
||||||
buffer.order(ByteOrder.LITTLE_ENDIAN)
|
|
||||||
localExtraField = ByteArray(buffer.getUShort().toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
internal 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
|
|
||||||
}
|
|
||||||
|
|
||||||
internal 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user