mirror of
https://github.com/ReVanced/revanced-library.git
synced 2026-01-11 22:06:18 +00:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
9ff06a2f1b | ||
|
|
57e36ab5c1 | ||
|
|
8d934bde00 | ||
|
|
d1a7699256 | ||
|
|
e08d0ad8f3 | ||
|
|
4a14c5ccaa | ||
|
|
4bbe2fa150 | ||
|
|
938eac53b1 | ||
|
|
ab004b91f0 | ||
|
|
b637e0d7d7 | ||
|
|
9fda407441 | ||
|
|
44f7c13a77 | ||
|
|
ecff6fe0d3 | ||
|
|
3515e331ac | ||
|
|
a921c40306 | ||
|
|
b6e1de5eaf | ||
|
|
bf5780e2f7 | ||
|
|
99e90c02ed |
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
|
||||
22
.github/dependabot.yml
vendored
Normal file
22
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
|
||||
- package-ecosystem: npm
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
|
||||
- package-ecosystem: gradle
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
25
.github/workflows/build_pull_request.yml
vendored
Normal file
25
.github/workflows/build_pull_request.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Build pull request
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v1
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew build --no-daemon
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish ReVanced Library
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -6,10 +6,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -24,25 +20,30 @@ jobs:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Node modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v1
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Cleaning is necessary to avoid uploading two identical artifacts with different versions
|
||||
run: ./gradlew clean --no-daemon
|
||||
run: ./gradlew build clean
|
||||
|
||||
- name: Setup semantic-release
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
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
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Dispatch event to documentation repository
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: peter-evans/repository-dispatch@v2
|
||||
- uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
||||
repository: revanced/revanced-documentation
|
||||
@@ -32,6 +32,12 @@
|
||||
backmergeBranches: [{"from": "main", "to": "dev"}],
|
||||
clearWorkspace: true
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
successComment: false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
174
CHANGELOG.md
174
CHANGELOG.md
@@ -1,3 +1,177 @@
|
||||
# [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Map dependencies from `KClass` into `String` to fix serialization ([57e36ab](https://github.com/ReVanced/revanced-library/commit/57e36ab5c15a5fa7c50fb689ee43ad4eb9a4a515))
|
||||
|
||||
# [1.5.0](https://github.com/ReVanced/revanced-library/compare/v1.4.0...v1.5.0) (2023-12-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add JSON de- and serialization of patches ([ecff6fe](https://github.com/ReVanced/revanced-library/commit/ecff6fe0d3889d729a0badcfa28b89610bd27d48))
|
||||
* Improve mount reliability by unmounting existing mounts and killing running apps ([9fda407](https://github.com/ReVanced/revanced-library/commit/9fda40744173669c84b0c2599ae5ac5d39591798))
|
||||
|
||||
# [1.5.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.5.0-dev.1...v1.5.0-dev.2) (2023-12-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Improve mount reliability by unmounting existing mounts and killing running apps ([9fda407](https://github.com/ReVanced/revanced-library/commit/9fda40744173669c84b0c2599ae5ac5d39591798))
|
||||
|
||||
# [1.5.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.4.0...v1.5.0-dev.1) (2023-12-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add JSON de- and serialization of patches ([ecff6fe](https://github.com/ReVanced/revanced-library/commit/ecff6fe0d3889d729a0badcfa28b89610bd27d48))
|
||||
|
||||
# [1.4.0](https://github.com/ReVanced/revanced-library/compare/v1.3.0...v1.4.0) (2023-11-27)
|
||||
|
||||
|
||||
|
||||
@@ -6,34 +6,53 @@
|
||||
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/">
|
||||
<img height="24px" src="assets/revanced-logo/revanced-logo-round.svg" />
|
||||
<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">
|
||||
<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">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<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">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<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">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<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://twitter.com/revancedapp">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032018-6da37214-7474-4641-a1da-7af7db3a31cd.png" />
|
||||
<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">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</a>
|
||||
<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
|
||||
@@ -46,22 +65,23 @@ This document describes how to contribute to ReVanced Library.
|
||||
## 📖 Resources to help you get started
|
||||
|
||||
<!-- * 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
|
||||
|
||||
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.
|
||||
> Good motivation has to be provided for a request to be accepted.
|
||||
|
||||
## 🐞 Submitting a bug report
|
||||
|
||||
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
|
||||
|
||||
@@ -69,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
|
||||
and whether it is worth your time to implement it
|
||||
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
|
||||
3. Commit your changes.
|
||||
3. Commit your changes
|
||||
4. Submit a pull request to the `dev` branch of the repository and reference issues
|
||||
that your pull request closes in the description of your pull request
|
||||
5. Our team will review your pull request and provide feedback. Once your pull request is approved,
|
||||
it will be merged into the `dev` branch and will be included in the next release of ReVanced Library
|
||||
|
||||
❤️ Thank you for considering contributing to ReVanced Library,
|
||||
ReVanced
|
||||
❤️ Thank you for considering contributing to ReVanced Library,
|
||||
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,19 +1,19 @@
|
||||
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 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 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 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 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;
|
||||
@@ -25,10 +25,38 @@ public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
|
||||
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 copyAligned (Ljava/io/File;Ljava/io/File;Lapp/revanced/patcher/PatcherResult;)V
|
||||
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 {
|
||||
@@ -62,11 +90,51 @@ public final class app/revanced/library/Options$Patch$Option {
|
||||
|
||||
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
|
||||
@@ -121,23 +189,3 @@ public final class app/revanced/library/logging/Logger {
|
||||
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,7 +1,10 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "1.9.10"
|
||||
alias(libs.plugins.kotlin)
|
||||
alias(libs.plugins.binary.compatibility.validator)
|
||||
`maven-publish`
|
||||
signing
|
||||
}
|
||||
|
||||
group = "app.revanced"
|
||||
@@ -9,17 +12,26 @@ group = "app.revanced"
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
google()
|
||||
maven {
|
||||
// A repository must be speficied for some reason. "registry" is a dummy.
|
||||
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||
credentials {
|
||||
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
|
||||
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.revanced.patcher)
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(libs.jadb) // Updated fork
|
||||
implementation(libs.apksig)
|
||||
implementation(libs.bcpkix.jdk18on)
|
||||
implementation(libs.jadb) // Fork with Shell v2 support.
|
||||
implementation(libs.jackson.module.kotlin)
|
||||
implementation(libs.apkzlib)
|
||||
implementation(libs.apksig)
|
||||
implementation(libs.bcpkix.jdk15on)
|
||||
implementation(libs.guava)
|
||||
|
||||
testImplementation(libs.revanced.patcher)
|
||||
testImplementation(libs.kotlin.test)
|
||||
@@ -34,15 +46,32 @@ tasks {
|
||||
}
|
||||
}
|
||||
|
||||
kotlin { jvmToolchain(11) }
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
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>("gpr") {
|
||||
create<MavenPublication>("revanced-library-publication") {
|
||||
from(components["java"])
|
||||
|
||||
version = project.version.toString()
|
||||
@@ -76,3 +105,8 @@ publishing {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign(publishing.publications["revanced-library-publication"])
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
kotlin.code.style = official
|
||||
version = 1.4.0
|
||||
version = 2.3.0
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
[versions]
|
||||
apksig = "8.1.4"
|
||||
bcpkix-jdk18on = "1.76"
|
||||
jackson-module-kotlin = "2.14.3"
|
||||
jackson-module-kotlin = "2.15.0"
|
||||
jadb = "1.2.1"
|
||||
kotlin-reflect = "1.9.10"
|
||||
kotlin-test = "1.9.20"
|
||||
revanced-patcher = "19.0.0"
|
||||
binary-compatibility-validator = "0.13.2"
|
||||
kotlin = "1.9.22"
|
||||
revanced-patcher = "19.3.1"
|
||||
binary-compatibility-validator = "0.14.0"
|
||||
apkzlib = "8.3.0"
|
||||
bcpkix-jdk15on = "1.70"
|
||||
guava = "33.0.0-jre"
|
||||
apksig = "8.3.0"
|
||||
|
||||
[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" }
|
||||
jadb = { module = "app.revanced:jadb", version.ref = "jadb" }
|
||||
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
|
||||
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
||||
apkzlib = { module = "com.android.tools.build:apkzlib", version.ref = "apkzlib" }
|
||||
bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" }
|
||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
||||
|
||||
[plugins]
|
||||
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dist
|
||||
5433
package-lock.json
generated
5433
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-backmerge": "^3.2.1",
|
||||
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"gradle-semantic-release-plugin": "^1.8.0",
|
||||
"semantic-release": "^22.0.8"
|
||||
"gradle-semantic-release-plugin": "^1.9.1",
|
||||
"semantic-release": "^23.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package app.revanced.library
|
||||
|
||||
import com.android.apksig.ApkSigner
|
||||
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
|
||||
@@ -16,14 +19,13 @@ 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.
|
||||
* 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(app.revanced.library.ApkSigner::class.java.name)
|
||||
private val logger = Logger.getLogger(ApkSigner::class.java.name)
|
||||
|
||||
init {
|
||||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
|
||||
@@ -31,44 +33,101 @@ object ApkSigner {
|
||||
}
|
||||
}
|
||||
|
||||
private fun newKeyStoreInstance() = KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME)
|
||||
|
||||
/**
|
||||
* Create a new [PrivateKeyCertificatePair].
|
||||
* 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 the certificate is valid.
|
||||
* @return The created [PrivateKeyCertificatePair].
|
||||
* @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 = "ReVanced",
|
||||
validUntil: Date = Date(System.currentTimeMillis() + 356.days.inWholeMilliseconds * 24),
|
||||
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 keyPair = KeyPairGenerator.getInstance("RSA").apply {
|
||||
initialize(4096)
|
||||
}.generateKeyPair()
|
||||
|
||||
var serialNumber: BigInteger
|
||||
do serialNumber = BigInteger.valueOf(SecureRandom().nextLong())
|
||||
while (serialNumber < BigInteger.ZERO)
|
||||
val contentSigner = JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private)
|
||||
|
||||
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)),
|
||||
)
|
||||
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)
|
||||
}
|
||||
@@ -79,10 +138,15 @@ object ApkSigner {
|
||||
* @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 readKeyCertificatePair(
|
||||
fun readPrivateKeyCertificatePair(
|
||||
keyStore: KeyStore,
|
||||
keyStoreEntryAlias: String,
|
||||
keyStoreEntryPassword: String,
|
||||
@@ -90,7 +154,7 @@ object ApkSigner {
|
||||
logger.fine("Reading key and certificate pair from keystore entry $keyStoreEntryAlias")
|
||||
|
||||
if (!keyStore.containsAlias(keyStoreEntryAlias)) {
|
||||
throw IllegalArgumentException("Keystore does not contain alias $keyStoreEntryAlias")
|
||||
throw IllegalArgumentException("Keystore does not contain entry with alias $keyStoreEntryAlias")
|
||||
}
|
||||
|
||||
// Read the private key and certificate from the keystore.
|
||||
@@ -108,29 +172,48 @@ object ApkSigner {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new keystore with a new keypair.
|
||||
* Create a new [Signer].
|
||||
*
|
||||
* @param entries The entries to add to the keystore.
|
||||
* @return The created keystore.
|
||||
* @see KeyStoreEntry
|
||||
* @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 newKeyStore(entries: List<KeyStoreEntry>): KeyStore {
|
||||
logger.fine("Creating keystore")
|
||||
fun newApkSigner(
|
||||
signer: String,
|
||||
privateKeyCertificatePair: PrivateKeyCertificatePair,
|
||||
) = Signer(
|
||||
com.android.apksig.ApkSigner.Builder(
|
||||
listOf(
|
||||
SignerConfig.Builder(
|
||||
signer,
|
||||
privateKeyCertificatePair.privateKey,
|
||||
listOf(privateKeyCertificatePair.certificate),
|
||||
).build(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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].
|
||||
@@ -139,112 +222,79 @@ object ApkSigner {
|
||||
* @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: List<KeyStoreEntry>,
|
||||
keyStorePassword: String?,
|
||||
entries: Set<KeyStoreEntry>,
|
||||
) = newKeyStore(entries).store(
|
||||
keyStoreOutputStream,
|
||||
keyStorePassword.toCharArray(),
|
||||
) // Save the keystore.
|
||||
keyStorePassword?.toCharArray(),
|
||||
)
|
||||
|
||||
/**
|
||||
* 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].
|
||||
* Create a new [Signer].
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return The new [Signer].
|
||||
*
|
||||
* @see PrivateKeyCertificatePair
|
||||
* @see Signer
|
||||
*/
|
||||
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",
|
||||
@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 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 [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 [ApkSigner.Builder].
|
||||
* 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.
|
||||
* @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
|
||||
*
|
||||
* @return The new [Signer].
|
||||
*
|
||||
* @see KeyStore
|
||||
* @see Signer
|
||||
*/
|
||||
fun newApkSignerBuilder(
|
||||
@Deprecated("This method will be removed in the future.")
|
||||
fun newApkSigner(
|
||||
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()
|
||||
}
|
||||
) = newApkSigner("ReVanced", readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))
|
||||
|
||||
/**
|
||||
* An entry in a keystore.
|
||||
@@ -252,12 +302,13 @@ object ApkSigner {
|
||||
* @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(),
|
||||
val privateKeyCertificatePair: PrivateKeyCertificatePair,
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -270,4 +321,49 @@ object ApkSigner {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +1,240 @@
|
||||
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.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.io.path.deleteIfExists
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
/**
|
||||
* Utility functions for working with apks.
|
||||
* 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),
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a new apk from [apkFile] and [patchedEntriesSource] and writes it to [outputFile].
|
||||
* Applies the [PatcherResult] to the given [apkFile].
|
||||
*
|
||||
* @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.
|
||||
* 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 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(),
|
||||
)
|
||||
fun PatcherResult.applyTo(apkFile: File) {
|
||||
ZFile.openReadWrite(apkFile, zFileOptions).use { targetApkZFile ->
|
||||
dexFiles.forEach { dexFile ->
|
||||
targetApkZFile.add(dexFile.name, dexFile.stream)
|
||||
}
|
||||
|
||||
patchedEntriesSource.resourceFile?.let {
|
||||
file.copyEntriesFromFileAligned(
|
||||
ZipFile(it),
|
||||
ZipFile.apkZipEntryAlignment,
|
||||
)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Do not compress result.doNotCompress
|
||||
logger.info("Aligning APK")
|
||||
|
||||
// TODO: Fix copying resources that are not needed anymore.
|
||||
file.copyEntriesFromFileAligned(
|
||||
ZipFile(apkFile),
|
||||
ZipFile.apkZipEntryAlignment,
|
||||
)
|
||||
targetApkZFile.realign()
|
||||
|
||||
logger.fine("Writing changes")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the [apk] file and writes it to [output].
|
||||
* Creates a new private key and certificate pair and saves it to the keystore in [keyStoreDetails].
|
||||
*
|
||||
* @param apk The apk to sign.
|
||||
* @param output The apk to write the signed apk to.
|
||||
* @param signingOptions The options to use for signing.
|
||||
* @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.
|
||||
*/
|
||||
fun sign(
|
||||
apk: File,
|
||||
output: File,
|
||||
@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,
|
||||
) {
|
||||
// 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,
|
||||
): ApkSigner.PrivateKeyCertificatePair {
|
||||
val privateKeyCertificatePairDetails = PrivateKeyCertificatePairDetails(
|
||||
signingOptions.alias,
|
||||
PrivateKeyCertificatePairDetails().validUntil,
|
||||
)
|
||||
val keyStoreDetails = KeyStoreDetails(
|
||||
signingOptions.keyStore,
|
||||
signingOptions.keyStorePassword,
|
||||
signingOptions.alias,
|
||||
signingOptions.password,
|
||||
signingOptions.signer,
|
||||
signingOptions.signer,
|
||||
).signApk(apk, output)
|
||||
)
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -102,6 +244,7 @@ object ApkUtils {
|
||||
* @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?,
|
||||
@@ -109,4 +252,30 @@ object ApkUtils {
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,24 +3,22 @@
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.library.Options.Patch.Option
|
||||
import app.revanced.patcher.PatchClass
|
||||
import app.revanced.patcher.PatchSet
|
||||
import app.revanced.patcher.patch.options.PatchOptionException
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
private typealias PatchList = List<PatchClass>
|
||||
|
||||
@Suppress("unused")
|
||||
object Options {
|
||||
private val logger = Logger.getLogger(Options::class.java.name)
|
||||
|
||||
private var mapper = jacksonObjectMapper()
|
||||
private val mapper = jacksonObjectMapper()
|
||||
|
||||
/**
|
||||
* Serializes the options for the patches in the list.
|
||||
* Serializes the options for a set of patches.
|
||||
*
|
||||
* @param patches The list of patches to serialize.
|
||||
* @param patches The set of patches to serialize.
|
||||
* @param prettyPrint Whether to pretty print the JSON.
|
||||
* @return The JSON string containing the options.
|
||||
*/
|
||||
@@ -57,17 +55,16 @@ object Options {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the options for the patches in the list.
|
||||
* Deserializes the options to a set of patches.
|
||||
*
|
||||
* @param json The JSON string containing the options.
|
||||
* @return The list of [Patch]s.
|
||||
* @return A set of [Patch]s.
|
||||
* @see Patch
|
||||
* @see PatchList
|
||||
*/
|
||||
fun deserialize(json: String): Array<Patch> = mapper.readValue(json, Array<Patch>::class.java)
|
||||
|
||||
/**
|
||||
* Sets the options for the patches in the list.
|
||||
* Sets the options for a set of patches.
|
||||
*
|
||||
* @param json The JSON string containing the options.
|
||||
*/
|
||||
@@ -95,7 +92,7 @@ object Options {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options for the patches in the list.
|
||||
* Sets the options for a set of patches.
|
||||
*
|
||||
* @param file The file containing the JSON string containing the options.
|
||||
* @see setOptions
|
||||
|
||||
@@ -2,6 +2,11 @@ package app.revanced.library
|
||||
|
||||
import app.revanced.patcher.PatchSet
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.options.PatchOption
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
|
||||
typealias PackageName = String
|
||||
typealias Version = String
|
||||
@@ -15,35 +20,6 @@ typealias PackageNameMap = Map<PackageName, VersionMap>
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
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.
|
||||
*
|
||||
@@ -90,4 +66,104 @@ object PatchUtils {
|
||||
.associate { it.key to it.value } as VersionMap
|
||||
}
|
||||
}
|
||||
|
||||
object Json {
|
||||
private val mapper = jacksonObjectMapper()
|
||||
|
||||
/**
|
||||
* Serializes a set of [Patch]es to a JSON string and writes it to an output stream.
|
||||
*
|
||||
* @param patches The set of [Patch]es to serialize.
|
||||
* @param transform A function to transform the [Patch]es to [JsonPatch]es.
|
||||
* @param prettyPrint Whether to pretty print the JSON.
|
||||
* @param outputStream The output stream to write the JSON to.
|
||||
*/
|
||||
fun serialize(
|
||||
patches: PatchSet,
|
||||
transform: (Patch<*>) -> JsonPatch = { patch -> FullJsonPatch.fromPatch(patch) },
|
||||
prettyPrint: Boolean = false,
|
||||
outputStream: OutputStream,
|
||||
) {
|
||||
patches.map(transform).let { transformed ->
|
||||
if (prettyPrint) {
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(outputStream, transformed)
|
||||
} else {
|
||||
mapper.writeValue(outputStream, transformed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a JSON string to a set of [FullJsonPatch]es from an input stream.
|
||||
*
|
||||
* @param inputStream The input stream to read the JSON from.
|
||||
* @param jsonPatchElementClass The class of the [JsonPatch]es to deserialize.
|
||||
* @return A set of [JsonPatch]es.
|
||||
* @see FullJsonPatch
|
||||
*/
|
||||
fun <T : JsonPatch> deserialize(
|
||||
inputStream: InputStream,
|
||||
jsonPatchElementClass: Class<T>,
|
||||
): Set<T> =
|
||||
mapper.readValue(
|
||||
inputStream,
|
||||
mapper.typeFactory.constructCollectionType(Set::class.java, jsonPatchElementClass),
|
||||
)
|
||||
|
||||
interface JsonPatch
|
||||
|
||||
/**
|
||||
* A JSON representation of a [Patch].
|
||||
* @see Patch
|
||||
*/
|
||||
class FullJsonPatch internal constructor(
|
||||
val name: String?,
|
||||
val description: String?,
|
||||
val compatiblePackages: Set<Patch.CompatiblePackage>?,
|
||||
val dependencies: Set<String>?,
|
||||
val use: Boolean,
|
||||
var requiresIntegrations: Boolean,
|
||||
val options: Map<String, FullJsonPatchOption<*>>,
|
||||
) : JsonPatch {
|
||||
companion object {
|
||||
fun fromPatch(patch: Patch<*>) =
|
||||
FullJsonPatch(
|
||||
patch.name,
|
||||
patch.description,
|
||||
patch.compatiblePackages,
|
||||
buildSet { patch.dependencies?.forEach { add(it.jvmName) } },
|
||||
patch.use,
|
||||
patch.requiresIntegrations,
|
||||
patch.options.mapValues { FullJsonPatchOption.fromPatchOption(it.value) },
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A JSON representation of a [PatchOption].
|
||||
* @see PatchOption
|
||||
*/
|
||||
class FullJsonPatchOption<T> internal constructor(
|
||||
val key: String,
|
||||
val default: T?,
|
||||
val values: Map<String, T?>?,
|
||||
val title: String?,
|
||||
val description: String?,
|
||||
val required: Boolean,
|
||||
val valueType: String,
|
||||
) {
|
||||
companion object {
|
||||
fun fromPatchOption(option: PatchOption<*>) =
|
||||
FullJsonPatchOption(
|
||||
option.key,
|
||||
option.default,
|
||||
option.values,
|
||||
option.title,
|
||||
option.description,
|
||||
option.required,
|
||||
option.valueType,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ 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
|
||||
import app.revanced.library.adb.Constants.INSTALL_MOUNT_SCRIPT
|
||||
import app.revanced.library.adb.Constants.INSTALL_PATCHED_APK
|
||||
import app.revanced.library.adb.Constants.MOUNT_PATH
|
||||
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
|
||||
@@ -104,9 +105,8 @@ sealed class AdbManager private constructor(deviceSerial: String?) {
|
||||
|
||||
device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement(packageName))
|
||||
|
||||
device.run(INSTALL_MOUNT, packageName).waitFor()
|
||||
device.run(UMOUNT, packageName).waitFor() // Sanity check.
|
||||
device.run(MOUNT_PATH, packageName).waitFor()
|
||||
device.run(INSTALL_MOUNT_SCRIPT, packageName).waitFor()
|
||||
device.run(MOUNT_SCRIPT_PATH, packageName).waitFor()
|
||||
device.run(RESTART, packageName)
|
||||
device.run(DELETE, TMP_PATH)
|
||||
|
||||
@@ -118,9 +118,9 @@ sealed class AdbManager private constructor(deviceSerial: String?) {
|
||||
|
||||
device.run(UMOUNT, packageName)
|
||||
device.run(DELETE.applyReplacement(PATCHED_APK_PATH), packageName)
|
||||
device.run(DELETE, MOUNT_PATH.applyReplacement(packageName))
|
||||
device.run(DELETE, MOUNT_SCRIPT_PATH.applyReplacement(packageName))
|
||||
device.run(DELETE, TMP_PATH)
|
||||
device.run(RESTART, packageName)
|
||||
device.run(KILL, packageName)
|
||||
|
||||
super.uninstall(packageName)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ internal object Constants {
|
||||
internal const val TMP_PATH = "/data/local/tmp/revanced.tmp"
|
||||
internal const val INSTALLATION_PATH = "/data/adb/revanced/"
|
||||
internal const val PATCHED_APK_PATH = "$INSTALLATION_PATH$PLACEHOLDER.apk"
|
||||
internal const val MOUNT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
|
||||
internal const val 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 =
|
||||
@@ -23,21 +24,31 @@ internal object Constants {
|
||||
internal const val UMOUNT =
|
||||
"grep $PLACEHOLDER /proc/mounts | while read -r line; do echo ${'$'}line | cut -d ' ' -f 2 | sed 's/apk.*/apk/' | xargs -r umount -l; done"
|
||||
|
||||
internal const val INSTALL_MOUNT = "mv $TMP_PATH $MOUNT_PATH && chmod +x $MOUNT_PATH"
|
||||
internal 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"
|
||||
|
||||
# Use Magisk mirror, if possible.
|
||||
if command -v magisk &> /dev/null; then
|
||||
MIRROR="${'$'}(magisk --path)/.magisk/mirror"
|
||||
fi
|
||||
|
||||
# Wait for the system to boot.
|
||||
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
|
||||
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 currently 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
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ internal object PatchOptionsTest {
|
||||
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":\"test\"},{\"key\":\"key2\",\"value\":false}]}]"
|
||||
|
||||
@Patch("PatchOptionsTestPatch")
|
||||
object PatchOptionsTestPatch : BytecodePatch() {
|
||||
object PatchOptionsTestPatch : BytecodePatch(emptySet()) {
|
||||
var option1 by stringPatchOption("key1", null, null, "title1", "description1")
|
||||
var option2 by booleanPatchOption("key2", true, null, "title2", "description2")
|
||||
|
||||
|
||||
@@ -4,19 +4,24 @@ import app.revanced.patcher.PatchSet
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.Patch
|
||||
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.stringPatchOption
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal object PatchUtilsTest {
|
||||
private val patches =
|
||||
arrayOf(
|
||||
newPatch("some.package", setOf("a")),
|
||||
newPatch("some.package", setOf("a")) { stringPatchOption("string", "value") },
|
||||
newPatch("some.package", setOf("a", "b"), use = false),
|
||||
newPatch("some.package", setOf("a", "b", "c"), use = false),
|
||||
newPatch("some.other.package", setOf("b"), use = false),
|
||||
newPatch("some.other.package", setOf("b", "c")),
|
||||
newPatch("some.other.package", setOf("b", "c")) { booleanPatchOption("bool", true) },
|
||||
newPatch("some.other.package", setOf("b", "c", "d")),
|
||||
newPatch("some.other.other.package"),
|
||||
newPatch("some.other.other.package") { intArrayPatchOption("intArray", arrayOf(1, 2, 3)) },
|
||||
newPatch("some.other.other.package", setOf("a")),
|
||||
newPatch("some.other.other.package", setOf("b")),
|
||||
newPatch("some.other.other.other.package", use = false),
|
||||
@@ -56,12 +61,12 @@ internal object PatchUtilsTest {
|
||||
fun assertEqualsExpected(compatiblePackageNames: Set<String>?) =
|
||||
assertEqualsVersions(
|
||||
expected =
|
||||
mapOf(
|
||||
"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.other.package" to linkedMapOf("a" to 1, "b" to 1),
|
||||
"some.other.other.other.package" to linkedMapOf(),
|
||||
),
|
||||
mapOf(
|
||||
"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.other.package" to linkedMapOf("a" to 1, "b" to 1),
|
||||
"some.other.other.other.package" to linkedMapOf(),
|
||||
),
|
||||
patches,
|
||||
compatiblePackageNames,
|
||||
countUnusedPatches = true,
|
||||
@@ -69,12 +74,12 @@ internal object PatchUtilsTest {
|
||||
|
||||
assertEqualsExpected(
|
||||
compatiblePackageNames =
|
||||
setOf(
|
||||
"some.package",
|
||||
"some.other.package",
|
||||
"some.other.other.package",
|
||||
"some.other.other.other.package",
|
||||
),
|
||||
setOf(
|
||||
"some.package",
|
||||
"some.other.package",
|
||||
"some.other.other.package",
|
||||
"some.other.other.other.package",
|
||||
),
|
||||
)
|
||||
|
||||
assertEqualsExpected(
|
||||
@@ -86,19 +91,19 @@ internal object PatchUtilsTest {
|
||||
fun `common versions correctly ordered for each package without counting unused patches`() {
|
||||
assertEqualsVersions(
|
||||
expected =
|
||||
mapOf(
|
||||
"some.package" to linkedMapOf("a" 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),
|
||||
),
|
||||
mapOf(
|
||||
"some.package" to linkedMapOf("a" 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),
|
||||
),
|
||||
patches,
|
||||
compatiblePackageNames =
|
||||
setOf(
|
||||
"some.package",
|
||||
"some.other.package",
|
||||
"some.other.other.package",
|
||||
"some.other.other.other.package",
|
||||
),
|
||||
setOf(
|
||||
"some.package",
|
||||
"some.other.package",
|
||||
"some.other.other.package",
|
||||
"some.other.other.other.package",
|
||||
),
|
||||
countUnusedPatches = false,
|
||||
)
|
||||
}
|
||||
@@ -136,6 +141,20 @@ internal object PatchUtilsTest {
|
||||
assertEqualsVersion(null, patches, "other.package")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `serializes to and deserializes from JSON string correctly`() {
|
||||
val out = ByteArrayOutputStream()
|
||||
PatchUtils.Json.serialize(patches, outputStream = out)
|
||||
|
||||
val deserialized =
|
||||
PatchUtils.Json.deserialize(
|
||||
ByteArrayInputStream(out.toByteArray()),
|
||||
PatchUtils.Json.FullJsonPatch::class.java,
|
||||
)
|
||||
|
||||
assert(patches.size == deserialized.size)
|
||||
}
|
||||
|
||||
private fun assertEqualsVersions(
|
||||
expected: PackageNameMap,
|
||||
patches: PatchSet,
|
||||
@@ -151,14 +170,6 @@ internal object PatchUtilsTest {
|
||||
patches: PatchSet,
|
||||
compatiblePackageName: String,
|
||||
) {
|
||||
// Test both the deprecated and the new method.
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
assertEquals(
|
||||
expected,
|
||||
PatchUtils.getMostCommonCompatibleVersion(patches, compatiblePackageName),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
PatchUtils.getMostCommonCompatibleVersions(patches, setOf(compatiblePackageName))
|
||||
@@ -170,19 +181,14 @@ internal object PatchUtilsTest {
|
||||
packageName: String,
|
||||
versions: Set<String>? = null,
|
||||
use: Boolean = true,
|
||||
) = object : BytecodePatch() {
|
||||
options: Patch<*>.() -> Unit = {},
|
||||
) = object : BytecodePatch(
|
||||
name = "test",
|
||||
compatiblePackages = setOf(CompatiblePackage(packageName, versions?.toSet())),
|
||||
use = use,
|
||||
) {
|
||||
init {
|
||||
// Set the compatible packages field to the supplied package name and versions reflectively,
|
||||
// because the setter is private but needed for testing.
|
||||
val compatiblePackagesField = Patch::class.java.getDeclaredField("compatiblePackages")
|
||||
|
||||
compatiblePackagesField.isAccessible = true
|
||||
compatiblePackagesField.set(this, setOf(CompatiblePackage(packageName, versions?.toSet())))
|
||||
|
||||
val useField = Patch::class.java.getDeclaredField("use")
|
||||
|
||||
useField.isAccessible = true
|
||||
useField.set(this, use)
|
||||
options()
|
||||
}
|
||||
|
||||
override fun execute(context: BytecodeContext) {}
|
||||
|
||||
Reference in New Issue
Block a user