Compare commits

...

80 Commits

Author SHA1 Message Date
dependabot[bot]
c71fc671ae build(deps): bump android from 8.5.2 to 8.13.2
Bumps `android` from 8.5.2 to 8.13.2.

Updates `com.android.tools.build:apkzlib` from 8.5.2 to 8.13.2

Updates `com.android.tools.build:apksig` from 8.5.2 to 8.13.2

Updates `com.android.library` from 8.5.2 to 8.13.2

---
updated-dependencies:
- dependency-name: com.android.tools.build:apkzlib
  dependency-version: 8.13.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.android.tools.build:apksig
  dependency-version: 8.13.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.android.library
  dependency-version: 8.13.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-01 09:10:23 +00:00
Pun Butrach
0673307204 docs: Use American spelling (#96) 2025-12-14 16:40:34 +01:00
semantic-release-bot
5df2bb81bf chore: Release v3.2.0-dev.1 [skip ci]
# [3.2.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.1.1-dev.1...v3.2.0-dev.1) (2025-05-27)

### Features

* Request the update ownership enforcement ([#71](https://github.com/ReVanced/revanced-library/issues/71)) ([be0f6bf](be0f6bf247))
2025-05-27 13:37:00 +00:00
Brosssh
be0f6bf247 feat: Request the update ownership enforcement (#71)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-05-27 15:31:09 +02:00
semantic-release-bot
9d060c188f chore: Release v3.1.1-dev.1 [skip ci]
## [3.1.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.1.0...v3.1.1-dev.1) (2025-05-02)

### Bug Fixes

* Interpret package name as a string instead of Regex when using grep  ([#68](https://github.com/ReVanced/revanced-library/issues/68)) ([254f36d](254f36d03c))
2025-05-02 13:07:51 +00:00
laur89
254f36d03c fix: Interpret package name as a string instead of Regex when using grep (#68) 2025-05-02 15:04:34 +02:00
semantic-release-bot
4065c87d5f chore: Release v3.1.0 [skip ci]
# [3.1.0](https://github.com/ReVanced/revanced-library/compare/v3.0.2...v3.1.0) (2024-11-27)

### Bug Fixes

* Detect if app is installed by fixing inversion ([649f06b](649f06b19d))

### Features

* Warn when option could not be set because the option does not exist ([7ec6504](7ec6504619))
2024-11-27 21:47:55 +00:00
oSumAtrIX
be8d7bf643 chore: Merge branch dev to main (#65) 2024-11-27 22:45:14 +01:00
semantic-release-bot
2328902b6b chore: Release v3.1.0-dev.1 [skip ci]
# [3.1.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.3-dev.1...v3.1.0-dev.1) (2024-11-25)

### Features

* Warn when option could not be set because the option does not exist ([7ec6504](7ec6504619))
2024-11-25 21:30:09 +00:00
oSumAtrIX
7ec6504619 feat: Warn when option could not be set because the option does not exist 2024-11-25 22:23:31 +01:00
semantic-release-bot
e7a98b5795 chore: Release v3.0.3-dev.1 [skip ci]
## [3.0.3-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.2...v3.0.3-dev.1) (2024-11-11)

### Bug Fixes

* Detect if app is installed by fixing inversion ([649f06b](649f06b19d))
2024-11-11 22:32:39 +00:00
oSumAtrIX
649f06b19d fix: Detect if app is installed by fixing inversion 2024-11-11 23:29:43 +01:00
semantic-release-bot
cace51700a chore: Release v3.0.2 [skip ci]
## [3.0.2](https://github.com/ReVanced/revanced-library/compare/v3.0.1...v3.0.2) (2024-11-05)
2024-11-05 18:44:21 +00:00
oSumAtrIX
91cefc8598 chore: Merge branch dev to main (#63) 2024-11-05 19:41:48 +01:00
semantic-release-bot
735c1e39cd chore: Release v3.0.2-dev.1 [skip ci]
## [3.0.2-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.1...v3.0.2-dev.1) (2024-11-05)
2024-11-05 18:39:39 +00:00
oSumAtrIX
84cc315541 build(Needs bump): Bump dependencies 2024-11-05 19:37:09 +01:00
semantic-release-bot
4fe9304570 chore: Release v3.0.1 [skip ci]
## [3.0.1](https://github.com/ReVanced/revanced-library/compare/v3.0.0...v3.0.1) (2024-10-13)

### Bug Fixes

* Serialize compatible packages as a map instead of a set of pairs. ([737e272](737e272481))
2024-10-13 01:55:25 +00:00
oSumAtrIX
8bb41be8fc chore: Merge branch dev to main (#62) 2024-10-13 03:52:36 +02:00
semantic-release-bot
4b8ac026c3 chore: Release v3.0.1-dev.3 [skip ci]
## [3.0.1-dev.3](https://github.com/ReVanced/revanced-library/compare/v3.0.1-dev.2...v3.0.1-dev.3) (2024-10-06)
2024-10-06 01:27:57 +00:00
oSumAtrIX
557b6035f8 build(Needs bump): Bump dependencies 2024-10-06 03:25:03 +02:00
oSumAtrIX
bfc5394b4e refactor: Indent code 2024-10-01 17:30:14 +02:00
semantic-release-bot
5b1cf1f190 chore: Release v3.0.1-dev.2 [skip ci]
## [3.0.1-dev.2](https://github.com/ReVanced/revanced-library/compare/v3.0.1-dev.1...v3.0.1-dev.2) (2024-10-01)
2024-10-01 15:09:29 +00:00
oSumAtrIX
dd5c37ddec ci: Use permissions and regular GitHub token instead of PAT 2024-10-01 17:07:05 +02:00
oSumAtrIX
9adccc04dd build(Needs bump): Update dependencies 2024-09-30 23:21:45 +02:00
oSumAtrIX
ed94d29461 ci: Adjust release commit message 2024-09-30 22:34:24 +02:00
semantic-release-bot
efc72cdc55 chore(release): 3.0.1-dev.1 [skip ci]
## [3.0.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.0...v3.0.1-dev.1) (2024-08-16)

### Bug Fixes

* Serialize compatible packages as a map instead of a set of pairs. ([737e272](737e272481))
2024-08-16 22:39:26 +00:00
oSumAtrIX
737e272481 fix: Serialize compatible packages as a map instead of a set of pairs. 2024-08-17 00:36:43 +02:00
semantic-release-bot
92ff93d6e6 chore(release): 3.0.0 [skip ci]
# [3.0.0](https://github.com/ReVanced/revanced-library/compare/v2.3.0...v3.0.0) (2024-08-06)

### Bug Fixes

* Make functions internal which are supposed to be internal ([893d22d](893d22d793))

### Build System

* Refactor to DSL to bump ReVanced Patcher ([7f5d6da](7f5d6dad7b))

### Features

* Add local Android installer ([#25](https://github.com/ReVanced/revanced-library/issues/25)) ([43d655a](43d655aea5))
* Remove deprecated functions ([b9bf3bc](b9bf3bc882))

### BREAKING CHANGES

* Some functions have been removed.
* Some functions are not available anymore.
* The signature of some functions has changed.
2024-08-06 22:53:49 +00:00
oSumAtrIX
d56126aa58 chore: Merge branch dev to main (#60) 2024-08-07 00:51:37 +02:00
semantic-release-bot
079776f241 chore(release): 3.0.0-dev.1 [skip ci]
# [3.0.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.4.0-dev.1...v3.0.0-dev.1) (2024-08-06)

### Bug Fixes

* Make functions internal which are supposed to be internal ([893d22d](893d22d793))

### Build System

* Refactor to DSL to bump ReVanced Patcher ([7f5d6da](7f5d6dad7b))

### Features

* Remove deprecated functions ([b9bf3bc](b9bf3bc882))

### BREAKING CHANGES

* Some functions have been removed.
* Some functions are not available anymore.
* The signature of some functions has changed.
2024-08-06 22:48:55 +00:00
oSumAtrIX
7a554a85a8 build: Fix duplicate publication
KMP already creates one.
2024-08-07 00:46:26 +02:00
oSumAtrIX
b9bf3bc882 feat: Remove deprecated functions
BREAKING CHANGE: Some functions have been removed.
2024-08-06 17:55:35 +02:00
oSumAtrIX
27b3359d66 refactor: Move functions to top level 2024-08-06 17:55:35 +02:00
oSumAtrIX
893d22d793 fix: Make functions internal which are supposed to be internal
BREAKING CHANGE: Some functions are not available anymore.
2024-08-06 17:55:35 +02:00
oSumAtrIX
7f5d6dad7b build: Refactor to DSL to bump ReVanced Patcher
BREAKING CHANGE:  The signature of some functions has changed.
2024-08-06 17:55:35 +02:00
oSumAtrIX
8aca650ebc ci: Correct usage of repository variable 2024-07-13 00:45:18 +02:00
oSumAtrIX
db59d2cd0b docs: Improve issue templates 2024-05-26 00:43:37 +02:00
oSumAtrIX
fe3e1c9dc8 chore: Remove unnecessary file [skip ci] 2024-04-07 18:34:00 +02:00
semantic-release-bot
825e6490fe chore(release): 2.4.0-dev.1 [skip ci]
# [2.4.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.3.0...v2.4.0-dev.1) (2024-04-07)

### Features

* Add local Android installer ([#25](https://github.com/ReVanced/revanced-library/issues/25)) ([43d655a](43d655aea5))
2024-04-07 16:33:09 +00:00
oSumAtrIX
43d655aea5 feat: Add local Android installer (#25) 2024-04-07 18:30:43 +02:00
semantic-release-bot
8d0e4b2470 chore(release): 2.3.0 [skip ci]
# [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](2a30845f61))

### Features

* Add utility function around key certificate pairs ([2df3484](2df3484b68))
* Improve exception message ([b15efa4](b15efa41f8))
* Simplify signing utility API ([4c6a636](4c6a6360cf))
2024-03-14 12:16:21 +00:00
oSumAtrIX
4486c59674 chore: Merge branch dev to main (#38) 2024-03-14 13:14:43 +01:00
semantic-release-bot
8e4d5765e1 chore(release): 2.3.0-dev.3 [skip ci]
# [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](b15efa41f8))
2024-03-14 11:51:48 +00:00
oSumAtrIX
b15efa41f8 feat: Improve exception message 2024-03-14 12:50:20 +01:00
semantic-release-bot
af0aba4a8e chore(release): 2.3.0-dev.2 [skip ci]
# [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](4c6a6360cf))
2024-03-14 11:40:37 +00:00
oSumAtrIX
4c6a6360cf feat: Simplify signing utility API 2024-03-14 12:39:01 +01:00
oSumAtrIX
80e35270c4 build: Publish sources 2024-03-14 12:21:47 +01:00
semantic-release-bot
13b6a846d7 chore(release): 2.3.0-dev.1 [skip ci]
# [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](2df3484b68))
2024-03-13 22:06:34 +00:00
oSumAtrIX
2df3484b68 feat: Add utility function around key certificate pairs 2024-03-13 23:04:35 +01:00
semantic-release-bot
f2959b610a chore(release): 2.2.2-dev.1 [skip ci]
## [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](2a30845f61))
2024-03-12 14:46:49 +00:00
oSumAtrIX
2a30845f61 fix: Support mounting even when Magisk is not installed 2024-03-12 15:44:59 +01:00
semantic-release-bot
1b4d87e563 chore(release): 2.2.1 [skip ci]
## [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](249372c31f))
2024-03-09 08:49:38 +00:00
oSumAtrIX
7f7dfdd5b5 chore: Merge branch dev to main (#37) 2024-03-09 09:48:22 +01:00
semantic-release-bot
56773fa3d3 chore(release): 2.2.1-dev.1 [skip ci]
## [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](249372c31f))
2024-03-09 08:45:38 +00:00
oSumAtrIX
249372c31f fix: Do not specify a provider to automatically select an available one 2024-03-09 09:44:10 +01:00
semantic-release-bot
e7bed8565e chore(release): 2.2.0 [skip ci]
# [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](51109c4768))
* Sign APKs using `apksig` ([f59ecbc](f59ecbccd1))

### Features

* Increase default expiration date of certificate ([f2bd3f5](f2bd3f5eee))
2024-03-09 03:36:08 +00:00
oSumAtrIX
4a24e2e92d chore: Merge branch dev to main (#35) 2024-03-09 04:34:48 +01:00
semantic-release-bot
fca8a3f4c0 chore(release): 2.2.0-dev.1 [skip ci]
# [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](51109c4768))
* Sign APKs using `apksig` ([f59ecbc](f59ecbccd1))

### Features

* Increase default expiration date of certificate ([f2bd3f5](f2bd3f5eee))
2024-03-09 03:31:10 +00:00
oSumAtrIX
f59ecbccd1 fix: Sign APKs using apksig
Previously, the signing extension from apkzlib was used incorrectly. The extension is meant to be added to a ZFile whereas on changes the extension would be used to sign. Instead the extension was added to a newly created ZFile, and without any changes, closed again, leading to no APK being signed. It turns out to be impractical to use the signing extension as we do not write an entire APK ZFile so that the signing extension can consider every file inside the ZFile, instead we just merge the patcher result to an existing ZFile. Instead use `apksig` after applying the patcher result to the ZFile which signs everything correctly.
2024-03-09 04:29:41 +01:00
oSumAtrIX
c92be32607 refactor: Simplify code 2024-03-09 03:25:11 +01:00
oSumAtrIX
51109c4768 fix: Make property private 2024-03-09 03:25:10 +01:00
oSumAtrIX
f2bd3f5eee feat: Increase default expiration date of certificate 2024-03-08 02:30:15 +01:00
oSumAtrIX
cc5ee29d14 build: Set target bytecode level to JVM 11 2024-03-04 19:16:09 +01:00
semantic-release-bot
c4bad9a653 chore(release): 2.1.0 [skip ci]
# [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](41805fcb0b))

### Features

* Mention APK file name when logging aligning ([244ebc2](244ebc2186))
2024-03-04 14:39:47 +00:00
oSumAtrIX
3f713cf76b chore: Merge branch dev to main (#32) 2024-03-04 15:38:10 +01:00
oSumAtrIX
dfe48a24bf ci: Update action 2024-03-04 15:37:06 +01:00
oSumAtrIX
132ad13670 build: Bump dependencies 2024-03-04 15:37:06 +01:00
semantic-release-bot
1670ceff9f chore(release): 2.1.0-dev.2 [skip ci]
# [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](41805fcb0b))
2024-03-04 14:33:02 +00:00
oSumAtrIX
41805fcb0b fix: Use BKS instead of default signing provider to fix backwards compatibility 2024-03-04 15:31:26 +01:00
oSumAtrIX
0e9939f70d docs: Fix spelling mistakes 2024-02-26 04:37:58 +01:00
oSumAtrIX
f28a7ddeec docs: Fix broken links 2024-02-26 04:37:46 +01:00
oSumAtrIX
3e574e723c docs: Add readme 2024-02-25 04:01:25 +01:00
oSumAtrIX
a73ca721f3 chore: Rename issue templates 2024-02-25 03:37:52 +01:00
oSumAtrIX
393d74b7d9 ci: Rename workflow file 2024-02-25 03:14:02 +01:00
oSumAtrIX
51d6e9976c ci: Fix indentation in workflow 2024-02-24 01:14:44 +01:00
oSumAtrIX
24cecb1e57 docs: Format markdown code 2024-02-23 03:03:50 +01:00
oSumAtrIX
d4938c57e8 ci: Split release into a separate PR build workflow
Because the release workflow already runs on dev and main, it is not necessary to also trigger it for PRs.
2024-02-23 02:26:06 +01:00
semantic-release-bot
1526f98f53 chore(release): 2.1.0-dev.1 [skip ci]
# [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](244ebc2186))
2024-02-15 03:46:55 +00:00
oSumAtrIX
244ebc2186 feat: Mention APK file name when logging aligning 2024-02-15 02:54:31 +01:00
oSumAtrIX
486a7acff2 ci: Create a GitHub release with changelogs 2024-02-15 02:29:58 +01:00
60 changed files with 4286 additions and 2363 deletions

View File

@@ -70,7 +70,8 @@ body:
Before creating a new bug report, please keep the following in mind:
- **Do not submit a duplicate bug report**: You can review existing bug reports [here](https://github.com/ReVanced/revanced-library/labels/Bug%20report).
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-library/issues?q=label%3A%22Bug+report%22).
- **Review the contribution guidelines**: Make sure your bug request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-library/blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
attributes:
@@ -100,7 +101,7 @@ body:
label: Acknowledgements
description: Your bug report will be closed if you don't follow the checklist below.
options:
- label: This issue is not a duplicate of an existing bug report.
- label: I have checked all open and closed bug reports and this is not a duplicate.
required: true
- label: I have chosen an appropriate title.
required: true

View File

@@ -70,8 +70,8 @@ body:
Before creating a new feature request, please keep the following in mind:
- **Do not submit a duplicate feature request**: You can review existing feature requests [here](https://github.com/ReVanced/revanced-library/labels/Feature%20request).
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-library/blob/main/CONTRIBUTING.md).
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-library/issues?q=label%3A%22Feature+request%22).
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-library/blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
attributes:
@@ -98,7 +98,7 @@ body:
label: Acknowledgements
description: Your feature request will be closed if you don't follow the checklist below.
options:
- label: This issue is not a duplicate of an existing feature request.
- label: I have checked all open and closed feature requests and this is not a duplicate.
required: true
- label: I have chosen an appropriate title.
required: true

View File

@@ -0,0 +1,28 @@
name: Build pull request
on:
workflow_dispatch:
pull_request:
branches:
- dev
jobs:
release:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
- name: Setup Java
run: echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build --no-daemon

View File

@@ -6,14 +6,13 @@ on:
branches:
- main
- dev
pull_request:
branches:
- main
- dev
jobs:
release:
name: Release
permissions:
contents: write
packages: write
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -27,6 +26,9 @@ jobs:
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
- name: Setup Java
run: echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -46,9 +48,9 @@ jobs:
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
fingerprint: ${{ env.GPG_FINGERPRINT }}
fingerprint: ${{ vars.GPG_FINGERPRINT }}
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm exec semantic-release

View File

@@ -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

123
.gitignore vendored
View File

@@ -1,118 +1,11 @@
### Java template
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
.idea/artifacts
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/modules.xml
.idea/*.iml
.idea/modules
*.iml
*.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
.idea
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Gradle template
.gradle
**/build/
!src/**/build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
# Dependency directories
.idea
.DS_Store
build
captures
.externalNativeBuild
.cxx
local.properties
xcuserdata
node_modules/

View File

@@ -23,7 +23,8 @@
"assets": [
"CHANGELOG.md",
"gradle.properties"
]
],
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[
@@ -32,6 +33,12 @@
backmergeBranches: [{"from": "main", "to": "dev"}],
clearWorkspace: true
}
],
[
"@semantic-release/github",
{
successComment: false
}
]
]
}

View File

@@ -1,3 +1,229 @@
# [3.2.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.1.1-dev.1...v3.2.0-dev.1) (2025-05-27)
### Features
* Request the update ownership enforcement ([#71](https://github.com/ReVanced/revanced-library/issues/71)) ([be0f6bf](https://github.com/ReVanced/revanced-library/commit/be0f6bf247461d16fbf649a9f2dc6facbb5b0c93))
## [3.1.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.1.0...v3.1.1-dev.1) (2025-05-02)
### Bug Fixes
* Interpret package name as a string instead of Regex when using grep ([#68](https://github.com/ReVanced/revanced-library/issues/68)) ([254f36d](https://github.com/ReVanced/revanced-library/commit/254f36d03cc8fd3e2508a5e8f69bb5c8e1eb9775))
# [3.1.0](https://github.com/ReVanced/revanced-library/compare/v3.0.2...v3.1.0) (2024-11-27)
### Bug Fixes
* Detect if app is installed by fixing inversion ([649f06b](https://github.com/ReVanced/revanced-library/commit/649f06b19dd4d2a3f3216a0b3ea947b9fe0d475f))
### Features
* Warn when option could not be set because the option does not exist ([7ec6504](https://github.com/ReVanced/revanced-library/commit/7ec650461935faf2a8fbb667db3cf137157b70b5))
# [3.1.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.3-dev.1...v3.1.0-dev.1) (2024-11-25)
### Features
* Warn when option could not be set because the option does not exist ([7ec6504](https://github.com/ReVanced/revanced-library/commit/7ec650461935faf2a8fbb667db3cf137157b70b5))
## [3.0.3-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.2...v3.0.3-dev.1) (2024-11-11)
### Bug Fixes
* Detect if app is installed by fixing inversion ([649f06b](https://github.com/ReVanced/revanced-library/commit/649f06b19dd4d2a3f3216a0b3ea947b9fe0d475f))
## [3.0.2](https://github.com/ReVanced/revanced-library/compare/v3.0.1...v3.0.2) (2024-11-05)
## [3.0.2-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.1...v3.0.2-dev.1) (2024-11-05)
## [3.0.1](https://github.com/ReVanced/revanced-library/compare/v3.0.0...v3.0.1) (2024-10-13)
### Bug Fixes
* Serialize compatible packages as a map instead of a set of pairs. ([737e272](https://github.com/ReVanced/revanced-library/commit/737e272481fe3b0b4c89233d139b5e657a0c1de4))
## [3.0.1-dev.3](https://github.com/ReVanced/revanced-library/compare/v3.0.1-dev.2...v3.0.1-dev.3) (2024-10-06)
## [3.0.1-dev.2](https://github.com/ReVanced/revanced-library/compare/v3.0.1-dev.1...v3.0.1-dev.2) (2024-10-01)
## [3.0.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.0...v3.0.1-dev.1) (2024-08-16)
### Bug Fixes
* Serialize compatible packages as a map instead of a set of pairs. ([737e272](https://github.com/ReVanced/revanced-library/commit/737e272481fe3b0b4c89233d139b5e657a0c1de4))
# [3.0.0](https://github.com/ReVanced/revanced-library/compare/v2.3.0...v3.0.0) (2024-08-06)
### Bug Fixes
* Make functions internal which are supposed to be internal ([893d22d](https://github.com/ReVanced/revanced-library/commit/893d22d7938fa1c7544795635ed2ffacdd0cbf0d))
### Build System
* Refactor to DSL to bump ReVanced Patcher ([7f5d6da](https://github.com/ReVanced/revanced-library/commit/7f5d6dad7ba73e2ee53010241ba3204d04860a22))
### Features
* Add local Android installer ([#25](https://github.com/ReVanced/revanced-library/issues/25)) ([43d655a](https://github.com/ReVanced/revanced-library/commit/43d655aea5d86288ae9916630e0f30de219d5cfb))
* Remove deprecated functions ([b9bf3bc](https://github.com/ReVanced/revanced-library/commit/b9bf3bc88284c0381c7370c3606b662da2ef380d))
### BREAKING CHANGES
* Some functions have been removed.
* Some functions are not available anymore.
* The signature of some functions has changed.
# [3.0.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.4.0-dev.1...v3.0.0-dev.1) (2024-08-06)
### Bug Fixes
* Make functions internal which are supposed to be internal ([893d22d](https://github.com/ReVanced/revanced-library/commit/893d22d7938fa1c7544795635ed2ffacdd0cbf0d))
### Build System
* Refactor to DSL to bump ReVanced Patcher ([7f5d6da](https://github.com/ReVanced/revanced-library/commit/7f5d6dad7ba73e2ee53010241ba3204d04860a22))
### Features
* Remove deprecated functions ([b9bf3bc](https://github.com/ReVanced/revanced-library/commit/b9bf3bc88284c0381c7370c3606b662da2ef380d))
### BREAKING CHANGES
* Some functions have been removed.
* Some functions are not available anymore.
* The signature of some functions has changed.
# [2.4.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.3.0...v2.4.0-dev.1) (2024-04-07)
### Features
* Add local Android installer ([#25](https://github.com/ReVanced/revanced-library/issues/25)) ([43d655a](https://github.com/ReVanced/revanced-library/commit/43d655aea5d86288ae9916630e0f30de219d5cfb))
# [2.3.0](https://github.com/ReVanced/revanced-library/compare/v2.2.1...v2.3.0) (2024-03-14)
### Bug Fixes
* Support mounting even when Magisk is not installed ([2a30845](https://github.com/ReVanced/revanced-library/commit/2a30845f61d5f77ded7a72ee3d6ab55b4c512d52))
### Features
* Add utility function around key certificate pairs ([2df3484](https://github.com/ReVanced/revanced-library/commit/2df3484b68ed72338a52e76fb4b7ceb9c9c644ed))
* Improve exception message ([b15efa4](https://github.com/ReVanced/revanced-library/commit/b15efa41f8dc7d73865d0eab15be274b9ee3d7a3))
* Simplify signing utility API ([4c6a636](https://github.com/ReVanced/revanced-library/commit/4c6a6360cf83659d1f5c3a7c5710ac54426e9235))
# [2.3.0-dev.3](https://github.com/ReVanced/revanced-library/compare/v2.3.0-dev.2...v2.3.0-dev.3) (2024-03-14)
### Features
* Improve exception message ([b15efa4](https://github.com/ReVanced/revanced-library/commit/b15efa41f8dc7d73865d0eab15be274b9ee3d7a3))
# [2.3.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v2.3.0-dev.1...v2.3.0-dev.2) (2024-03-14)
### Features
* Simplify signing utility API ([4c6a636](https://github.com/ReVanced/revanced-library/commit/4c6a6360cf83659d1f5c3a7c5710ac54426e9235))
# [2.3.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.2-dev.1...v2.3.0-dev.1) (2024-03-13)
### Features
* Add utility function around key certificate pairs ([2df3484](https://github.com/ReVanced/revanced-library/commit/2df3484b68ed72338a52e76fb4b7ceb9c9c644ed))
## [2.2.2-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.1...v2.2.2-dev.1) (2024-03-12)
### Bug Fixes
* Support mounting even when Magisk is not installed ([2a30845](https://github.com/ReVanced/revanced-library/commit/2a30845f61d5f77ded7a72ee3d6ab55b4c512d52))
## [2.2.1](https://github.com/ReVanced/revanced-library/compare/v2.2.0...v2.2.1) (2024-03-09)
### Bug Fixes
* Do not specify a provider to automatically select an available one ([249372c](https://github.com/ReVanced/revanced-library/commit/249372c31f7e7975fc9eacb5361bd07dbc5dfb92))
## [2.2.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.0...v2.2.1-dev.1) (2024-03-09)
### Bug Fixes
* Do not specify a provider to automatically select an available one ([249372c](https://github.com/ReVanced/revanced-library/commit/249372c31f7e7975fc9eacb5361bd07dbc5dfb92))
# [2.2.0](https://github.com/ReVanced/revanced-library/compare/v2.1.0...v2.2.0) (2024-03-09)
### Bug Fixes
* Make property private ([51109c4](https://github.com/ReVanced/revanced-library/commit/51109c476837828535dcd395a5222d2fcf7fc22c))
* Sign APKs using `apksig` ([f59ecbc](https://github.com/ReVanced/revanced-library/commit/f59ecbccd14a08d87d4f18c3c0cc47a884088b99))
### Features
* Increase default expiration date of certificate ([f2bd3f5](https://github.com/ReVanced/revanced-library/commit/f2bd3f5eeee14ca32094be0d41c32b231a16bcc3))
# [2.2.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.1.0...v2.2.0-dev.1) (2024-03-09)
### Bug Fixes
* Make property private ([51109c4](https://github.com/ReVanced/revanced-library/commit/51109c476837828535dcd395a5222d2fcf7fc22c))
* Sign APKs using `apksig` ([f59ecbc](https://github.com/ReVanced/revanced-library/commit/f59ecbccd14a08d87d4f18c3c0cc47a884088b99))
### Features
* Increase default expiration date of certificate ([f2bd3f5](https://github.com/ReVanced/revanced-library/commit/f2bd3f5eeee14ca32094be0d41c32b231a16bcc3))
# [2.1.0](https://github.com/ReVanced/revanced-library/compare/v2.0.0...v2.1.0) (2024-03-04)
### Bug Fixes
* Use `BKS` instead of default signing provider to fix backwards compatibility ([41805fc](https://github.com/ReVanced/revanced-library/commit/41805fcb0bdc778fe0870427a0a1caa6d4369cee))
### Features
* Mention APK file name when logging aligning ([244ebc2](https://github.com/ReVanced/revanced-library/commit/244ebc21868c07d1852857f6858c1a53a5561155))
# [2.1.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v2.1.0-dev.1...v2.1.0-dev.2) (2024-03-04)
### Bug Fixes
* Use `BKS` instead of default signing provider to fix backwards compatibility ([41805fc](https://github.com/ReVanced/revanced-library/commit/41805fcb0bdc778fe0870427a0a1caa6d4369cee))
# [2.1.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.0.0...v2.1.0-dev.1) (2024-02-15)
### Features
* Mention APK file name when logging aligning ([244ebc2](https://github.com/ReVanced/revanced-library/commit/244ebc21868c07d1852857f6858c1a53a5561155))
# [2.0.0](https://github.com/ReVanced/revanced-library/compare/v1.5.0...v2.0.0) (2024-02-15)

View File

@@ -65,13 +65,14 @@ 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]
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Library.
@@ -80,7 +81,7 @@ Features can be requested by opening an issue using the
## 🐞 Submitting a bug report
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
@@ -88,11 +89,11 @@ If you encounter a bug while using ReVanced Library, open an issue using the
with the maintainers of ReVanced Library. This will help you determine whether your change is acceptable
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,
❤️ Thank you for considering contributing to ReVanced Library,
ReVanced

117
README.md Normal file
View 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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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>&nbsp;&nbsp;&nbsp;
<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
![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/ReVanced/revanced-library/release.yml)
![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg)
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).
## 📜 License
ReVanced Library is licensed under the GPLv3 license. Please see the [license 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.

View File

@@ -0,0 +1,221 @@
public final class app/revanced/library/ApkSigner {
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore;
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
public final fun getPrivateKey ()Ljava/security/PrivateKey;
}
public final class app/revanced/library/ApkSigner$Signer {
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
}
public final class app/revanced/library/ApkUtils {
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
public final fun 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/OptionsKt {
public static final fun setOptions (Ljava/util/Set;Ljava/util/Map;)V
}
public final class app/revanced/library/PatchKt {
public static final fun mostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
public static synthetic fun mostCommonCompatibleVersions$default (Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
}
public final class app/revanced/library/SerializationKt {
public static final fun serializeTo (Ljava/util/Set;Ljava/io/OutputStream;Z)V
public static synthetic fun serializeTo$default (Ljava/util/Set;Ljava/io/OutputStream;ZILjava/lang/Object;)V
}
public final class app/revanced/library/Utils {
public static final field INSTANCE Lapp/revanced/library/Utils;
public final fun isAndroidEnvironment ()Z
}
public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner {
}
public abstract interface class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService : android/os/IInterface {
public static final field DESCRIPTOR Ljava/lang/String;
public abstract fun getFileSystemService ()Landroid/os/IBinder;
}
public class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Default : app/revanced/library/installation/command/ILocalShellCommandRunnerRootService {
public fun <init> ()V
public fun asBinder ()Landroid/os/IBinder;
public fun getFileSystemService ()Landroid/os/IBinder;
}
public abstract class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Stub : android/os/Binder, app/revanced/library/installation/command/ILocalShellCommandRunnerRootService {
public fun <init> ()V
public fun asBinder ()Landroid/os/IBinder;
public static fun asInterface (Landroid/os/IBinder;)Lapp/revanced/library/installation/command/ILocalShellCommandRunnerRootService;
public fun onTransact (ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
}
public final class app/revanced/library/installation/command/LocalShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner, android/content/ServiceConnection, java/io/Closeable {
public fun close ()V
public fun onServiceConnected (Landroid/content/ComponentName;Landroid/os/IBinder;)V
public fun onServiceDisconnected (Landroid/content/ComponentName;)V
}
public abstract interface class app/revanced/library/installation/command/RunResult {
public abstract fun getError ()Ljava/lang/String;
public abstract fun getExitCode ()I
public abstract fun getOutput ()Ljava/lang/String;
public abstract fun waitFor ()V
}
public final class app/revanced/library/installation/command/RunResult$DefaultImpls {
public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V
}
public abstract class app/revanced/library/installation/command/ShellCommandRunner {
protected final fun getLogger ()Ljava/util/logging/Logger;
protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
}
public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult {
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult {
public final fun getException ()Ljava/lang/Exception;
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult {
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
}
public final class app/revanced/library/installation/installer/AdbRootInstaller : app/revanced/library/installation/installer/RootInstaller {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/library/installation/installer/DeviceNotFoundException : java/lang/Exception {
public fun <init> ()V
}
public class app/revanced/library/installation/installer/Installation {
public final fun getApkFilePath ()Ljava/lang/String;
}
public abstract class app/revanced/library/installation/installer/Installer {
public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getLogger ()Ljava/util/logging/Logger;
public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/library/installation/installer/Installer$Apk {
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getFile ()Ljava/io/File;
public final fun getPackageName ()Ljava/lang/String;
}
public final class app/revanced/library/installation/installer/LocalInstaller : app/revanced/library/installation/installer/Installer, java/io/Closeable {
public static final field Companion Lapp/revanced/library/installation/installer/LocalInstaller$Companion;
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
public fun close ()V
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/library/installation/installer/LocalInstaller$Companion {
}
public final class app/revanced/library/installation/installer/LocalInstallerResult {
public final fun getExtra ()Ljava/lang/String;
public final fun getPackageName ()Ljava/lang/String;
public final fun getPmStatus ()I
}
public final class app/revanced/library/installation/installer/LocalInstallerService : android/app/Service {
public fun <init> ()V
public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder;
public fun onStartCommand (Landroid/content/Intent;II)I
}
public final class app/revanced/library/installation/installer/LocalRootInstaller : app/revanced/library/installation/installer/RootInstaller, java/io/Closeable {
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
}
public final class app/revanced/library/installation/installer/RootInstallation : app/revanced/library/installation/installer/Installation {
public final fun getInstalledApkFilePath ()Ljava/lang/String;
public final fun getMounted ()Z
}
public abstract class app/revanced/library/installation/installer/RootInstaller : app/revanced/library/installation/installer/Installer {
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
protected final fun move (Ljava/io/File;Ljava/lang/String;)V
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/library/installation/installer/RootInstallerResult : java/lang/Enum {
public static final field FAILURE Lapp/revanced/library/installation/installer/RootInstallerResult;
public static final field SUCCESS Lapp/revanced/library/installation/installer/RootInstallerResult;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/RootInstallerResult;
public static fun values ()[Lapp/revanced/library/installation/installer/RootInstallerResult;
}
public final class app/revanced/library/logging/Logger {
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
public final fun removeAllHandlers ()V
public final fun setDefault ()V
public final fun setFormat (Ljava/lang/String;)V
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
}

View File

@@ -0,0 +1,167 @@
public final class app/revanced/library/ApkSigner {
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore;
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
public final fun getPrivateKey ()Ljava/security/PrivateKey;
}
public final class app/revanced/library/ApkSigner$Signer {
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
}
public final class app/revanced/library/ApkUtils {
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
public final fun 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/OptionsKt {
public static final fun setOptions (Ljava/util/Set;Ljava/util/Map;)V
}
public final class app/revanced/library/PatchKt {
public static final fun mostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
public static synthetic fun mostCommonCompatibleVersions$default (Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
}
public final class app/revanced/library/SerializationKt {
public static final fun serializeTo (Ljava/util/Set;Ljava/io/OutputStream;Z)V
public static synthetic fun serializeTo$default (Ljava/util/Set;Ljava/io/OutputStream;ZILjava/lang/Object;)V
}
public final class app/revanced/library/Utils {
public static final field INSTANCE Lapp/revanced/library/Utils;
public final fun isAndroidEnvironment ()Z
}
public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner {
}
public abstract interface class app/revanced/library/installation/command/RunResult {
public abstract fun getError ()Ljava/lang/String;
public abstract fun getExitCode ()I
public abstract fun getOutput ()Ljava/lang/String;
public abstract fun waitFor ()V
}
public final class app/revanced/library/installation/command/RunResult$DefaultImpls {
public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V
}
public abstract class app/revanced/library/installation/command/ShellCommandRunner {
protected final fun getLogger ()Ljava/util/logging/Logger;
protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
}
public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult {
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult {
public final fun getException ()Ljava/lang/Exception;
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult {
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
}
public final class app/revanced/library/installation/installer/AdbRootInstaller : app/revanced/library/installation/installer/RootInstaller {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/library/installation/installer/DeviceNotFoundException : java/lang/Exception {
public fun <init> ()V
}
public class app/revanced/library/installation/installer/Installation {
public final fun getApkFilePath ()Ljava/lang/String;
}
public abstract class app/revanced/library/installation/installer/Installer {
public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getLogger ()Ljava/util/logging/Logger;
public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/library/installation/installer/Installer$Apk {
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getFile ()Ljava/io/File;
public final fun getPackageName ()Ljava/lang/String;
}
public final class app/revanced/library/installation/installer/RootInstallation : app/revanced/library/installation/installer/Installation {
public final fun getInstalledApkFilePath ()Ljava/lang/String;
public final fun getMounted ()Z
}
public abstract class app/revanced/library/installation/installer/RootInstaller : app/revanced/library/installation/installer/Installer {
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
protected final fun move (Ljava/io/File;Ljava/lang/String;)V
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/library/installation/installer/RootInstallerResult : java/lang/Enum {
public static final field FAILURE Lapp/revanced/library/installation/installer/RootInstallerResult;
public static final field SUCCESS Lapp/revanced/library/installation/installer/RootInstallerResult;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/RootInstallerResult;
public static fun values ()[Lapp/revanced/library/installation/installer/RootInstallerResult;
}
public final class app/revanced/library/logging/Logger {
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
public final fun removeAllHandlers ()V
public final fun setDefault ()V
public final fun setFormat (Ljava/lang/String;)V
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
}

View File

@@ -1,168 +0,0 @@
public final class app/revanced/library/ApkSigner {
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/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 class app/revanced/library/ApkSigner$KeyStoreEntry {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
public final fun getPrivateKey ()Ljava/security/PrivateKey;
}
public final class app/revanced/library/ApkSigner$Signer {
public final fun getSigningExtension ()Lcom/android/tools/build/apkzlib/sign/SigningExtension;
public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V
public final fun signApk (Ljava/io/File;)V
}
public final class app/revanced/library/ApkUtils {
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
public final fun sign (Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
}
public final class app/revanced/library/ApkUtils$SigningOptions {
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getKeyStore ()Ljava/io/File;
public final fun getKeyStorePassword ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getSigner ()Ljava/lang/String;
}
public final class app/revanced/library/Options {
public static final field INSTANCE Lapp/revanced/library/Options;
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch;
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
public static synthetic fun serialize$default (Lapp/revanced/library/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String;
public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V
public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V
}
public final class app/revanced/library/Options$Patch {
public final fun getOptions ()Ljava/util/List;
public final fun getPatchName ()Ljava/lang/String;
}
public final class app/revanced/library/Options$Patch$Option {
public final fun getKey ()Ljava/lang/String;
public final fun getValue ()Ljava/lang/Object;
}
public final class app/revanced/library/PatchUtils {
public static final field INSTANCE Lapp/revanced/library/PatchUtils;
public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
public static synthetic fun getMostCommonCompatibleVersions$default (Lapp/revanced/library/PatchUtils;Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
}
public final class app/revanced/library/PatchUtils$Json {
public static final field INSTANCE Lapp/revanced/library/PatchUtils$Json;
public final fun deserialize (Ljava/io/InputStream;Ljava/lang/Class;)Ljava/util/Set;
public final fun serialize (Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;)V
public static synthetic fun serialize$default (Lapp/revanced/library/PatchUtils$Json;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;ILjava/lang/Object;)V
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch : app/revanced/library/PatchUtils$Json$JsonPatch {
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$Companion;
public final fun getCompatiblePackages ()Ljava/util/Set;
public final fun getDependencies ()Ljava/util/Set;
public final fun getDescription ()Ljava/lang/String;
public final fun getName ()Ljava/lang/String;
public final fun getOptions ()Ljava/util/Map;
public final fun getRequiresIntegrations ()Z
public final fun getUse ()Z
public final fun setRequiresIntegrations (Z)V
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$Companion {
public final fun fromPatch (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch;
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption {
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion;
public final fun getDefault ()Ljava/lang/Object;
public final fun getDescription ()Ljava/lang/String;
public final fun getKey ()Ljava/lang/String;
public final fun getRequired ()Z
public final fun getTitle ()Ljava/lang/String;
public final fun getValueType ()Ljava/lang/String;
public final fun getValues ()Ljava/util/Map;
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion {
public final fun fromPatchOption (Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption;
}
public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch {
}
public abstract class app/revanced/library/adb/AdbManager {
public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion;
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
protected final fun getDevice ()Lse/vidstige/jadb/JadbDevice;
protected final fun getLogger ()Ljava/util/logging/Logger;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)V
public fun uninstall (Ljava/lang/String;)V
}
public final class app/revanced/library/adb/AdbManager$Apk {
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getFile ()Ljava/io/File;
public final fun getPackageName ()Ljava/lang/String;
}
public final class app/revanced/library/adb/AdbManager$Companion {
public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/library/adb/AdbManager;
public static synthetic fun getAdbManager$default (Lapp/revanced/library/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/library/adb/AdbManager;
}
public final class app/revanced/library/adb/AdbManager$DeviceNotFoundException : java/lang/Exception {
public fun <init> ()V
}
public final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager {
public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)V
public fun uninstall (Ljava/lang/String;)V
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils {
}
public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager {
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)V
public fun uninstall (Ljava/lang/String;)V
}
public final class app/revanced/library/logging/Logger {
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
public final fun removeAllHandlers ()V
public final fun setDefault ()V
public final fun setFormat (Ljava/lang/String;)V
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
}

View File

@@ -1,52 +1,93 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlin)
alias(libs.plugins.android.library)
alias(libs.plugins.binary.compatibility.validator)
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.serialization)
`maven-publish`
signing
}
group = "app.revanced"
// Because access to the project is necessary to authenticate with GitHub,
// the following block must be placed in the root build.gradle.kts file
// instead of the settings.gradle.kts file inside the dependencyResolutionManagement block.
repositories {
mavenCentral()
mavenLocal()
google()
maven {
// A repository must be speficied for some reason. "registry" is a dummy.
// A repository must be specified 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")
}
}
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation(libs.revanced.patcher)
implementation(libs.kotlin.reflect)
implementation(libs.jadb) // Fork with Shell v2 support.
implementation(libs.jackson.module.kotlin)
implementation(libs.apkzlib)
implementation(libs.bcpkix.jdk15on)
implementation(libs.guava)
kotlin {
jvm {
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}
testImplementation(libs.revanced.patcher)
testImplementation(libs.kotlin.test)
}
androidTarget {
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
tasks {
test {
useJUnitPlatform()
testLogging {
events("PASSED", "SKIPPED", "FAILED")
publishLibraryVariants("release")
}
sourceSets {
androidMain.dependencies {
implementation(libs.core.ktx)
implementation(libs.libsu.nio)
implementation(libs.libsu.service)
}
commonMain.dependencies {
implementation(libs.apksig)
implementation(libs.apkzlib)
implementation(libs.bcpkix.jdk18on)
implementation(libs.guava)
implementation(libs.jadb)
implementation(libs.kotlin.reflect)
implementation(libs.kotlinx.serialization.json)
implementation(libs.revanced.patcher)
}
commonTest.dependencies {
implementation(libs.kotlin.test.junit)
implementation(libs.revanced.patcher)
}
}
}
kotlin { jvmToolchain(11) }
android {
namespace = "app.revanced.library"
compileSdk = 34
defaultConfig {
minSdk = 26
}
buildFeatures {
aidl = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
java {
withSourcesJar()
targetCompatibility = JavaVersion.VERSION_11
}
publishing {
@@ -55,43 +96,40 @@ publishing {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-library")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
}
}
}
publications {
create<MavenPublication>("revanced-library-publication") {
from(components["java"])
// KMP plugin creates a publication already, so just configure the POM.
publications.all {
if (this !is MavenPublication) return@all
version = project.version.toString()
pom {
name = "ReVanced Library"
description = "Library containing common utilities for ReVanced"
url = "https://revanced.app"
pom {
name = "ReVanced Library"
description = "Library containing common utilities for ReVanced"
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-library.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
url = "https://github.com/revanced/revanced-library"
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-library.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
url = "https://github.com/revanced/revanced-library"
}
}
}
@@ -99,5 +137,5 @@ publishing {
signing {
useGpgCmd()
sign(publishing.publications["revanced-library-publication"])
sign(publishing.publications)
}

View File

@@ -1,4 +1,11 @@
org.gradle.parallel = true
version = 3.2.0-dev.1
#Gradle
org.gradle.jvmargs = -Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options="-Xmx2048M"
org.gradle.caching = true
org.gradle.configuration-cache = true
org.gradle.parallel = true
#Kotlin
kotlin.code.style = official
version = 2.0.0
#Android
android.useAndroidX = true
android.nonTransitiveRClass = true

View File

@@ -1,23 +1,35 @@
[versions]
jackson-module-kotlin = "2.15.0"
jadb = "1.2.1"
kotlin = "1.9.22"
revanced-patcher = "19.3.1"
binary-compatibility-validator = "0.14.0"
apkzlib = "8.2.2"
bcpkix-jdk15on = "1.70"
guava = "33.0.0-jre"
android = "8.13.2"
bcpkix-jdk18on = "1.77"
binary-compatibility-validator = "0.15.1"
core-ktx = "1.15.0"
guava = "33.2.1-jre"
jadb = "1.2.1.1"
kotlin = "2.0.20"
kotlinx-coroutines = "1.8.1"
kotlinx-serialization = "1.7.1"
libsu = "5.2.2"
revanced-patcher = "21.0.0"
[libraries]
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" }
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" }
apkzlib = { module = "com.android.tools.build:apkzlib", version.ref = "android" }
apksig = { module = "com.android.tools.build:apksig", version.ref = "android" }
bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bcpkix-jdk18on" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
jadb = { module = "app.revanced:jadb", version.ref = "jadb" } # Fork with Shell v2 support.
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" }
libsu-service = { module = "com.github.topjohnwu.libsu:service", version.ref = "libsu" }
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
[plugins]
android-library = { id = "com.android.library", version.ref = "android" }
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

Binary file not shown.

View File

@@ -1,6 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dist
zipStorePath=wrapper/dists

22
gradlew vendored
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,7 +85,9 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -201,11 +205,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

22
gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

3002
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
"@saithodev/semantic-release-backmerge": "^4.0.1",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.9.1",
"semantic-release": "^23.0.2"
"gradle-semantic-release-plugin": "^1.10.1",
"semantic-release": "^24.1.2"
}
}

View File

@@ -1,7 +1,15 @@
// TODO: Figure out why this causes problems.
rootProject.name = "revanced-library"
buildCache {
local {
isEnabled = !System.getenv().containsKey("CI")
isEnabled = "CI" !in System.getenv()
}
}
pluginManagement {
repositories {
google()
mavenCentral()
}
}

View File

@@ -0,0 +1,5 @@
package app.revanced.library.installation.command;
interface ILocalShellCommandRunnerRootService {
IBinder getFileSystemService();
}

View File

@@ -0,0 +1,88 @@
package app.revanced.library.installation.command
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.BuilderImpl
import com.topjohnwu.superuser.ipc.RootService
import com.topjohnwu.superuser.nio.FileSystemManager
import java.io.Closeable
import java.io.File
import java.io.InputStream
/**
* The [LocalShellCommandRunner] for running commands locally on the device.
*
* @param context The [Context] to use for binding to the [RootService].
* @param onReady A callback to be invoked when [LocalShellCommandRunner] is ready to be used.
* @throws IllegalStateException If the main shell was already created
*
* @see ShellCommandRunner
*/
class LocalShellCommandRunner internal constructor(
private val context: Context,
private val onReady: () -> Unit,
) : ShellCommandRunner(), ServiceConnection, Closeable {
private var fileSystemManager: FileSystemManager? = null
init {
logger.info("Binding to RootService")
val intent = Intent(context, LocalShellCommandRunnerRootService::class.java)
RootService.bind(intent, this)
}
override fun runCommand(command: String) = shell.newJob().add(command).exec().let {
object : RunResult {
override val exitCode = it.code
override val output by lazy { it.out.joinToString("\n") }
override val error by lazy { it.err.joinToString("\n") }
}
}
override fun hasRootPermission() = shell.isRoot
/**
* Writes the given [content] to the given [targetFilePath].
*
* @param content The [InputStream] to write.
* @param targetFilePath The path to write to.
* @throws NotReadyException If the [LocalShellCommandRunner] is not ready yet.
*/
override fun write(content: InputStream, targetFilePath: String) {
fileSystemManager?.let {
it.getFile(targetFilePath).newOutputStream().use { outputStream ->
content.copyTo(outputStream)
}
} ?: throw NotReadyException("FileSystemManager service is not ready yet")
}
override fun move(file: File, targetFilePath: String) {
invoke("mv ${file.absolutePath} $targetFilePath")
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val ipc = ILocalShellCommandRunnerRootService.Stub.asInterface(service)
fileSystemManager = FileSystemManager.getRemote(ipc.fileSystemService)
logger.info("LocalShellCommandRunner service is ready")
onReady()
}
override fun onServiceDisconnected(name: ComponentName?) {
fileSystemManager = null
logger.info("LocalShellCommandRunner service is disconnected")
}
override fun close() = RootService.unbind(this)
private companion object {
private val shell = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER).build()
}
internal class NotReadyException internal constructor(message: String) : Exception(message)
}

View File

@@ -0,0 +1,15 @@
package app.revanced.library.installation.command
import android.content.Intent
import com.topjohnwu.superuser.ipc.RootService
import com.topjohnwu.superuser.nio.FileSystemManager
/**
* The [RootService] for the [LocalShellCommandRunner].
*/
internal class LocalShellCommandRunnerRootService : RootService() {
override fun onBind(intent: Intent) = object : ILocalShellCommandRunnerRootService.Stub() {
override fun getFileSystemService() =
FileSystemManager.getService()
}
}

View File

@@ -0,0 +1,108 @@
package app.revanced.library.installation.installer
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.ContextCompat
import app.revanced.library.installation.installer.Installer.Apk
import java.io.Closeable
import java.io.File
/**
* [LocalInstaller] for installing and uninstalling [Apk] files locally.
*
* @param context The [Context] to use for installing and uninstalling.
* @param onResult The callback to be invoked when the [Apk] is installed or uninstalled.
*
* @see Installer
*/
@Suppress("unused")
class LocalInstaller(
private val context: Context,
onResult: (result: LocalInstallerResult) -> Unit,
) : Installer<Unit, Installation>(), Closeable {
private val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val pmStatus = intent.getIntExtra(LocalInstallerService.EXTRA_STATUS, -999)
val extra = intent.getStringExtra(LocalInstallerService.EXTRA_STATUS_MESSAGE)!!
val packageName = intent.getStringExtra(LocalInstallerService.EXTRA_PACKAGE_NAME)!!
onResult.invoke(LocalInstallerResult(pmStatus, extra, packageName))
}
}
private val intentSender
get() = PendingIntent.getService(
context,
0,
Intent(context, LocalInstallerService::class.java),
PendingIntent.FLAG_UPDATE_CURRENT,
).intentSender
init {
ContextCompat.registerReceiver(
context,
broadcastReceiver,
IntentFilter().apply {
addAction(LocalInstallerService.ACTION)
},
ContextCompat.RECEIVER_NOT_EXPORTED,
)
}
override suspend fun install(apk: Apk) {
logger.info("Installing ${apk.file.name}")
val packageInstaller = context.packageManager.packageInstaller
packageInstaller.openSession(packageInstaller.createSession(sessionParams)).use { session ->
session.writeApk(apk.file)
session.commit(intentSender)
}
}
@SuppressLint("MissingPermission")
override suspend fun uninstall(packageName: String) {
logger.info("Uninstalling $packageName")
val packageInstaller = context.packageManager.packageInstaller
packageInstaller.uninstall(packageName, intentSender)
}
override suspend fun getInstallation(packageName: String) = try {
val packageInfo = context.packageManager.getPackageInfo(packageName, 0)
Installation(packageInfo.applicationInfo.sourceDir)
} catch (e: PackageManager.NameNotFoundException) {
null
}
override fun close() = context.unregisterReceiver(broadcastReceiver)
@SuppressLint("MissingPermission")
companion object {
private val sessionParams = PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL,
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
setRequestUpdateOwnership(true)
setInstallReason(PackageManager.INSTALL_REASON_USER)
}
private fun PackageInstaller.Session.writeApk(apk: File) {
apk.inputStream().use { inputStream ->
openWrite(apk.name, 0, apk.length()).use { outputStream ->
inputStream.copyTo(outputStream, 1024 * 1024)
fsync(outputStream)
}
}
}
}
}

View File

@@ -0,0 +1,15 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.installer.Installer.Apk
/**
* The result of installing or uninstalling an [Apk] locally using [LocalInstaller].
*
* @param pmStatus The status code returned by the package manager.
* @param extra The extra information returned by the package manager.
* @param packageName The package name of the installed app.
*
* @see LocalInstaller
*/
@Suppress("MemberVisibilityCanBePrivate")
class LocalInstallerResult internal constructor(val pmStatus: Int, val extra: String, val packageName: String)

View File

@@ -0,0 +1,57 @@
package app.revanced.library.installation.installer
import android.app.Service
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Build
import android.os.IBinder
class LocalInstallerService : Service() {
override fun onStartCommand(
intent: Intent, flags: Int, startId: Int
): Int {
val extraStatus = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)
val extraStatusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
val extraPackageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
when (extraStatus) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
startActivity(
if (Build.VERSION.SDK_INT >= 33) {
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(Intent.EXTRA_INTENT)
}?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
}
else -> {
sendBroadcast(
Intent().apply {
action = ACTION
`package` = packageName
putExtra(EXTRA_STATUS, extraStatus)
putExtra(EXTRA_STATUS_MESSAGE, extraStatusMessage)
putExtra(EXTRA_PACKAGE_NAME, extraPackageName)
}
)
}
}
stopSelf()
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
internal companion object {
internal const val ACTION = "PACKAGE_INSTALLER_ACTION"
internal const val EXTRA_STATUS = "EXTRA_STATUS"
internal const val EXTRA_STATUS_MESSAGE = "EXTRA_STATUS_MESSAGE"
internal const val EXTRA_PACKAGE_NAME = "EXTRA_PACKAGE_NAME"
}
}

View File

@@ -0,0 +1,34 @@
package app.revanced.library.installation.installer
import android.content.Context
import app.revanced.library.installation.command.LocalShellCommandRunner
import app.revanced.library.installation.installer.Installer.Apk
import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException
import com.topjohnwu.superuser.ipc.RootService
import java.io.Closeable
/**
* [LocalRootInstaller] for installing and uninstalling [Apk] files locally with using root permissions by mounting.
*
* @param context The [Context] to use for binding to the [RootService].
* @param onReady A callback to be invoked when [LocalRootInstaller] is ready to be used.
*
* @throws NoRootPermissionException If the device does not have root permission.
*
* @see Installer
* @see LocalShellCommandRunner
*/
@Suppress("unused")
class LocalRootInstaller(
context: Context,
onReady: LocalRootInstaller.() -> Unit = {},
) : RootInstaller(
{ installer ->
LocalShellCommandRunner(context) {
(installer as LocalRootInstaller).onReady()
}
},
),
Closeable {
override fun close() = (shellCommandRunner as LocalShellCommandRunner).close()
}

View File

@@ -1,70 +1,129 @@
package app.revanced.library
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 com.android.apksig.ApkSigner.SignerConfig
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.math.BigInteger
import java.security.*
import java.security.cert.X509Certificate
import java.util.*
import java.util.logging.Logger
import kotlin.time.Duration.Companion.days
/**
* Utility class for reading or writing keystore files and entries as well as signing APK files.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object ApkSigner {
private val logger = Logger.getLogger(Signer::class.java.name)
private val logger = Logger.getLogger(ApkSigner::class.java.name)
init {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(BouncyCastleProvider())
}
}
private fun newKeyStoreInstance() = KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME)
/**
* Create a new [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.
* @param validUntil The date until which the certificate is valid.
*
* @return The created [PrivateKeyCertificatePair].
* @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,8 +138,11 @@ object ApkSigner {
* @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,
@@ -88,7 +150,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.
@@ -105,81 +167,10 @@ object ApkSigner {
return PrivateKeyCertificatePair(privateKey, certificate)
}
/**
* Create a new keystore with a new keypair.
*
* @param entries The entries to add to the keystore.
*
* @return The created keystore.
*
* @see KeyStoreEntry
*/
fun newKeyStore(entries: Set<KeyStoreEntry>): KeyStore {
logger.fine("Creating keystore")
return KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null)
entries.forEach { entry ->
// Add all entries to the keystore.
setKeyEntry(
entry.alias,
entry.privateKeyCertificatePair.privateKey,
entry.password.toCharArray(),
arrayOf(entry.privateKeyCertificatePair.certificate),
)
}
}
}
/**
* Create a new keystore with a new keypair and saves it to the given [keyStoreOutputStream].
*
* @param keyStoreOutputStream The stream to write the keystore to.
* @param keyStorePassword The password for the keystore.
* @param entries The entries to add to the keystore.
*/
fun newKeyStore(
keyStoreOutputStream: OutputStream,
keyStorePassword: String,
entries: Set<KeyStoreEntry>,
) = newKeyStore(entries).store(
keyStoreOutputStream,
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(KeyStore.getDefaultType()).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 [Signer].
*
* @param signer The name of the signer.
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
*
* @return The new [Signer].
@@ -187,36 +178,20 @@ object ApkSigner {
* @see PrivateKeyCertificatePair
* @see Signer
*/
fun newApkSigner(privateKeyCertificatePair: PrivateKeyCertificatePair) =
Signer(
SigningExtension(
SigningOptions.builder()
.setMinSdkVersion(21) // TODO: Extracting from the target APK would be ideal.
.setV1SigningEnabled(true)
.setV2SigningEnabled(true)
.setCertificates(privateKeyCertificatePair.certificate)
.setKey(privateKeyCertificatePair.privateKey)
.build(),
),
)
/**
* Create a new [Signer].
*
* @param 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
*/
fun newApkSigner(
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
) = newApkSigner(readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))
signer: String,
privateKeyCertificatePair: PrivateKeyCertificatePair,
) = Signer(
com.android.apksig.ApkSigner.Builder(
listOf(
SignerConfig.Builder(
signer,
privateKeyCertificatePair.privateKey,
listOf(privateKeyCertificatePair.certificate),
).build(),
),
),
)
/**
* An entry in a keystore.
@@ -230,7 +205,7 @@ object ApkSigner {
class KeyStoreEntry(
val alias: String,
val password: String,
val privateKeyCertificatePair: PrivateKeyCertificatePair = newPrivateKeyCertificatePair(),
val privateKeyCertificatePair: PrivateKeyCertificatePair,
)
/**
@@ -244,23 +219,11 @@ object ApkSigner {
val certificate: X509Certificate,
)
class Signer internal constructor(val signingExtension: SigningExtension) {
/**
* Sign an APK file.
*
* @param apkFile The APK file to sign.
*/
fun signApk(apkFile: File) = ZFile.openReadWrite(apkFile).use { signApk(it) }
class Signer internal constructor(private val signerBuilder: com.android.apksig.ApkSigner.Builder) {
fun signApk(inputApkFile: File, outputApkFile: File) {
logger.info("Signing APK")
/**
* Sign an APK file.
*
* @param apkZFile The APK [ZFile] to sign.
*/
fun signApk(apkZFile: ZFile) {
logger.info("Signing ${apkZFile.file.name}")
signingExtension.register(apkZFile)
signerBuilder.setInputApk(inputApkFile)?.setOutputApk(outputApkFile)?.build()?.sign()
}
}
}

View File

@@ -1,12 +1,15 @@
package app.revanced.library
import app.revanced.library.ApkSigner.newPrivateKeyCertificatePair
import app.revanced.patcher.PatcherResult
import com.android.tools.build.apkzlib.zip.AlignmentRules
import com.android.tools.build.apkzlib.zip.StoredEntry
import com.android.tools.build.apkzlib.zip.ZFile
import com.android.tools.build.apkzlib.zip.ZFileOptions
import java.io.File
import java.util.*
import java.util.logging.Logger
import kotlin.time.Duration.Companion.days
/**
* Utility functions to work with APK files.
@@ -79,7 +82,7 @@ object ApkUtils {
// Delete resources that were staged for deletion.
if (resources.deleteResources.isNotEmpty()) {
targetApkZFile.entries().filter { entry ->
resources.deleteResources.any { shouldDelete -> shouldDelete(entry.centralDirectoryHeader.name) }
entry.centralDirectoryHeader.name in resources.deleteResources
}.forEach(StoredEntry::delete)
}
}
@@ -93,48 +96,99 @@ object ApkUtils {
}
/**
* Signs the apk file with the given options.
* Creates a new private key and certificate pair and saves it to the keystore in [keyStoreDetails].
*
* @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 File.sign(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 entries = setOf(ApkSigner.KeyStoreEntry(signingOptions.alias, signingOptions.password))
// Create a new keystore with a new keypair and saves it.
ApkSigner.newKeyStore(entries).apply {
store(
signingOptions.keyStore.outputStream(),
signingOptions.keyStorePassword?.toCharArray(),
)
}
}
ApkSigner.newApkSigner(
keyStore,
signingOptions.alias,
signingOptions.password,
).signApk(this)
private 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(),
)
}
/**
* Options for signing an apk.
* Reads the private key and certificate pair from an existing keystore.
*
* @param keyStore The keystore to use for signing.
* @param keyStoreDetails The details for the keystore.
*
* @return The private key and certificate pair.
*/
private 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)
/**
* 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.
* @param signer The name of the signer.
*/
class SigningOptions(
class KeyStoreDetails(
val keyStore: File,
val keyStorePassword: String?,
val keyStorePassword: String? = null,
val alias: String = "ReVanced Key",
val password: String = "",
val signer: String = "ReVanced",
)
/**
* 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),
)
}

View File

@@ -0,0 +1,36 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package app.revanced.library
import app.revanced.patcher.patch.OptionException
import app.revanced.patcher.patch.Patch
import java.util.logging.Logger
typealias PatchName = String
typealias OptionKey = String
typealias OptionValue = Any?
typealias PatchesOptions = Map<PatchName, Map<OptionKey, OptionValue>>
private val logger = Logger.getLogger("Options")
/**
* Set the options for a set of patches that have a name.
*
* @param options The options to set. The key is the patch name and the value is a map of option keys to option values.
*/
fun Set<Patch<*>>.setOptions(options: PatchesOptions) = filter { it.name != null }.forEach { patch ->
options[patch.name]?.forEach setOption@{ (optionKey, optionValue) ->
if (optionKey !in patch.options) {
return@setOption logger.warning(
"Could not set option for the \"${patch.name}\" patch because " +
"option with key \"${optionKey}\" does not exist",
)
}
try {
patch.options[optionKey] = optionValue
} catch (e: OptionException) {
logger.warning("Could not set option value for the \"${patch.name}\" patch: ${e.message}")
}
}
}

View File

@@ -0,0 +1,52 @@
package app.revanced.library
import app.revanced.patcher.patch.Package
import app.revanced.patcher.patch.Patch
typealias PackageName = String
typealias Version = String
typealias Count = Int
typealias VersionMap = LinkedHashMap<Version, Count>
typealias PackageNameMap = Map<PackageName, VersionMap>
/**
* Get the count of versions for each compatible package from the set of [Patch] ordered by the most common version.
*
* @param packageNames The names of the compatible packages to include. If null, all packages will be included.
* @param countUnusedPatches Whether to count patches that are not used.
* @return A map of package names to a map of versions to their count.
*/
fun Set<Patch<*>>.mostCommonCompatibleVersions(
packageNames: Set<String>? = null,
countUnusedPatches: Boolean = false,
): PackageNameMap = buildMap {
fun filterWantedPackages(compatiblePackages: List<Package>): List<Package> {
val wantedPackages = packageNames?.toHashSet() ?: return compatiblePackages
return compatiblePackages.filter { (name, _) -> name in wantedPackages }
}
this@mostCommonCompatibleVersions.filter { it.use || countUnusedPatches }
.flatMap { it.compatiblePackages ?: emptyList() }
.let(::filterWantedPackages)
.forEach { (name, versions) ->
if (versions?.isEmpty() == true) {
return@forEach
}
val versionMap = getOrPut(name) { linkedMapOf() }
versions?.forEach { version ->
versionMap[version] = versionMap.getOrDefault(version, 0) + 1
}
}
// Sort the version maps by the most common version.
forEach { (packageName, versionMap) ->
this[packageName] =
versionMap
.asIterable()
.sortedWith(compareByDescending { it.value })
.associate { it.key to it.value } as VersionMap
}
}

View File

@@ -0,0 +1,119 @@
package app.revanced.library
import app.revanced.patcher.patch.Option
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.VersionName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.element
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.encodeStructure
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToStream
import kotlinx.serialization.serializer
import java.io.OutputStream
private class PatchSerializer : KSerializer<Patch<*>> {
override val descriptor = buildClassSerialDescriptor("Patch") {
element<String?>("name")
element<String?>("description")
element<Boolean>("use")
element<List<String>>("dependencies")
element<Map<PackageName, Set<VersionName>?>?>("compatiblePackages")
element("options", OptionSerializer.descriptor)
}
override fun deserialize(decoder: Decoder) = throw NotImplementedError("Deserialization is unsupported")
@OptIn(ExperimentalSerializationApi::class)
override fun serialize(encoder: Encoder, value: Patch<*>) {
encoder.encodeStructure(descriptor) {
encodeNullableSerializableElement(
descriptor,
0,
String.serializer(),
value.name,
)
encodeNullableSerializableElement(
descriptor,
1,
String.serializer(),
value.description,
)
encodeBooleanElement(
descriptor,
2,
value.use,
)
encodeSerializableElement(
descriptor,
3,
ListSerializer(String.serializer()),
value.dependencies.map { it.name ?: it.toString() },
)
encodeNullableSerializableElement(
descriptor,
4,
MapSerializer(String.serializer(), SetSerializer(String.serializer()).nullable),
value.compatiblePackages?.associate { (packageName, versions) -> packageName to versions },
)
encodeSerializableElement(
descriptor,
5,
SetSerializer(OptionSerializer),
value.options.values.toSet(),
)
}
}
private object OptionSerializer : KSerializer<Option<*>> {
override val descriptor = buildClassSerialDescriptor("Option") {
element<String>("key")
element<String?>("title")
element<String?>("description")
element<Boolean>("required")
// Type does not matter for serialization. Using String.
element<String>("type")
element<String?>("default")
// Map value type does not matter for serialization. Using String.
element<Map<String, String?>?>("values")
}
override fun deserialize(decoder: Decoder) = throw NotImplementedError("Deserialization is unsupported")
@OptIn(ExperimentalSerializationApi::class)
override fun serialize(encoder: Encoder, value: Option<*>) {
encoder.encodeStructure(descriptor) {
encodeStringElement(descriptor, 0, value.key)
encodeNullableSerializableElement(descriptor, 1, String.serializer(), value.title)
encodeNullableSerializableElement(descriptor, 2, String.serializer(), value.description)
encodeBooleanElement(descriptor, 3, value.required)
encodeSerializableElement(descriptor, 4, String.serializer(), value.type.toString())
encodeNullableSerializableElement(descriptor, 5, serializer(value.type), value.default)
encodeNullableSerializableElement(descriptor, 6, MapSerializer(String.serializer(), serializer(value.type)), value.values)
}
}
}
}
private val patchPrettySerializer by lazy { Json { prettyPrint = true } }
private val patchSerializer by lazy { Json }
/**
* Serialize this set of [Patch] to JSON and write it to the given [outputStream].
*
* @param outputStream The output stream to write the JSON to.
* @param prettyPrint Whether to pretty print the JSON.
*/
@OptIn(ExperimentalSerializationApi::class)
fun Set<Patch<*>>.serializeTo(
outputStream: OutputStream,
prettyPrint: Boolean = true,
) = if (prettyPrint) {
patchPrettySerializer
} else {
patchSerializer
}.encodeToStream(SetSerializer(PatchSerializer()), this, outputStream)

View File

@@ -0,0 +1,18 @@
package app.revanced.library
/**
* Utils for the library.
*/
@Suppress("unused")
object Utils {
/**
* True if the environment is Android.
*/
val isAndroidEnvironment =
try {
Class.forName("android.app.Application")
true
} catch (e: ClassNotFoundException) {
false
}
}

View File

@@ -0,0 +1,59 @@
package app.revanced.library.installation.command
import app.revanced.library.installation.installer.getDevice
import se.vidstige.jadb.JadbDevice
import se.vidstige.jadb.RemoteFile
import java.io.File
import java.io.InputStream
/**
* [AdbShellCommandRunner] for running commands on a device remotely using ADB.
*
* @see ShellCommandRunner
*/
class AdbShellCommandRunner : ShellCommandRunner {
private val device: JadbDevice
/**
* Creates a [AdbShellCommandRunner] for the given device.
*
* @param device The device.
*/
internal constructor(device: JadbDevice) {
this.device = device
}
/**
* Creates a [AdbShellCommandRunner] for the device with the given serial.
*
* @param deviceSerial deviceSerial The device serial. If null, the first connected device will be used.
*/
internal constructor(deviceSerial: String?) {
device = getDevice(deviceSerial, logger)
}
override fun runCommand(command: String) = device.shellProcessBuilder(command).start().let { process ->
object : RunResult {
override val exitCode by lazy { process.waitFor() }
override val output by lazy { process.inputStream.bufferedReader().readText() }
override val error by lazy { process.errorStream.bufferedReader().readText() }
override fun waitFor() {
process.waitFor()
}
}
}
override fun hasRootPermission(): Boolean = invoke("whoami").exitCode == 0
override fun write(content: InputStream, targetFilePath: String) =
device.push(content, System.currentTimeMillis(), 644, RemoteFile(targetFilePath))
/**
* Moves the given [file] from the local to the target file path on the device.
*
* @param file The file to move.
* @param targetFilePath The target file path.
*/
override fun move(file: File, targetFilePath: String) = device.push(file, RemoteFile(targetFilePath))
}

View File

@@ -0,0 +1,26 @@
package app.revanced.library.installation.command
/**
* The result of a command execution.
*/
interface RunResult {
/**
* The exit code of the command.
*/
val exitCode: Int
/**
* The output of the command.
*/
val output: String
/**
* The error of the command.
*/
val error: String
/**
* Waits for the command to finish.
*/
fun waitFor() {}
}

View File

@@ -0,0 +1,59 @@
package app.revanced.library.installation.command
import java.io.File
import java.io.InputStream
import java.util.logging.Logger
/**
* [ShellCommandRunner] for running commands on a device.
*/
abstract class ShellCommandRunner internal constructor() {
protected val logger: Logger = Logger.getLogger(this::class.java.name)
/**
* Writes the given [content] to the file at the given [targetFilePath] path.
*
* @param content The content of the file.
* @param targetFilePath The target file path.
*/
internal abstract fun write(
content: InputStream,
targetFilePath: String,
)
/**
* Moves the given [file] to the given [targetFilePath] path.
*
* @param file The file to move.
* @param targetFilePath The target file path.
*/
internal abstract fun move(
file: File,
targetFilePath: String,
)
/**
* Runs the given [command] on the device as root.
*
* @param command The command to run.
* @return The [RunResult].
*/
protected abstract fun runCommand(command: String): RunResult
/**
* Checks if the device has root permission.
*
* @return True if the device has root permission, false otherwise.
*/
internal abstract fun hasRootPermission(): Boolean
/**
* Runs a command on the device as root.
*
* @param command The command to run.
* @return The [RunResult].
*/
internal operator fun invoke(
command: String,
) = runCommand("su -c \'$command\'")
}

View File

@@ -0,0 +1,58 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.command.AdbShellCommandRunner
import app.revanced.library.installation.command.ShellCommandRunner
import app.revanced.library.installation.installer.Constants.GET_SDK_VERSION
import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH
import se.vidstige.jadb.JadbException
import se.vidstige.jadb.managers.Package
import se.vidstige.jadb.managers.PackageManager
import se.vidstige.jadb.managers.PackageManager.UPDATE_OWNERSHIP
/**
* [AdbInstaller] for installing and uninstalling [Apk] files using ADB.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*
* @see Installer
*/
class AdbInstaller(
deviceSerial: String? = null,
) : Installer<AdbInstallerResult, Installation>() {
private val shellCommandRunner: ShellCommandRunner
private val packageManager: PackageManager
init {
val device = getDevice(deviceSerial, logger)
shellCommandRunner = AdbShellCommandRunner(device)
packageManager = PackageManager(device)
logger.fine("Connected to $deviceSerial")
}
override suspend fun install(apk: Apk): AdbInstallerResult {
return runPackageManager {
val sdkVersion = shellCommandRunner(GET_SDK_VERSION).output.toInt()
if (sdkVersion < 34) install(apk.file)
else installWithOptions(apk.file, listOf(UPDATE_OWNERSHIP))
}
}
override suspend fun uninstall(packageName: String): AdbInstallerResult {
logger.info("Uninstalling $packageName")
return runPackageManager { uninstall(Package(packageName)) }
}
override suspend fun getInstallation(packageName: String): Installation? = packageManager.packages.find {
it.toString() == packageName
}?.let { Installation(shellCommandRunner(INSTALLED_APK_PATH).output) }
private fun runPackageManager(block: PackageManager.() -> Unit) = try {
packageManager.run(block)
AdbInstallerResult.Success
} catch (e: JadbException) {
AdbInstallerResult.Failure(e)
}
}

View File

@@ -0,0 +1,23 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.installer.Installer.Apk
/**
* The result of installing or uninstalling an [Apk] via ADB using [AdbInstaller].
*
* @see AdbInstaller
*/
@Suppress("MemberVisibilityCanBePrivate")
interface AdbInstallerResult {
/**
* The result of installing an [Apk] successfully.
*/
object Success : AdbInstallerResult
/**
* The result of installing an [Apk] unsuccessfully.
*
* @param exception The exception that caused the installation to fail.
*/
class Failure internal constructor(val exception: Exception) : AdbInstallerResult
}

View File

@@ -0,0 +1,23 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.command.AdbShellCommandRunner
import app.revanced.library.installation.installer.Installer.Apk
import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException
/**
* [AdbRootInstaller] for installing and uninstalling [Apk] files with using ADB root permissions by mounting.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*
* @throws NoRootPermissionException If the device does not have root permission.
*
* @see RootInstaller
* @see AdbShellCommandRunner
*/
class AdbRootInstaller(
deviceSerial: String? = null,
) : RootInstaller({ AdbShellCommandRunner(deviceSerial) }) {
init {
logger.fine("Connected to $deviceSerial")
}
}

View File

@@ -0,0 +1,77 @@
package app.revanced.library.installation.installer
@Suppress("MemberVisibilityCanBePrivate")
internal object Constants {
const val PLACEHOLDER = "PLACEHOLDER"
const val SELINUX_CONTEXT = "u:object_r:apk_data_file:s0"
const val TMP_FILE_PATH = "/data/local/tmp/revanced.tmp"
const val MOUNT_PATH = "/data/adb/revanced/"
const val MOUNTED_APK_PATH = "$MOUNT_PATH$PLACEHOLDER.apk"
const val MOUNT_SCRIPT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
const val EXISTS = "[[ -f $PLACEHOLDER ]] || exit 1"
const val MOUNT_GREP = "grep -F $PLACEHOLDER /proc/mounts"
const val DELETE = "rm -rf $PLACEHOLDER"
const val CREATE_DIR = "mkdir -p"
const val RESTART = "am start -S $PLACEHOLDER"
const val KILL = "am force-stop $PLACEHOLDER"
const val INSTALLED_APK_PATH = "pm path $PLACEHOLDER"
const val CREATE_INSTALLATION_PATH = "$CREATE_DIR $MOUNT_PATH"
const val GET_SDK_VERSION = "getprop ro.build.version.sdk"
const val MOUNT_APK =
"base_path=\"$MOUNTED_APK_PATH\" && " +
"mv $TMP_FILE_PATH \$base_path && " +
"chmod 644 \$base_path && " +
"chown system:system \$base_path && " +
"chcon $SELINUX_CONTEXT \$base_path"
const val UMOUNT =
"grep $PLACEHOLDER /proc/mounts | " +
"while read -r line; do echo \$line | " +
"cut -d ' ' -f 2 | " +
"sed 's/apk.*/apk/' | " +
"xargs -r umount -l; done"
const val INSTALL_MOUNT_SCRIPT = "mv $TMP_FILE_PATH $MOUNT_SCRIPT_PATH && chmod +x $MOUNT_SCRIPT_PATH"
val MOUNT_SCRIPT =
"""
#!/system/bin/sh
until [ "$( getprop sys.boot_completed )" = 1 ]; do sleep 3; done
until [ -d "/sdcard/Android" ]; do sleep 1; done
stock_path=$( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
# Make sure the app is installed.
if [ -z "${'$'}stock_path" ]; then
exit 1
fi
# Unmount any existing installations to prevent multiple unnecessary mounts.
$UMOUNT
base_path="$MOUNTED_APK_PATH"
chcon $SELINUX_CONTEXT ${'$'}base_path
# Use Magisk mirror, if possible.
if command -v magisk &> /dev/null; then
MIRROR="${'$'}(magisk --path)/.magisk/mirror"
fi
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
# Kill the app to force it to restart the mounted APK in case it's currently running.
$KILL
""".trimIndent()
/**
* Replaces the [PLACEHOLDER] with the given [replacement].
*
* @param replacement The replacement to use.
* @return The replaced string.
*/
operator fun String.invoke(replacement: String) = replace(PLACEHOLDER, replacement)
}

View File

@@ -0,0 +1,11 @@
package app.revanced.library.installation.installer
/**
* [Installation] of an apk file.
*
* @param apkFilePath The apk file path.
*/
@Suppress("MemberVisibilityCanBePrivate")
open class Installation internal constructor(
val apkFilePath: String,
)

View File

@@ -0,0 +1,53 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.installer.Installer.Apk
import java.io.File
import java.util.logging.Logger
/**
* [Installer] for installing and uninstalling [Apk] files.
*
* @param TInstallerResult The type of the result of the installation.
* @param TInstallation The type of the installation.
*/
abstract class Installer<TInstallerResult, TInstallation : Installation> internal constructor() {
/**
* The [Logger].
*/
protected val logger: Logger = Logger.getLogger(this::class.java.name)
/**
* Installs the [Apk] file.
*
* @param apk The [Apk] file.
*
* @return The result of the installation.
*/
abstract suspend fun install(apk: Apk): TInstallerResult
/**
* Uninstalls the package.
*
* @param packageName The package name.
*
* @return The result of the uninstallation.
*/
abstract suspend fun uninstall(packageName: String): TInstallerResult
/**
* Gets the current installation or null if not installed.
*
* @param packageName The package name.
*
* @return The installation.
*/
abstract suspend fun getInstallation(packageName: String): TInstallation?
/**
* Apk file for [Installer].
*
* @param file The [Apk] file.
* @param packageName The package name of the [Apk] file.
*/
class Apk(val file: File, val packageName: String? = null)
}

View File

@@ -0,0 +1,15 @@
package app.revanced.library.installation.installer
/**
* [RootInstallation] of the apk file that is mounted to [installedApkFilePath] with root permissions.
*
* @param installedApkFilePath The installed apk file path or null if the apk is not installed.
* @param apkFilePath The mounting apk file path.
* @param mounted Whether the apk is mounted to [installedApkFilePath].
*/
@Suppress("MemberVisibilityCanBePrivate")
class RootInstallation internal constructor(
val installedApkFilePath: String?,
apkFilePath: String,
val mounted: Boolean,
) : Installation(apkFilePath)

View File

@@ -0,0 +1,134 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.command.ShellCommandRunner
import app.revanced.library.installation.installer.Constants.CREATE_INSTALLATION_PATH
import app.revanced.library.installation.installer.Constants.DELETE
import app.revanced.library.installation.installer.Constants.EXISTS
import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH
import app.revanced.library.installation.installer.Constants.INSTALL_MOUNT_SCRIPT
import app.revanced.library.installation.installer.Constants.KILL
import app.revanced.library.installation.installer.Constants.MOUNTED_APK_PATH
import app.revanced.library.installation.installer.Constants.MOUNT_APK
import app.revanced.library.installation.installer.Constants.MOUNT_GREP
import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT
import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT_PATH
import app.revanced.library.installation.installer.Constants.RESTART
import app.revanced.library.installation.installer.Constants.TMP_FILE_PATH
import app.revanced.library.installation.installer.Constants.UMOUNT
import app.revanced.library.installation.installer.Constants.invoke
import app.revanced.library.installation.installer.Installer.Apk
import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException
import java.io.File
/**
* [RootInstaller] for installing and uninstalling [Apk] files using root permissions by mounting.
*
* @param shellCommandRunnerSupplier A supplier for the [ShellCommandRunner] to use.
*
* @throws NoRootPermissionException If the device does not have root permission.
*/
@Suppress("MemberVisibilityCanBePrivate")
abstract class RootInstaller internal constructor(
shellCommandRunnerSupplier: (RootInstaller) -> ShellCommandRunner,
) : Installer<RootInstallerResult, RootInstallation>() {
/**
* The command runner used to run commands on the device.
*/
@Suppress("LeakingThis")
protected val shellCommandRunner = shellCommandRunnerSupplier(this)
init {
if (!shellCommandRunner.hasRootPermission()) throw NoRootPermissionException()
}
/**
* Installs the given [apk] by mounting.
*
* @param apk The [Apk] to install.
*
* @throws PackageNameRequiredException If the [Apk] does not have a package name.
*/
override suspend fun install(apk: Apk): RootInstallerResult {
logger.info("Installing ${apk.packageName} by mounting")
val packageName = apk.packageName?.also { it.assertInstalled() } ?: throw PackageNameRequiredException()
// Setup files.
apk.file.move(TMP_FILE_PATH)
CREATE_INSTALLATION_PATH().waitFor()
MOUNT_APK(packageName)().waitFor()
// Install and run.
TMP_FILE_PATH.write(MOUNT_SCRIPT(packageName))
INSTALL_MOUNT_SCRIPT(packageName)().waitFor()
MOUNT_SCRIPT_PATH(packageName)().waitFor()
RESTART(packageName)()
DELETE(TMP_FILE_PATH)()
return RootInstallerResult.SUCCESS
}
override suspend fun uninstall(packageName: String): RootInstallerResult {
logger.info("Uninstalling $packageName by unmounting")
UMOUNT(packageName)()
DELETE(MOUNTED_APK_PATH)(packageName)()
DELETE(MOUNT_SCRIPT_PATH)(packageName)()
DELETE(TMP_FILE_PATH)() // Remove residual.
KILL(packageName)()
return RootInstallerResult.SUCCESS
}
override suspend fun getInstallation(packageName: String): RootInstallation? {
val patchedApkPath = MOUNTED_APK_PATH(packageName)
val patchedApkExists = EXISTS(patchedApkPath)().exitCode == 0
if (patchedApkExists) return null
return RootInstallation(
INSTALLED_APK_PATH(packageName)().output.ifEmpty { null },
patchedApkPath,
MOUNT_GREP(patchedApkPath)().exitCode == 0,
)
}
/**
* Runs a command on the device.
*/
protected operator fun String.invoke() = shellCommandRunner(this)
/**
* Moves the given file to the given [targetFilePath].
*
* @param targetFilePath The target file path.
*/
protected fun File.move(targetFilePath: String) = shellCommandRunner.move(this, targetFilePath)
/**
* Writes the given [content] to the file.
*
* @param content The content of the file.
*/
protected fun String.write(content: String) = shellCommandRunner.write(content.byteInputStream(), this)
/**
* Asserts that the package is installed.
*
* @throws FailedToFindInstalledPackageException If the package is not installed.
*/
private fun String.assertInstalled() {
if (INSTALLED_APK_PATH(this)().output.isEmpty()) {
throw FailedToFindInstalledPackageException(this)
}
}
internal class FailedToFindInstalledPackageException internal constructor(packageName: String) : Exception("Failed to find installed package \"$packageName\" because no activity was found")
internal class PackageNameRequiredException internal constructor() : Exception("Package name is required")
internal class NoRootPermissionException internal constructor() : Exception("No root permission")
}

View File

@@ -0,0 +1,20 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.installer.Installer.Apk
/**
* The result of installing or uninstalling an [Apk] with root permissions using [RootInstaller].
*
* @see RootInstaller
*/
enum class RootInstallerResult {
/**
* The result of installing an [Apk] successfully.
*/
SUCCESS,
/**
* The result of installing an [Apk] unsuccessfully.
*/
FAILURE,
}

View File

@@ -0,0 +1,34 @@
package app.revanced.library.installation.installer
import se.vidstige.jadb.JadbConnection
import java.util.logging.Logger
/**
* Gets the device with the given serial.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
* @param logger The logger.
* @return The device.
* @throws DeviceNotFoundException If no device with the given serial is found.
*/
internal fun getDevice(
deviceSerial: String? = null,
logger: Logger,
) = with(JadbConnection().devices) {
if (isEmpty()) throw DeviceNotFoundException()
deviceSerial?.let {
firstOrNull { it.serial == deviceSerial } ?: throw DeviceNotFoundException(
deviceSerial,
)
} ?: first().also {
logger.warning("No device serial supplied. Using device with serial ${it.serial}")
}
}!!
class DeviceNotFoundException internal constructor(deviceSerial: String? = null) :
Exception(
deviceSerial?.let {
"The device with the ADB device serial \"$deviceSerial\" can not be found"
} ?: "No ADB device found",
)

View File

@@ -5,15 +5,17 @@ import java.util.logging.Level
import java.util.logging.LogRecord
import java.util.logging.SimpleFormatter
@Suppress("MemberVisibilityCanBePrivate")
@Suppress("MemberVisibilityCanBePrivate", "unused")
object Logger {
/**
* Rules for allowed loggers.
*/
private val allowedLoggersRules =
arrayOf<String.() -> Boolean>(
{ startsWith("app.revanced") }, // ReVanced loggers.
{ this == "" }, // Logs warnings when compiling resources (Logger in class brut.util.OS).
// ReVanced loggers.
{ startsWith("app.revanced") },
// Logs warnings when compiling resources (Logger in class brut.util.OS).
{ this == "" },
)
private val rootLogger = java.util.logging.Logger.getLogger("")

View File

@@ -1,27 +1,19 @@
package app.revanced.library
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 app.revanced.patcher.patch.*
import kotlin.test.Test
import kotlin.test.assertEquals
internal object PatchUtilsTest {
internal class MostCommonCompatibleVersionsTest {
private val patches =
arrayOf(
newPatch("some.package", setOf("a")) { stringPatchOption("string", "value") },
newPatch("some.package", setOf("a")) { stringOption("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")) { booleanPatchOption("bool", true) },
newPatch("some.other.package", setOf("b", "c")) { booleanOption("bool", true) },
newPatch("some.other.package", setOf("b", "c", "d")),
newPatch("some.other.other.package") { intArrayPatchOption("intArray", arrayOf(1, 2, 3)) },
newPatch("some.other.other.package") { intsOption("intArray", listOf(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),
@@ -141,38 +133,24 @@ 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,
patches: Set<Patch<*>>,
compatiblePackageNames: Set<String>?,
countUnusedPatches: Boolean = false,
) = assertEquals(
expected,
PatchUtils.getMostCommonCompatibleVersions(patches, compatiblePackageNames, countUnusedPatches),
patches.mostCommonCompatibleVersions(compatiblePackageNames, countUnusedPatches),
)
private fun assertEqualsVersion(
expected: String?,
patches: PatchSet,
patches: Set<Patch<*>>,
compatiblePackageName: String,
) {
assertEquals(
expected,
PatchUtils.getMostCommonCompatibleVersions(patches, setOf(compatiblePackageName))
patches.mostCommonCompatibleVersions(setOf(compatiblePackageName))
.entries.firstOrNull()?.value?.keys?.firstOrNull(),
)
}
@@ -181,19 +159,23 @@ internal object PatchUtilsTest {
packageName: String,
versions: Set<String>? = null,
use: Boolean = true,
options: Patch<*>.() -> Unit = {},
) = object : BytecodePatch(
options: PatchBuilder<*>.() -> Unit = {},
) = bytecodePatch(
name = "test",
compatiblePackages = setOf(CompatiblePackage(packageName, versions?.toSet())),
use = use,
) {
init {
options()
if (versions == null) {
compatibleWith(packageName)
} else {
compatibleWith(
if (versions.isEmpty()) {
packageName()
} else {
packageName(*versions.toTypedArray())
},
)
}
override fun execute(context: BytecodeContext) {}
// Needed to make the patches unique.
override fun equals(other: Any?) = false
options()
}
}

View File

@@ -0,0 +1,36 @@
package app.revanced.library
import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.stringOption
import kotlin.test.Test
import kotlin.test.assertEquals
class OptionsTest {
@Test
fun `serializes and deserializes`() {
val options = mapOf(
"Test patch" to mapOf("key1" to "test", "key2" to false),
)
val patch = bytecodePatch("Test patch") {
stringOption("key1")
booleanOption("key2", true)
}
val duplicatePatch = bytecodePatch("Test patch") {
stringOption("key1")
}
val unnamedPatch = bytecodePatch {
booleanOption("key1")
}
setOf(patch, duplicatePatch, unnamedPatch).setOptions(options)
assert(patch.options["key1"].value == "test")
assert(patch.options["key2"].value == false)
assertEquals(patch.options["key1"].value, duplicatePatch.options["key1"].value)
assert(unnamedPatch.options["key1"].value == null)
}
}

View File

@@ -0,0 +1,58 @@
package app.revanced.library
import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.floatsOption
import app.revanced.patcher.patch.stringOption
import kotlinx.serialization.json.*
import java.io.ByteArrayOutputStream
import kotlin.test.Test
import kotlin.test.assertIs
class SerializationTest {
private val testPatch = bytecodePatch("Test patch") {
compatibleWith("com.example.package"("1.0.0"))
compatibleWith("com.example.package2")
dependsOn(bytecodePatch(), bytecodePatch())
stringOption("key1", null, null, "title1", "description1")
booleanOption("key2", true, null, "title2", "description2")
floatsOption("key3", listOf(1.0f), mapOf("list" to listOf(1f)), "title3", "description3")
}
private var patches = setOf(testPatch)
@Test
fun `serializes and deserializes`() {
val serializedJson = ByteArrayOutputStream().apply { patches.serializeTo(this) }.toString()
val deserializedJson = Json.parseToJsonElement(serializedJson)
// Test patch serialization.
assertIs<JsonArray>(deserializedJson)
val deserializedPatch = deserializedJson[0].jsonObject
assert(deserializedPatch["name"]!!.jsonPrimitive.content == "Test patch")
assert(deserializedPatch["compatiblePackages"]!!.jsonObject.size == 2) {
"The patch should be compatible with two packages."
}
assert(deserializedPatch["dependencies"]!!.jsonArray.size == 2) {
"Even though the dependencies are named the same, they are different objects."
}
// Test option serialization.
val options = deserializedPatch["options"]!!.jsonArray
assert(options.size == 3) { "The patch should have three options." }
assert(options[0].jsonObject["title"]!!.jsonPrimitive.content == "title1")
assert(options[0].jsonObject["default"]!!.jsonPrimitive.contentOrNull == null)
assert(options[1].jsonObject["default"]!!.jsonPrimitive.boolean)
assert(options[2].jsonObject["values"]!!.jsonObject["list"]!!.jsonArray[0].jsonPrimitive.float == 1f)
}
}

View File

@@ -1,120 +0,0 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package app.revanced.library
import app.revanced.library.Options.Patch.Option
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
@Suppress("unused")
object Options {
private val logger = Logger.getLogger(Options::class.java.name)
private val mapper = jacksonObjectMapper()
/**
* Serializes the options for a set of patches.
*
* @param patches The set of patches to serialize.
* @param prettyPrint Whether to pretty print the JSON.
* @return The JSON string containing the options.
*/
fun serialize(
patches: PatchSet,
prettyPrint: Boolean = false,
): String =
patches
.filter { it.options.any() }
.map { patch ->
Patch(
patch.name!!,
patch.options.values.map { option ->
val optionValue =
try {
option.value
} catch (e: PatchOptionException) {
logger.warning("Using default option value for the ${patch.name} patch: ${e.message}")
option.default
}
Option(option.key, optionValue)
},
)
}
// See https://github.com/revanced/revanced-patches/pull/2434/commits/60e550550b7641705e81aa72acfc4faaebb225e7.
.distinctBy { it.patchName }
.let {
if (prettyPrint) {
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(it)
} else {
mapper.writeValueAsString(it)
}
}
/**
* Deserializes the options to a set of patches.
*
* @param json The JSON string containing the options.
* @return A set of [Patch]s.
* @see Patch
*/
fun deserialize(json: String): Array<Patch> = mapper.readValue(json, Array<Patch>::class.java)
/**
* Sets the options for a set of patches.
*
* @param json The JSON string containing the options.
*/
fun PatchSet.setOptions(json: String) {
filter { it.options.any() }.let { patches ->
if (patches.isEmpty()) return
val jsonPatches =
deserialize(json).associate {
it.patchName to it.options.associate { option -> option.key to option.value }
}
patches.forEach { patch ->
jsonPatches[patch.name]?.let { jsonPatchOptions ->
jsonPatchOptions.forEach { (option, value) ->
try {
patch.options[option] = value
} catch (e: PatchOptionException) {
logger.warning("Could not set option value for the ${patch.name} patch: ${e.message}")
}
}
}
}
}
}
/**
* Sets the options for a set of patches.
*
* @param file The file containing the JSON string containing the options.
* @see setOptions
*/
fun PatchSet.setOptions(file: File) = setOptions(file.readText())
/**
* Data class for a patch and its [Option]s.
*
* @property patchName The name of the patch.
* @property options The [Option]s for the patch.
*/
class Patch internal constructor(
val patchName: String,
val options: List<Option>,
) {
/**
* Data class for patch option.
*
* @property key The name of the option.
* @property value The value of the option.
*/
class Option internal constructor(val key: String, val value: Any?)
}
}

View File

@@ -1,169 +0,0 @@
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
typealias Count = Int
typealias VersionMap = LinkedHashMap<Version, Count>
typealias PackageNameMap = Map<PackageName, VersionMap>
/**
* Utility functions for working with patches.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object PatchUtils {
/**
* Get the count of versions for each compatible package from a supplied set of [patches] ordered by the most common version.
*
* @param patches The set of patches to check.
* @param packageNames The names of the compatible packages to include. If null, all packages will be included.
* @param countUnusedPatches Whether to count patches that are not used.
* @return A map of package names to a map of versions to their count.
*/
fun getMostCommonCompatibleVersions(
patches: PatchSet,
packageNames: Set<String>? = null,
countUnusedPatches: Boolean = false,
): PackageNameMap =
buildMap {
fun filterWantedPackages(compatiblePackages: Iterable<Patch.CompatiblePackage>): Iterable<Patch.CompatiblePackage> {
val wantedPackages = packageNames?.toHashSet() ?: return compatiblePackages
return compatiblePackages.filter { it.name in wantedPackages }
}
patches
.filter { it.use || countUnusedPatches }
.flatMap { it.compatiblePackages ?: emptyList() }
.let(::filterWantedPackages)
.forEach { compatiblePackage ->
if (compatiblePackage.versions?.isEmpty() == true) {
return@forEach
}
val versionMap = getOrPut(compatiblePackage.name) { linkedMapOf() }
compatiblePackage.versions?.let { versions ->
versions.forEach { version ->
versionMap[version] = versionMap.getOrDefault(version, 0) + 1
}
}
}
// Sort the version maps by the most common version.
forEach { (packageName, versionMap) ->
this[packageName] =
versionMap
.asIterable()
.sortedWith(compareByDescending { it.value })
.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,
)
}
}
}
}
}

View File

@@ -1,183 +0,0 @@
package app.revanced.library.adb
import app.revanced.library.adb.AdbManager.Apk
import app.revanced.library.adb.Constants.CREATE_DIR
import app.revanced.library.adb.Constants.DELETE
import app.revanced.library.adb.Constants.GET_INSTALLED_PATH
import app.revanced.library.adb.Constants.INSTALLATION_PATH
import app.revanced.library.adb.Constants.INSTALL_MOUNT_SCRIPT
import app.revanced.library.adb.Constants.INSTALL_PATCHED_APK
import app.revanced.library.adb.Constants.KILL
import app.revanced.library.adb.Constants.MOUNT_SCRIPT
import app.revanced.library.adb.Constants.MOUNT_SCRIPT_PATH
import app.revanced.library.adb.Constants.PATCHED_APK_PATH
import app.revanced.library.adb.Constants.PLACEHOLDER
import app.revanced.library.adb.Constants.RESTART
import app.revanced.library.adb.Constants.TMP_PATH
import app.revanced.library.adb.Constants.UMOUNT
import se.vidstige.jadb.JadbConnection
import se.vidstige.jadb.JadbDevice
import se.vidstige.jadb.managers.Package
import se.vidstige.jadb.managers.PackageManager
import java.io.File
import java.util.logging.Logger
/**
* Adb manager. Used to install and uninstall [Apk] files.
*
* @param deviceSerial The serial of the device. If null, the first connected device will be used.
*/
sealed class AdbManager private constructor(deviceSerial: String?) {
protected val logger: Logger = Logger.getLogger(AdbManager::class.java.name)
protected val device =
with(JadbConnection().devices) {
if (isEmpty()) throw DeviceNotFoundException()
deviceSerial?.let {
firstOrNull { it.serial == deviceSerial } ?: throw DeviceNotFoundException(deviceSerial)
} ?: first().also {
logger.warning("No device serial supplied. Using device with serial ${it.serial}")
}
}!!
init {
logger.fine("Connected to ${device.serial}")
}
/**
* Installs the [Apk] file.
*
* @param apk The [Apk] file.
*/
open fun install(apk: Apk) {
logger.info("Finished installing ${apk.file.name}")
}
/**
* Uninstalls the package.
*
* @param packageName The package name.
*/
open fun uninstall(packageName: String) {
logger.info("Finished uninstalling $packageName")
}
companion object {
/**
* Gets an [AdbManager] for the supplied device serial.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
* @param root Whether to use root or not.
* @return The [AdbManager].
* @throws DeviceNotFoundException If the device can not be found.
*/
fun getAdbManager(
deviceSerial: String? = null,
root: Boolean = false,
): AdbManager = if (root) RootAdbManager(deviceSerial) else UserAdbManager(deviceSerial)
}
/**
* Adb manager for rooted devices.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*/
class RootAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) {
init {
if (!device.hasSu()) throw IllegalArgumentException("Root required on ${device.serial}. Task failed")
}
override fun install(apk: Apk) {
logger.info("Installing by mounting")
val packageName = apk.packageName ?: throw PackageNameRequiredException()
device.run(GET_INSTALLED_PATH, packageName).inputStream.bufferedReader().readLine().let { line ->
if (line != null) return@let
throw throw FailedToFindInstalledPackageException(packageName)
}
device.push(apk.file, TMP_PATH)
device.run("$CREATE_DIR $INSTALLATION_PATH").waitFor()
device.run(INSTALL_PATCHED_APK, packageName).waitFor()
device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement(packageName))
device.run(INSTALL_MOUNT_SCRIPT, packageName).waitFor()
device.run(MOUNT_SCRIPT_PATH, packageName).waitFor()
device.run(RESTART, packageName)
device.run(DELETE, TMP_PATH)
super.install(apk)
}
override fun uninstall(packageName: String) {
logger.info("Uninstalling $packageName by unmounting")
device.run(UMOUNT, packageName)
device.run(DELETE.applyReplacement(PATCHED_APK_PATH), packageName)
device.run(DELETE, MOUNT_SCRIPT_PATH.applyReplacement(packageName))
device.run(DELETE, TMP_PATH)
device.run(KILL, packageName)
super.uninstall(packageName)
}
companion object Utils {
private fun JadbDevice.run(
command: String,
with: String,
) = run(command.applyReplacement(with))
private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
}
}
/**
* Adb manager for non-rooted devices.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*/
class UserAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) {
private val packageManager = PackageManager(device)
override fun install(apk: Apk) {
logger.info("Installing ${apk.file.name}")
PackageManager(device).install(apk.file)
super.install(apk)
}
override fun uninstall(packageName: String) {
logger.info("Uninstalling $packageName")
packageManager.uninstall(Package(packageName))
super.uninstall(packageName)
}
}
/**
* Apk file for [AdbManager].
*
* @param file The [Apk] file.
* @param packageName The package name of the [Apk] file.
*/
class Apk(val file: File, val packageName: String? = null)
class DeviceNotFoundException internal constructor(deviceSerial: String? = null) :
Exception(
deviceSerial?.let {
"The device with the ADB device serial \"$deviceSerial\" can not be found"
} ?: "No ADB device found",
)
class FailedToFindInstalledPackageException internal constructor(packageName: String) :
Exception("Failed to find installed package \"$packageName\" because no activity was found")
class PackageNameRequiredException internal constructor() :
Exception("Package name is required")
}

View File

@@ -1,35 +0,0 @@
package app.revanced.library.adb
import se.vidstige.jadb.JadbDevice
import se.vidstige.jadb.RemoteFile
import se.vidstige.jadb.ShellProcessBuilder
import java.io.File
internal fun JadbDevice.buildCommand(
command: String,
su: Boolean = true,
): ShellProcessBuilder {
if (su) return shellProcessBuilder("su -c \'$command\'")
val args = command.split(" ") as ArrayList<String>
val cmd = args.removeFirst()
return shellProcessBuilder(cmd, *args.toTypedArray())
}
internal fun JadbDevice.run(
command: String,
su: Boolean = true,
) = this.buildCommand(command, su).start()
internal fun JadbDevice.hasSu() = this.run("whoami", true).waitFor() == 0
internal fun JadbDevice.push(
file: File,
targetFilePath: String,
) = push(file, RemoteFile(targetFilePath))
internal fun JadbDevice.createFile(
targetFile: String,
content: String,
) = push(content.byteInputStream(), System.currentTimeMillis(), 644, RemoteFile(targetFile))

View File

@@ -1,50 +0,0 @@
package app.revanced.library.adb
internal object Constants {
internal const val PLACEHOLDER = "PLACEHOLDER"
internal const val TMP_PATH = "/data/local/tmp/revanced.tmp"
internal const val INSTALLATION_PATH = "/data/adb/revanced/"
internal const val PATCHED_APK_PATH = "$INSTALLATION_PATH$PLACEHOLDER.apk"
internal const val MOUNT_SCRIPT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
internal const val DELETE = "rm -rf $PLACEHOLDER"
internal const val CREATE_DIR = "mkdir -p"
internal const val RESTART = "am start -S $PLACEHOLDER"
internal const val KILL = "am force-stop $PLACEHOLDER"
internal const val GET_INSTALLED_PATH = "pm path $PLACEHOLDER"
internal const val INSTALL_PATCHED_APK =
"base_path=\"$PATCHED_APK_PATH\" && " +
"mv $TMP_PATH ${'$'}base_path && " +
"chmod 644 ${'$'}base_path && " +
"chown system:system ${'$'}base_path && " +
"chcon u:object_r:apk_data_file:s0 ${'$'}base_path"
internal const val UMOUNT =
"grep $PLACEHOLDER /proc/mounts | while read -r line; do echo ${'$'}line | cut -d ' ' -f 2 | sed 's/apk.*/apk/' | xargs -r umount -l; done"
internal const val INSTALL_MOUNT_SCRIPT = "mv $TMP_PATH $MOUNT_SCRIPT_PATH && chmod +x $MOUNT_SCRIPT_PATH"
internal val MOUNT_SCRIPT =
"""
#!/system/bin/sh
MAGISKTMP="$( magisk --path )" || MAGISKTMP=/sbin
MIRROR="${'$'}MAGISKTMP/.magisk/mirror"
until [ "$( getprop sys.boot_completed )" = 1 ]; do sleep 3; done
until [ -d "/sdcard/Android" ]; do sleep 1; done
# Unmount any existing mount as a safety measure
$UMOUNT
base_path="$PATCHED_APK_PATH"
stock_path=$( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
chcon u:object_r:apk_data_file:s0 ${'$'}base_path
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
# Kill the app to force it to restart the mounted APK in case it's already running
$KILL
""".trimIndent()
}

View File

@@ -1,48 +0,0 @@
package app.revanced.library
import app.revanced.library.Options.setOptions
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import org.junit.jupiter.api.MethodOrderer
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestMethodOrder
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
internal object PatchOptionsTest {
private var patches = setOf(PatchOptionsTestPatch)
@Test
@Order(1)
fun serializeTest() {
assert(SERIALIZED_JSON == Options.serialize(patches))
}
@Test
@Order(2)
fun loadOptionsTest() {
patches.setOptions(CHANGED_JSON)
assert(PatchOptionsTestPatch.option1 == "test")
assert(PatchOptionsTestPatch.option2 == false)
}
private const val SERIALIZED_JSON =
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":null},{\"key\":\"key2\",\"value\":true}]}]"
private const val CHANGED_JSON =
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":\"test\"},{\"key\":\"key2\",\"value\":false}]}]"
@Patch("PatchOptionsTestPatch")
object PatchOptionsTestPatch : BytecodePatch(emptySet()) {
var option1 by stringPatchOption("key1", null, null, "title1", "description1")
var option2 by booleanPatchOption("key2", true, null, "title2", "description2")
override fun execute(context: BytecodeContext) {
// Do nothing
}
}
}