Compare commits

..

133 Commits

Author SHA1 Message Date
oSumAtrIX
42de60e95b feat: Add networking module 2024-04-17 05:22:41 +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
semantic-release-bot
f0b8bc5438 chore(release): 2.0.0 [skip ci]
# [2.0.0](https://github.com/ReVanced/revanced-library/compare/v1.5.0...v2.0.0) (2024-02-15)

### Bug Fixes

* Map dependencies from `KClass` into `String` to fix serialization ([57e36ab](57e36ab5c1))
* Use the JVM name instead of the value from `KClass#toString` ([d18e436](d18e436de1))

### Features

* Use `apkzlib` instead of own implementations and bump ReVanced Patcher ([3aa6dc2](3aa6dc223a))

### BREAKING CHANGES

* This commit removes deprecated APIs and bumps ReVanced Patcher. Because of it's changes, `apkzlib` is now used instead of own implementations of `ZipFile`
2024-02-15 01:24:23 +00:00
oSumAtrIX
806f45c77f chore: Merge branch dev to main (#24) 2024-02-15 02:22:42 +01:00
semantic-release-bot
259bcd04c9 chore(release): 2.0.0-dev.1 [skip ci]
# [2.0.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.5.1-dev.2...v2.0.0-dev.1) (2024-02-14)

### Features

* Use `apkzlib` instead of own implementations and bump ReVanced Patcher ([3aa6dc2](3aa6dc223a))

### BREAKING CHANGES

* This commit removes deprecated APIs and bumps ReVanced Patcher. Because of it's changes, `apkzlib` is now used instead of own implementations of `ZipFile`
2024-02-14 03:25:29 +00:00
oSumAtrIX
0d040dcfd0 chore: Fix builds 2024-02-14 04:23:49 +01:00
oSumAtrIX
13af960de1 chore: Dump API 2024-02-14 04:21:57 +01:00
oSumAtrIX
d21fd5798c build: Bump Gradle 2024-02-14 02:51:09 +01:00
oSumAtrIX
1c348f3621 build: Bump dependencies 2024-02-14 02:51:09 +01:00
oSumAtrIX
0c02680b70 build: Publish to GitHub Packages
Because Jitpack can not sign artifacts.
2024-02-14 02:51:09 +01:00
oSumAtrIX
b86b8aacea build: Sign publication artifacts 2024-02-14 02:51:08 +01:00
oSumAtrIX
3aa6dc223a feat: Use apkzlib instead of own implementations and bump ReVanced Patcher
BREAKING CHANGE: This commit removes deprecated APIs and bumps ReVanced Patcher. Because of it's changes, `apkzlib` is now used instead of own implementations of `ZipFile`
2024-02-14 02:31:44 +01:00
semantic-release-bot
c664a6eaed chore(release): 1.5.1-dev.2 [skip ci]
## [1.5.1-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.5.1-dev.1...v1.5.1-dev.2) (2024-02-08)

### Bug Fixes

* Use the JVM name instead of the value from `KClass#toString` ([d18e436](d18e436de1))
2024-02-08 02:51:47 +00:00
oSumAtrIX
d18e436de1 fix: Use the JVM name instead of the value from KClass#toString 2024-02-08 03:50:15 +01:00
semantic-release-bot
9ff06a2f1b chore(release): 1.5.1-dev.1 [skip ci]
## [1.5.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.5.0...v1.5.1-dev.1) (2024-02-08)

### Bug Fixes

* Map dependencies from `KClass` into `String` to fix serialization ([57e36ab](57e36ab5c1))
2024-02-08 02:10:11 +00:00
oSumAtrIX
57e36ab5c1 fix: Map dependencies from KClass into String to fix serialization 2024-02-08 03:08:10 +01:00
oSumAtrIX
8d934bde00 ci: Use latest Node.js LTS version to fix builds 2024-01-27 00:47:29 +01:00
oSumAtrIX
d1a7699256 ci: Add dependabot 2024-01-26 01:46:20 +01:00
oSumAtrIX
e08d0ad8f3 build: Bump dependencies 2024-01-26 01:46:20 +01:00
oSumAtrIX
4a14c5ccaa build: Bump dependencies 2024-01-10 09:32:58 +01:00
semantic-release-bot
4bbe2fa150 chore(release): 1.5.0 [skip ci]
# [1.5.0](https://github.com/ReVanced/revanced-library/compare/v1.4.0...v1.5.0) (2023-12-28)

### Features

* Add JSON de- and serialization of patches ([ecff6fe](ecff6fe0d3))
* Improve mount reliability by unmounting existing mounts and killing running apps ([9fda407](9fda407441))
2023-12-28 21:35:25 +00:00
oSumAtrIX
938eac53b1 chore: Merge branch dev to main (#20) 2023-12-28 22:34:05 +01:00
Pun Butrach
ab004b91f0 docs: Update to latest branding changes (#23) 2023-12-10 13:31:20 +01:00
semantic-release-bot
b637e0d7d7 chore(release): 1.5.0-dev.2 [skip ci]
# [1.5.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.5.0-dev.1...v1.5.0-dev.2) (2023-12-07)

### Features

* Improve mount reliability by unmounting existing mounts and killing running apps ([9fda407](9fda407441))
2023-12-07 15:41:07 +00:00
oSumAtrIX
9fda407441 feat: Improve mount reliability by unmounting existing mounts and killing running apps 2023-12-07 16:39:51 +01:00
semantic-release-bot
44f7c13a77 chore(release): 1.5.0-dev.1 [skip ci]
# [1.5.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.4.0...v1.5.0-dev.1) (2023-12-07)

### Features

* Add JSON de- and serialization of patches ([ecff6fe](ecff6fe0d3))
2023-12-07 14:16:31 +00:00
oSumAtrIX
ecff6fe0d3 feat: Add JSON de- and serialization of patches 2023-12-07 15:14:59 +01:00
oSumAtrIX
3515e331ac refactor: Simplify code 2023-12-07 02:45:49 +01:00
oSumAtrIX
a921c40306 build: Bump dependencies 2023-12-07 02:44:36 +01:00
oSumAtrIX
b6e1de5eaf chore: Use correct variable keyword 2023-12-07 02:38:52 +01:00
oSumAtrIX
bf5780e2f7 chore: Improve wording 2023-12-07 02:29:46 +01:00
oSumAtrIX
99e90c02ed refactor: Remove unused typealias 2023-12-01 23:06:29 +01:00
semantic-release-bot
ac3917fecc chore(release): 1.4.0 [skip ci]
# [1.4.0](https://github.com/ReVanced/revanced-library/compare/v1.3.0...v1.4.0) (2023-11-27)

### Bug Fixes

* Differentiate no package compatibility to any version compatibility ([762b7e3](762b7e3bc0))
* Sort the version maps by the most common version ([e4be6db](e4be6dbccd))

### Features

* Add `PatchUtils#getMostCommonCompatibleVersions` utility function ([c5f3536](c5f3536cbb))
* Allow getting most common compatible versions for all packages ([96845ba](96845ba265))
2023-11-27 21:50:58 +00:00
oSumAtrIX
98b7b347cb chore: Merge branch dev to main (#19) 2023-11-27 22:49:52 +01:00
semantic-release-bot
cdf94ffeca chore(release): 1.4.0-dev.2 [skip ci]
# [1.4.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.4.0-dev.1...v1.4.0-dev.2) (2023-11-27)

### Bug Fixes

* Differentiate no package compatibility to any version compatibility ([762b7e3](762b7e3bc0))
* Sort the version maps by the most common version ([e4be6db](e4be6dbccd))

### Features

* Allow getting most common compatible versions for all packages ([96845ba](96845ba265))
2023-11-27 21:43:22 +00:00
oSumAtrIX
37434cf4a4 build: Bump dependencies 2023-11-27 22:40:07 +01:00
oSumAtrIX
73c97abedd chore: Lint code 2023-11-27 22:40:00 +01:00
oSumAtrIX
762b7e3bc0 fix: Differentiate no package compatibility to any version compatibility 2023-11-27 22:30:59 +01:00
oSumAtrIX
e4be6dbccd fix: Sort the version maps by the most common version 2023-11-27 21:21:25 +01:00
oSumAtrIX
96845ba265 feat: Allow getting most common compatible versions for all packages 2023-11-27 21:03:23 +01:00
semantic-release-bot
34171b534b chore(release): 1.4.0-dev.1 [skip ci]
# [1.4.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.3.0...v1.4.0-dev.1) (2023-11-27)

### Features

* Add `PatchUtils#getMostCommonCompatibleVersions` utility function ([c5f3536](c5f3536cbb))
2023-11-27 01:26:17 +00:00
oSumAtrIX
c5f3536cbb feat: Add PatchUtils#getMostCommonCompatibleVersions utility function 2023-11-27 02:24:55 +01:00
oSumAtrIX
893274074b refactor: Do not escape unnecessary 2023-11-27 02:24:55 +01:00
semantic-release-bot
b6c09d42ae chore(release): 1.3.0 [skip ci]
# [1.3.0](https://github.com/ReVanced/revanced-library/compare/v1.2.0...v1.3.0) (2023-11-26)

### Bug Fixes

* Add missing log when calling `UserAdbManager#install` ([90b612b](90b612bee8))
* Delete mount script ([4fe0fb0](4fe0fb0a61))

### Features

* Increase certainty of the possibility to mount ([10f8cd1](10f8cd1470))
* Select first Adb device, if none supplied automatically ([1a5f868](1a5f868ecd))
2023-11-26 04:35:19 +00:00
oSumAtrIX
fe8a2334e6 chore: Merge branch dev to main (#18) 2023-11-26 05:33:52 +01:00
oSumAtrIX
a9e5966145 chore: Lint code 2023-11-26 05:27:29 +01:00
semantic-release-bot
50c2b3921c chore(release): 1.3.0-dev.1 [skip ci]
# [1.3.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.2.1-dev.1...v1.3.0-dev.1) (2023-11-26)

### Bug Fixes

* Delete mount script ([4fe0fb0](4fe0fb0a61))

### Features

* Increase certainty of the possibility to mount ([10f8cd1](10f8cd1470))
* Select first Adb device, if none supplied automatically ([1a5f868](1a5f868ecd))
2023-11-26 04:19:57 +00:00
oSumAtrIX
10f8cd1470 feat: Increase certainty of the possibility to mount 2023-11-26 05:16:38 +01:00
oSumAtrIX
1a5f868ecd feat: Select first Adb device, if none supplied automatically 2023-11-26 05:11:58 +01:00
oSumAtrIX
969ca16b77 refactor: Simplify checking for installed app 2023-11-25 03:30:08 +01:00
oSumAtrIX
4fe0fb0a61 fix: Delete mount script 2023-11-25 03:16:55 +01:00
oSumAtrIX
462a35ddcd chore: Add heading to issue templates 2023-11-23 00:56:52 +01:00
oSumAtrIX
00e1895c0f build: Bump Gradle wrapper 2023-11-22 00:59:43 +01:00
oSumAtrIX
75d4c658b9 build: Use dedicated Gradle cache action 2023-11-22 00:45:42 +01:00
oSumAtrIX
2e54af8f82 build: Use Gradle build cache 2023-11-22 00:26:39 +01:00
oSumAtrIX
143c13b175 ci: Simplify cache paths 2023-11-22 00:11:17 +01:00
oSumAtrIX
ed8ab35d60 chore: Reword comment for first PR merge 2023-11-22 00:07:42 +01:00
oSumAtrIX
97bf79be27 chore: Add a newline between steps 2023-11-22 00:04:37 +01:00
oSumAtrIX
1cbf407ce2 chore: Notice about contribution guidelines in issue templates 2023-11-22 00:01:23 +01:00
oSumAtrIX
5b3a597ad7 chore: Remove irrelevant example from issue template 2023-11-21 23:57:39 +01:00
oSumAtrIX
b0fe1c8bfc chore: Do not use a line break in issue template sentence 2023-11-21 23:48:28 +01:00
oSumAtrIX
3331a3cc3a build: Bump dependencies 2023-11-21 23:43:48 +01:00
oSumAtrIX
a00146c6f1 chore: Update packages 2023-11-21 23:41:54 +01:00
semantic-release-bot
e23d21c982 chore(release): 1.2.1-dev.1 [skip ci]
## [1.2.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.2.0...v1.2.1-dev.1) (2023-11-13)

### Bug Fixes

* Add missing log when calling `UserAdbManager#install` ([90b612b](90b612bee8))
2023-11-13 00:20:46 +00:00
oSumAtrIX
90b612bee8 fix: Add missing log when calling UserAdbManager#install 2023-11-13 01:19:20 +01:00
semantic-release-bot
49e6081286 chore(release): 1.2.0 [skip ci]
# [1.2.0](https://github.com/ReVanced/revanced-library/compare/v1.1.5...v1.2.0) (2023-11-03)

### Bug Fixes

* Catch exceptions when serializing invalid patch options ([cd73bd3](cd73bd39ce))

### Features

* Log warnings when compiling resources ([1dc8e2e](1dc8e2e2eb))
* Use better log messages when handling exceptions ([5896968](5896968358))
2023-11-03 17:20:21 +00:00
oSumAtrIX
de4276630b chore: Merge branch dev to main (#15) 2023-11-03 18:18:22 +01:00
semantic-release-bot
9d39995f48 chore(release): 1.2.0-dev.2 [skip ci]
# [1.2.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.2.0-dev.1...v1.2.0-dev.2) (2023-11-03)

### Features

* Log warnings when compiling resources ([1dc8e2e](1dc8e2e2eb))
2023-11-03 00:24:24 +00:00
oSumAtrIX
1dc8e2e2eb feat: Log warnings when compiling resources
The logger in class `brut.util.OS` is responsible for logging the standard error output stream from invoking AAPT. Previously,only loggers with the name starting with `app.revanced` were shown.
2023-11-03 01:22:52 +01:00
oSumAtrIX
6555fcdffe chore: Fix builds from bumping dependencies 2023-11-03 01:21:23 +01:00
semantic-release-bot
8b89993505 chore(release): 1.2.0-dev.1 [skip ci]
# [1.2.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.5...v1.2.0-dev.1) (2023-10-31)

### Bug Fixes

* Catch exceptions when serializing invalid patch options ([cd73bd3](cd73bd39ce))

### Features

* Use better log messages when handling exceptions ([5896968](5896968358))
2023-10-31 13:30:54 +00:00
oSumAtrIX
5896968358 feat: Use better log messages when handling exceptions 2023-10-31 14:28:21 +01:00
oSumAtrIX
cd73bd39ce fix: Catch exceptions when serializing invalid patch options 2023-10-31 14:23:54 +01:00
oSumAtrIX
77416b6bf4 build: Bump dependencies 2023-10-31 14:18:41 +01:00
semantic-release-bot
4227a57fc7 chore(release): 1.1.5 [skip ci]
## [1.1.5](https://github.com/ReVanced/revanced-library/compare/v1.1.4...v1.1.5) (2023-10-23)
2023-10-23 00:06:24 +00:00
oSumAtrIX
cc3de84d25 chore: Merge branch dev to main (#14) 2023-10-23 02:04:28 +02:00
semantic-release-bot
0545341f9a chore(release): 1.1.5-dev.1 [skip ci]
## [1.1.5-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.4...v1.1.5-dev.1) (2023-10-23)
2023-10-23 00:03:37 +00:00
oSumAtrIX
035870e08d build: Simplify declaration of repositories 2023-10-22 23:37:19 +02:00
oSumAtrIX
b58ea35f6a build(Needs bump): Bump dependencies 2023-10-22 23:36:31 +02:00
oSumAtrIX
3f61394f0a build: Bump Kotlin Gradle plugin version 2023-10-22 16:14:45 +02:00
semantic-release-bot
9536413fa3 chore(release): 1.1.4 [skip ci]
## [1.1.4](https://github.com/ReVanced/revanced-library/compare/v1.1.3...v1.1.4) (2023-10-12)

### Bug Fixes

* Ask for root permissions before trying to use them ([aea1d69](aea1d69157))
* Execute ADB commands sequentially to fix mounting issues ([#12](https://github.com/ReVanced/revanced-library/issues/12)) ([fda3eca](fda3eca74f))
2023-10-12 19:49:25 +00:00
oSumAtrIX
7b1ca25a6c chore: Merge branch dev to main (#13) 2023-10-12 21:47:46 +02:00
semantic-release-bot
79640bcb8c chore(release): 1.1.4-dev.2 [skip ci]
## [1.1.4-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.1.4-dev.1...v1.1.4-dev.2) (2023-10-10)

### Bug Fixes

* Ask for root permissions before trying to use them ([aea1d69](aea1d69157))
2023-10-10 08:25:22 +00:00
oSumAtrIX
aea1d69157 fix: Ask for root permissions before trying to use them 2023-10-10 10:23:39 +02:00
oSumAtrIX
7a6977aff2 refactor: Remove unnecessary function 2023-10-10 10:23:00 +02:00
semantic-release-bot
c221ab1482 chore(release): 1.1.4-dev.1 [skip ci]
## [1.1.4-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.3...v1.1.4-dev.1) (2023-10-09)

### Bug Fixes

* Execute ADB commands sequentially to fix mounting issues ([#12](https://github.com/ReVanced/revanced-library/issues/12)) ([fda3eca](fda3eca74f))
2023-10-09 23:24:41 +00:00
Riccardo Nava
fda3eca74f fix: Execute ADB commands sequentially to fix mounting issues (#12) 2023-10-10 01:22:45 +02:00
semantic-release-bot
d2e367e901 chore(release): 1.1.3 [skip ci]
## [1.1.3](https://github.com/ReVanced/revanced-library/compare/v1.1.2...v1.1.3) (2023-10-09)
2023-10-09 16:57:58 +00:00
oSumAtrIX
3e460805bc chore: Merge branch dev to main (#11) 2023-10-09 18:56:03 +02:00
semantic-release-bot
6019320d9e chore(release): 1.1.3-dev.2 [skip ci]
## [1.1.3-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.1.3-dev.1...v1.1.3-dev.2) (2023-10-09)
2023-10-09 16:53:41 +00:00
oSumAtrIX
0dc83c3e0a build(Needs bump): Use correct dependency 2023-10-09 18:51:32 +02:00
97 changed files with 8563 additions and 4364 deletions

3
.editorconfig Normal file
View File

@@ -0,0 +1,3 @@
[*.{kt,kts}]
ktlint_code_style = intellij_idea
ktlint_standard_no-wildcard-imports = disabled

View File

@@ -1,49 +0,0 @@
name: 🐞 Bug report
description: Report a bug or an issue.
title: 'bug: '
labels: ['Bug report']
body:
- type: markdown
attributes:
value: |
# ReVanced Library bug report
Please check for existing bug reports
[here](https://github.com/ReVanced/revanced-library/labels/Bug%20report)
before creating a new one.
- type: textarea
attributes:
label: Bug description
description: |
- Describe your bug in detail
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
- Add images and videos if possible
- List used patches if applicable
validations:
required: true
- type: textarea
attributes:
label: Error logs
description: Exceptions can be captured by running `logcat | grep AndroidRuntime` in a shell.
render: shell
- type: textarea
attributes:
label: Solution
description: If applicable, add a possible solution to the bug.
- type: textarea
attributes:
label: Additional context
description: Add additional context here.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your issue will be closed if you don't follow the checklist below.
options:
- label: This request is not a duplicate of an existing issue.
required: true
- label: I have chosen an appropriate title.
required: true
- label: All requested information has been provided properly.
required: true

109
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,109 @@
name: 🐞 Bug report
description: Report a bug or an issue.
title: 'bug: '
labels: ['Bug report']
body:
- type: markdown
attributes:
value: |
<p align="center">
<picture>
<source
width="256px"
media="(prefers-color-scheme: dark)"
srcset="https://raw.githubusercontent.com/revanced/revanced-library/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
>
<img
width="256px"
src="https://raw.githubusercontent.com/revanced/revanced-library/main/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="https://raw.githubusercontent.com/revanced/revanced-library/main/assets/revanced-logo/revanced-logo.svg" />
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-library/main/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 bug report
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 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:
label: Bug description
description: |
- Describe your bug in detail
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
- Add images and videos if possible
validations:
required: true
- type: textarea
attributes:
label: Error logs
description: Exceptions can be captured by running `logcat | grep AndroidRuntime` in a shell.
render: shell
- type: textarea
attributes:
label: Solution
description: If applicable, add a possible solution to the bug.
- type: textarea
attributes:
label: Additional context
description: Add additional context here.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your 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.
required: true
- label: I have chosen an appropriate title.
required: true
- label: All requested information has been provided properly.
required: true

View File

@@ -1,45 +0,0 @@
name: ⭐ Feature request
description: Create a detailed request for a new feature.
title: 'feat: '
labels: ['Feature request']
body:
- type: markdown
attributes:
value: |
# ReVanced Library feature request
Please check for existing feature requests
[here](https://github.com/ReVanced/revanced-library/labels/Feature%20request)
before creating a new one.
- type: textarea
attributes:
label: Feature description
description: |
- Describe your feature in detail
- Add images, videos, links, examples, references, etc. if possible
- Add the target application name in case you request a new patch
- type: textarea
attributes:
label: Motivation
description: |
A strong motivation is necessary for a feature request to be considered.
- Why should this feature be implemented?
- What is the explicit use case?
- What are the benefits?
- What makes this feature important?
validations:
required: true
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your issue will be closed if you don't follow the checklist below.
options:
- label: This request is not a duplicate of an existing issue.
required: true
- label: I have chosen an appropriate title.
required: true
- label: All requested information has been provided properly.
required: true

View File

@@ -0,0 +1,106 @@
name: ⭐ Feature request
description: Create a detailed request for a new feature.
title: 'feat: '
labels: ['Feature request']
body:
- type: markdown
attributes:
value: |
<p align="center">
<picture>
<source
width="256px"
media="(prefers-color-scheme: dark)"
srcset="https://raw.githubusercontent.com/revanced/revanced-library/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
>
<img
width="256px"
src="https://raw.githubusercontent.com/revanced/revanced-library/main/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="https://raw.githubusercontent.com/revanced/revanced-library/main/assets/revanced-logo/revanced-logo.svg" />
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-library/main/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 feature request
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 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:
label: Feature description
description: |
- Describe your feature in detail
- Add images, videos, links, examples, references, etc. if possible
- Add the target application name in case you request a new patch
- type: textarea
attributes:
label: Motivation
description: |
A strong motivation is necessary for a feature request to be considered.
- Why should this feature be implemented?
- What is the explicit use case?
- What are the benefits?
- What makes this feature important?
validations:
required: true
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your 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.
required: true
- label: I have chosen an appropriate title.
required: true
- label: All requested information has been provided properly.
required: true

2
.github/config.yml vendored
View File

@@ -1,2 +1,2 @@
firstPRMergeComment: >
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) to receive a role for your contribution.

22
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
version: 2
updates:
- package-ecosystem: github-actions
labels: []
directory: /
target-branch: dev
schedule:
interval: monthly
- package-ecosystem: npm
labels: []
directory: /
target-branch: dev
schedule:
interval: monthly
- package-ecosystem: gradle
labels: []
directory: /
target-branch: dev
schedule:
interval: monthly

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

@@ -16,6 +16,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Open pull request
uses: repo-sync/pull-request@v2
with:

View File

@@ -1,4 +1,4 @@
name: Publish ReVanced Library
name: Release
on:
workflow_dispatch:
@@ -6,10 +6,6 @@ on:
branches:
- main
- dev
pull_request:
branches:
- main
- dev
jobs:
release:
@@ -23,23 +19,34 @@ jobs:
# https://github.com/cycjimmy/semantic-release-action#private-packages
persist-credentials: false
fetch-depth: 0
- name: Cache
uses: actions/cache@v3
with:
path: |
${{ runner.home }}/.gradle/caches
${{ runner.home }}/.gradle/wrapper
.gradle
build
node_modules
key: ${{ runner.os }}-gradle-npm-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'package-lock.json') }}
- 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 }}
# Cleaning is necessary to avoid uploading two identical artifacts with different versions
run: ./gradlew clean --no-daemon
- name: Setup semantic-release
run: ./gradlew build clean
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
fingerprint: ${{ env.GPG_FINGERPRINT }}
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}

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

@@ -32,6 +32,12 @@
backmergeBranches: [{"from": "main", "to": "dev"}],
clearWorkspace: true
}
],
[
"@semantic-release/github",
{
successComment: false
}
]
]
}

View File

@@ -1,3 +1,314 @@
# [2.4.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.3.0...v2.4.0-dev.1) (2024-04-07)
### Features
* Add local Android installer ([#25](https://github.com/ReVanced/revanced-library/issues/25)) ([43d655a](https://github.com/ReVanced/revanced-library/commit/43d655aea5d86288ae9916630e0f30de219d5cfb))
# [2.3.0](https://github.com/ReVanced/revanced-library/compare/v2.2.1...v2.3.0) (2024-03-14)
### Bug Fixes
* Support mounting even when Magisk is not installed ([2a30845](https://github.com/ReVanced/revanced-library/commit/2a30845f61d5f77ded7a72ee3d6ab55b4c512d52))
### Features
* Add utility function around key certificate pairs ([2df3484](https://github.com/ReVanced/revanced-library/commit/2df3484b68ed72338a52e76fb4b7ceb9c9c644ed))
* Improve exception message ([b15efa4](https://github.com/ReVanced/revanced-library/commit/b15efa41f8dc7d73865d0eab15be274b9ee3d7a3))
* Simplify signing utility API ([4c6a636](https://github.com/ReVanced/revanced-library/commit/4c6a6360cf83659d1f5c3a7c5710ac54426e9235))
# [2.3.0-dev.3](https://github.com/ReVanced/revanced-library/compare/v2.3.0-dev.2...v2.3.0-dev.3) (2024-03-14)
### Features
* Improve exception message ([b15efa4](https://github.com/ReVanced/revanced-library/commit/b15efa41f8dc7d73865d0eab15be274b9ee3d7a3))
# [2.3.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v2.3.0-dev.1...v2.3.0-dev.2) (2024-03-14)
### Features
* Simplify signing utility API ([4c6a636](https://github.com/ReVanced/revanced-library/commit/4c6a6360cf83659d1f5c3a7c5710ac54426e9235))
# [2.3.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.2-dev.1...v2.3.0-dev.1) (2024-03-13)
### Features
* Add utility function around key certificate pairs ([2df3484](https://github.com/ReVanced/revanced-library/commit/2df3484b68ed72338a52e76fb4b7ceb9c9c644ed))
## [2.2.2-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.1...v2.2.2-dev.1) (2024-03-12)
### Bug Fixes
* Support mounting even when Magisk is not installed ([2a30845](https://github.com/ReVanced/revanced-library/commit/2a30845f61d5f77ded7a72ee3d6ab55b4c512d52))
## [2.2.1](https://github.com/ReVanced/revanced-library/compare/v2.2.0...v2.2.1) (2024-03-09)
### Bug Fixes
* Do not specify a provider to automatically select an available one ([249372c](https://github.com/ReVanced/revanced-library/commit/249372c31f7e7975fc9eacb5361bd07dbc5dfb92))
## [2.2.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.0...v2.2.1-dev.1) (2024-03-09)
### Bug Fixes
* Do not specify a provider to automatically select an available one ([249372c](https://github.com/ReVanced/revanced-library/commit/249372c31f7e7975fc9eacb5361bd07dbc5dfb92))
# [2.2.0](https://github.com/ReVanced/revanced-library/compare/v2.1.0...v2.2.0) (2024-03-09)
### Bug Fixes
* Make property private ([51109c4](https://github.com/ReVanced/revanced-library/commit/51109c476837828535dcd395a5222d2fcf7fc22c))
* Sign APKs using `apksig` ([f59ecbc](https://github.com/ReVanced/revanced-library/commit/f59ecbccd14a08d87d4f18c3c0cc47a884088b99))
### Features
* Increase default expiration date of certificate ([f2bd3f5](https://github.com/ReVanced/revanced-library/commit/f2bd3f5eeee14ca32094be0d41c32b231a16bcc3))
# [2.2.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.1.0...v2.2.0-dev.1) (2024-03-09)
### Bug Fixes
* Make property private ([51109c4](https://github.com/ReVanced/revanced-library/commit/51109c476837828535dcd395a5222d2fcf7fc22c))
* Sign APKs using `apksig` ([f59ecbc](https://github.com/ReVanced/revanced-library/commit/f59ecbccd14a08d87d4f18c3c0cc47a884088b99))
### Features
* Increase default expiration date of certificate ([f2bd3f5](https://github.com/ReVanced/revanced-library/commit/f2bd3f5eeee14ca32094be0d41c32b231a16bcc3))
# [2.1.0](https://github.com/ReVanced/revanced-library/compare/v2.0.0...v2.1.0) (2024-03-04)
### Bug Fixes
* Use `BKS` instead of default signing provider to fix backwards compatibility ([41805fc](https://github.com/ReVanced/revanced-library/commit/41805fcb0bdc778fe0870427a0a1caa6d4369cee))
### Features
* Mention APK file name when logging aligning ([244ebc2](https://github.com/ReVanced/revanced-library/commit/244ebc21868c07d1852857f6858c1a53a5561155))
# [2.1.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v2.1.0-dev.1...v2.1.0-dev.2) (2024-03-04)
### Bug Fixes
* Use `BKS` instead of default signing provider to fix backwards compatibility ([41805fc](https://github.com/ReVanced/revanced-library/commit/41805fcb0bdc778fe0870427a0a1caa6d4369cee))
# [2.1.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.0.0...v2.1.0-dev.1) (2024-02-15)
### Features
* Mention APK file name when logging aligning ([244ebc2](https://github.com/ReVanced/revanced-library/commit/244ebc21868c07d1852857f6858c1a53a5561155))
# [2.0.0](https://github.com/ReVanced/revanced-library/compare/v1.5.0...v2.0.0) (2024-02-15)
### Bug Fixes
* Map dependencies from `KClass` into `String` to fix serialization ([57e36ab](https://github.com/ReVanced/revanced-library/commit/57e36ab5c15a5fa7c50fb689ee43ad4eb9a4a515))
* Use the JVM name instead of the value from `KClass#toString` ([d18e436](https://github.com/ReVanced/revanced-library/commit/d18e436de1df14452ecaa7d827be5e6596ba8a2d))
### Features
* Use `apkzlib` instead of own implementations and bump ReVanced Patcher ([3aa6dc2](https://github.com/ReVanced/revanced-library/commit/3aa6dc223a9a1a2f735eda407917548ecbd366aa))
### BREAKING CHANGES
* This commit removes deprecated APIs and bumps ReVanced Patcher. Because of it's changes, `apkzlib` is now used instead of own implementations of `ZipFile`
# [2.0.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.5.1-dev.2...v2.0.0-dev.1) (2024-02-14)
### Features
* Use `apkzlib` instead of own implementations and bump ReVanced Patcher ([3aa6dc2](https://github.com/ReVanced/revanced-library/commit/3aa6dc223a9a1a2f735eda407917548ecbd366aa))
### BREAKING CHANGES
* This commit removes deprecated APIs and bumps ReVanced Patcher. Because of it's changes, `apkzlib` is now used instead of own implementations of `ZipFile`
## [1.5.1-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.5.1-dev.1...v1.5.1-dev.2) (2024-02-08)
### Bug Fixes
* Use the JVM name instead of the value from `KClass#toString` ([d18e436](https://github.com/ReVanced/revanced-library/commit/d18e436de1df14452ecaa7d827be5e6596ba8a2d))
## [1.5.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.5.0...v1.5.1-dev.1) (2024-02-08)
### Bug Fixes
* Map dependencies from `KClass` into `String` to fix serialization ([57e36ab](https://github.com/ReVanced/revanced-library/commit/57e36ab5c15a5fa7c50fb689ee43ad4eb9a4a515))
# [1.5.0](https://github.com/ReVanced/revanced-library/compare/v1.4.0...v1.5.0) (2023-12-28)
### Features
* Add JSON de- and serialization of patches ([ecff6fe](https://github.com/ReVanced/revanced-library/commit/ecff6fe0d3889d729a0badcfa28b89610bd27d48))
* Improve mount reliability by unmounting existing mounts and killing running apps ([9fda407](https://github.com/ReVanced/revanced-library/commit/9fda40744173669c84b0c2599ae5ac5d39591798))
# [1.5.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.5.0-dev.1...v1.5.0-dev.2) (2023-12-07)
### Features
* Improve mount reliability by unmounting existing mounts and killing running apps ([9fda407](https://github.com/ReVanced/revanced-library/commit/9fda40744173669c84b0c2599ae5ac5d39591798))
# [1.5.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.4.0...v1.5.0-dev.1) (2023-12-07)
### Features
* Add JSON de- and serialization of patches ([ecff6fe](https://github.com/ReVanced/revanced-library/commit/ecff6fe0d3889d729a0badcfa28b89610bd27d48))
# [1.4.0](https://github.com/ReVanced/revanced-library/compare/v1.3.0...v1.4.0) (2023-11-27)
### Bug Fixes
* Differentiate no package compatibility to any version compatibility ([762b7e3](https://github.com/ReVanced/revanced-library/commit/762b7e3bc01e2ca33dfcdbb1b5028d60ef6e0a48))
* Sort the version maps by the most common version ([e4be6db](https://github.com/ReVanced/revanced-library/commit/e4be6dbccd86700ffafe7cd8395e845bbd3d5138))
### Features
* Add `PatchUtils#getMostCommonCompatibleVersions` utility function ([c5f3536](https://github.com/ReVanced/revanced-library/commit/c5f3536cbb6997766076595dc0b2b5d2e861ca73))
* Allow getting most common compatible versions for all packages ([96845ba](https://github.com/ReVanced/revanced-library/commit/96845ba265e6dc208c7ac96f5e58734209cd1720))
# [1.4.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.4.0-dev.1...v1.4.0-dev.2) (2023-11-27)
### Bug Fixes
* Differentiate no package compatibility to any version compatibility ([762b7e3](https://github.com/ReVanced/revanced-library/commit/762b7e3bc01e2ca33dfcdbb1b5028d60ef6e0a48))
* Sort the version maps by the most common version ([e4be6db](https://github.com/ReVanced/revanced-library/commit/e4be6dbccd86700ffafe7cd8395e845bbd3d5138))
### Features
* Allow getting most common compatible versions for all packages ([96845ba](https://github.com/ReVanced/revanced-library/commit/96845ba265e6dc208c7ac96f5e58734209cd1720))
# [1.4.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.3.0...v1.4.0-dev.1) (2023-11-27)
### Features
* Add `PatchUtils#getMostCommonCompatibleVersions` utility function ([c5f3536](https://github.com/ReVanced/revanced-library/commit/c5f3536cbb6997766076595dc0b2b5d2e861ca73))
# [1.3.0](https://github.com/ReVanced/revanced-library/compare/v1.2.0...v1.3.0) (2023-11-26)
### Bug Fixes
* Add missing log when calling `UserAdbManager#install` ([90b612b](https://github.com/ReVanced/revanced-library/commit/90b612bee8591c01b8befabde4147c7de7a2a09f))
* Delete mount script ([4fe0fb0](https://github.com/ReVanced/revanced-library/commit/4fe0fb0a617082b24199331671193e4fa7f485e2))
### Features
* Increase certainty of the possibility to mount ([10f8cd1](https://github.com/ReVanced/revanced-library/commit/10f8cd1470fd29cfefe53bf00a4a014f71a3f706))
* Select first Adb device, if none supplied automatically ([1a5f868](https://github.com/ReVanced/revanced-library/commit/1a5f868ecd0d278d574c12664ee95139c2423c17))
# [1.3.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.2.1-dev.1...v1.3.0-dev.1) (2023-11-26)
### Bug Fixes
* Delete mount script ([4fe0fb0](https://github.com/ReVanced/revanced-library/commit/4fe0fb0a617082b24199331671193e4fa7f485e2))
### Features
* Increase certainty of the possibility to mount ([10f8cd1](https://github.com/ReVanced/revanced-library/commit/10f8cd1470fd29cfefe53bf00a4a014f71a3f706))
* Select first Adb device, if none supplied automatically ([1a5f868](https://github.com/ReVanced/revanced-library/commit/1a5f868ecd0d278d574c12664ee95139c2423c17))
## [1.2.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.2.0...v1.2.1-dev.1) (2023-11-13)
### Bug Fixes
* Add missing log when calling `UserAdbManager#install` ([90b612b](https://github.com/ReVanced/revanced-library/commit/90b612bee8591c01b8befabde4147c7de7a2a09f))
# [1.2.0](https://github.com/ReVanced/revanced-library/compare/v1.1.5...v1.2.0) (2023-11-03)
### Bug Fixes
* Catch exceptions when serializing invalid patch options ([cd73bd3](https://github.com/ReVanced/revanced-library/commit/cd73bd39ce7be5963a3c84ec43409b87c327579b))
### Features
* Log warnings when compiling resources ([1dc8e2e](https://github.com/ReVanced/revanced-library/commit/1dc8e2e2eb76bcade9167be0c4b4a628ff114f63))
* Use better log messages when handling exceptions ([5896968](https://github.com/ReVanced/revanced-library/commit/58969683582e70f36d6ed169b41c37928a2cf602))
# [1.2.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.2.0-dev.1...v1.2.0-dev.2) (2023-11-03)
### Features
* Log warnings when compiling resources ([1dc8e2e](https://github.com/ReVanced/revanced-library/commit/1dc8e2e2eb76bcade9167be0c4b4a628ff114f63))
# [1.2.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.5...v1.2.0-dev.1) (2023-10-31)
### Bug Fixes
* Catch exceptions when serializing invalid patch options ([cd73bd3](https://github.com/ReVanced/revanced-library/commit/cd73bd39ce7be5963a3c84ec43409b87c327579b))
### Features
* Use better log messages when handling exceptions ([5896968](https://github.com/ReVanced/revanced-library/commit/58969683582e70f36d6ed169b41c37928a2cf602))
## [1.1.5](https://github.com/ReVanced/revanced-library/compare/v1.1.4...v1.1.5) (2023-10-23)
## [1.1.5-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.4...v1.1.5-dev.1) (2023-10-23)
## [1.1.4](https://github.com/ReVanced/revanced-library/compare/v1.1.3...v1.1.4) (2023-10-12)
### Bug Fixes
* Ask for root permissions before trying to use them ([aea1d69](https://github.com/ReVanced/revanced-library/commit/aea1d6915766d9757075ee459955aa335d906bab))
* Execute ADB commands sequentially to fix mounting issues ([#12](https://github.com/ReVanced/revanced-library/issues/12)) ([fda3eca](https://github.com/ReVanced/revanced-library/commit/fda3eca74f30b968d8ee816d63a3dcf493e026de))
## [1.1.4-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.1.4-dev.1...v1.1.4-dev.2) (2023-10-10)
### Bug Fixes
* Ask for root permissions before trying to use them ([aea1d69](https://github.com/ReVanced/revanced-library/commit/aea1d6915766d9757075ee459955aa335d906bab))
## [1.1.4-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.3...v1.1.4-dev.1) (2023-10-09)
### Bug Fixes
* Execute ADB commands sequentially to fix mounting issues ([#12](https://github.com/ReVanced/revanced-library/issues/12)) ([fda3eca](https://github.com/ReVanced/revanced-library/commit/fda3eca74f30b968d8ee816d63a3dcf493e026de))
## [1.1.3](https://github.com/ReVanced/revanced-library/compare/v1.1.2...v1.1.3) (2023-10-09)
## [1.1.3-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.1.3-dev.1...v1.1.3-dev.2) (2023-10-09)
## [1.1.3-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.2...v1.1.3-dev.1) (2023-10-09)
## [1.1.2](https://github.com/ReVanced/revanced-library/compare/v1.1.1...v1.1.2) (2023-10-05)

View File

@@ -6,34 +6,53 @@
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
>
<img
width="256px"
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
>
</picture>
<br>
<a href="https://revanced.app/">
<img height="24px" src="assets/revanced-logo/revanced-logo-round.svg" />
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo.svg" />
<img height="24px" src="assets/revanced-logo/revanced-logo.svg" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://github.com/revanced">
<a href="https://github.com/ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="http://revanced.app/discord">
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://reddit.com/r/revancedapp">
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://t.me/app_revanced">
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://twitter.com/revancedapp">
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032018-6da37214-7474-4641-a1da-7af7db3a31cd.png" />
<a href="https://x.com/revancedapp">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://www.youtube.com/@ReVanced">
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
</a>&nbsp;&nbsp;&nbsp;
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
</picture>
</a>
<br>
<br>
Continuing the legacy of Vanced
@@ -46,22 +65,23 @@ This document describes how to contribute to ReVanced Library.
## 📖 Resources to help you get started
<!-- * The [documentation](/docs) explains how to use ReVanced Library -->
* [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
* [Issues](https://github.com/ReVanced/revanced-cli/issues) are where we keep track of bugs and feature requests
- [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
- [Issues](https://github.com/ReVanced/revanced-cli/issues) are where we keep track of bugs and feature requests
## 🙏 Submitting a feature request
Features can be requested by opening an issue using the
[Feature request issue template](https://github.com/ReVanced/revanced-cli/issues/new?assignees=&labels=Feature+request&projects=&template=feature-request.yml&title=feat%3A+).
[Feature request issue template](https://github.com/ReVanced/revanced-cli/issues/new?assignees=&labels=Feature+request&projects=&template=feature_request.yml&title=feat%3A+).
> **Note**
> [!NOTE]
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Library.
> Good motivation has to be provided for a request to be accepted.
## 🐞 Submitting a bug report
If you encounter a bug while using ReVanced Library, open an issue using the
[Bug report issue template](https://github.com/ReVanced/revanced-cli/issues/new?assignees=&labels=Bug+report&projects=&template=bug-report.yml&title=bug%3A+).
[Bug report issue template](https://github.com/ReVanced/revanced-cli/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
## 📝 How to contribute
@@ -69,11 +89,11 @@ If you encounter a bug while using ReVanced Library, open an issue using the
with the maintainers of ReVanced Library. This will help you determine whether your change is acceptable
and whether it is worth your time to implement it
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
3. Commit your changes.
3. Commit your changes
4. Submit a pull request to the `dev` branch of the repository and reference issues
that your pull request closes in the description of your pull request
5. Our team will review your pull request and provide feedback. Once your pull request is approved,
it will be merged into the `dev` branch and will be included in the next release of ReVanced Library
❤️ Thank you for considering contributing to ReVanced Library,
ReVanced
❤️ Thank you for considering contributing to ReVanced Library,
ReVanced

117
README.md Normal file
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).
## 📜 Licence
ReVanced Library is licensed under the GPLv3 license. Please see the [licence file](LICENSE) for more information.
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Library as long as you track changes/dates in source files.
Any modifications to ReVanced Library must also be made available under the GPL,
along with build & install instructions.

View File

@@ -1,140 +0,0 @@
public final class app/revanced/library/ApkSigner {
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
public final fun newApkSignerBuilder (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;Ljava/lang/String;Ljava/lang/String;)Lcom/android/apksig/ApkSigner$Builder;
public final fun newApkSignerBuilder (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/android/apksig/ApkSigner$Builder;
public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/List;)V
public final fun newKeyStore (Ljava/util/List;)Ljava/security/KeyStore;
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public static synthetic fun newPrivateKeyCertificatePair$default (Lapp/revanced/library/ApkSigner;Ljava/lang/String;Ljava/util/Date;ILjava/lang/Object;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
public final fun signApk (Lcom/android/apksig/ApkSigner$Builder;Ljava/io/File;Ljava/io/File;)V
}
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
public final fun getPrivateKey ()Ljava/security/PrivateKey;
}
public final class app/revanced/library/ApkUtils {
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
public final fun copyAligned (Ljava/io/File;Ljava/io/File;Lapp/revanced/patcher/PatcherResult;)V
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
}
public final class app/revanced/library/ApkUtils$SigningOptions {
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getKeyStore ()Ljava/io/File;
public final fun getKeyStorePassword ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getSigner ()Ljava/lang/String;
}
public final class app/revanced/library/Options {
public static final field INSTANCE Lapp/revanced/library/Options;
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch;
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
public static synthetic fun serialize$default (Lapp/revanced/library/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String;
public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V
public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V
}
public final class app/revanced/library/Options$Patch {
public final fun getOptions ()Ljava/util/List;
public final fun getPatchName ()Ljava/lang/String;
}
public final class app/revanced/library/Options$Patch$Option {
public final fun getKey ()Ljava/lang/String;
public final fun getValue ()Ljava/lang/Object;
}
public final class app/revanced/library/PatchUtils {
public static final field INSTANCE Lapp/revanced/library/PatchUtils;
public final fun getMostCommonCompatibleVersion (Ljava/util/Set;Ljava/lang/String;)Ljava/lang/String;
}
public 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 final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager {
public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)V
public fun uninstall (Ljava/lang/String;)V
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils {
}
public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager {
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)V
public fun uninstall (Ljava/lang/String;)V
}
public final class app/revanced/library/logging/Logger {
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
public final fun removeAllHandlers ()V
public final fun setDefault ()V
public final fun setFormat (Ljava/lang/String;)V
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
}
public final class app/revanced/library/zip/ZipFile : java/io/Closeable {
public static final field ApkZipFile Lapp/revanced/library/zip/ZipFile$ApkZipFile;
public fun <init> (Ljava/io/File;)V
public final fun addEntryCompressData (Lapp/revanced/library/zip/structures/ZipEntry;[B)V
public fun close ()V
public final fun copyEntriesFromFileAligned (Lapp/revanced/library/zip/ZipFile;Lkotlin/jvm/functions/Function1;)V
}
public final class app/revanced/library/zip/ZipFile$ApkZipFile {
public final fun getApkZipEntryAlignment ()Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/zip/structures/ZipEntry {
public static final field Companion Lapp/revanced/library/zip/structures/ZipEntry$Companion;
public fun <init> (Ljava/lang/String;)V
}
public final class app/revanced/library/zip/structures/ZipEntry$Companion {
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Logo"><g id="Ring"><circle id="Ring-Background" serif:id="Ring Background" cx="400" cy="400" r="400" style="fill:#1b1b1b;"/><path id="Ring1" serif:id="Ring" d="M400,0c220.766,0 400,179.234 400,400c-0,220.766 -179.234,400 -400,400c-220.766,-0 -400,-179.234 -400,-400c0,-220.766 179.234,-400 400,-400Zm-0,36c200.897,-0 364,163.103 364,364c0,200.897 -163.103,364 -364,364c-200.897,0 -364,-163.103 -364,-364c-0,-200.897 163.103,-364 364,-364Z" style="fill:url(#_Linear1);"/></g><g id="Shape"><path id="V-Shape" serif:id="V Shape" d="M538.74,269.872c1.481,-3.382 1.157,-7.283 -0.863,-10.373c-2.021,-3.091 -5.464,-4.954 -9.156,-4.954c-5.148,0 -10.435,0 -14.165,0c-3.1,0 -5.907,1.834 -7.153,4.672c-12.468,28.396 -78.273,178.273 -100.25,228.328c-1.246,2.838 -4.053,4.671 -7.154,4.671c-3.1,0 -5.907,-1.833 -7.153,-4.671c-21.977,-50.055 -87.782,-199.932 -100.25,-228.328c-1.246,-2.838 -4.053,-4.672 -7.153,-4.672c-3.73,0 -9.017,0 -14.164,0c-3.693,0 -7.135,1.863 -9.156,4.954c-2.02,3.09 -2.344,6.991 -0.863,10.373c23.557,53.766 101.872,232.519 117.871,269.034c1.743,3.979 5.674,6.549 10.018,6.549c6.293,-0 15.408,-0 21.701,-0c4.344,-0 8.275,-2.57 10.018,-6.549c15.999,-36.515 94.315,-215.268 117.872,-269.034Z" style="fill:#fff;"/><path id="Diamond" d="M408.119,395.312c-1.675,2.901 -4.77,4.688 -8.119,4.688c-3.349,-0 -6.444,-1.787 -8.119,-4.688c-16.997,-29.44 -56.156,-97.264 -73.153,-126.704c-1.675,-2.901 -1.675,-6.474 0,-9.375c1.675,-2.901 4.77,-4.688 8.119,-4.688c33.995,0 112.311,0 146.306,0c3.349,0 6.444,1.787 8.119,4.688c1.675,2.901 1.675,6.474 -0,9.375c-16.997,29.44 -56.156,97.264 -73.153,126.704Z" style="fill:url(#_Linear2);"/></g></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(4.89859e-14,800,-800,4.89859e-14,400.001,3.31681e-10)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.77155e-14,289.317,-282.535,1.73003e-14,400,254.545)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,71 +1,30 @@
plugins {
kotlin("jvm") version "1.9.0"
alias(libs.plugins.binary.compatibility.validator)
`maven-publish`
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.binary.compatibility.validator) apply false
alias(libs.plugins.ktor) apply false
}
group = "app.revanced"
dependencies {
implementation(libs.revanced.patcher)
implementation(libs.kotlin.reflect)
implementation(libs.jadb) // Updated fork
implementation(libs.apksig)
implementation(libs.bcpkix.jdk18on)
implementation(libs.jackson.module.kotlin)
testImplementation(libs.revanced.patcher)
testImplementation(libs.kotlin.test)
}
tasks {
test {
useJUnitPlatform()
testLogging {
events("PASSED", "SKIPPED", "FAILED")
}
}
}
kotlin { jvmToolchain(11) }
java {
withSourcesJar()
}
publishing {
publications {
create<MavenPublication>("gpr") {
from(components["java"])
version = project.version.toString()
pom {
name = "ReVanced Library"
description = "Library containing common utilities for ReVanced"
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-library.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
url = "https://github.com/revanced/revanced-library"
}
subprojects {
// 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 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") }
}
}
}

View File

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

View File

@@ -1,21 +1,54 @@
[versions]
apksig = "8.1.2"
bcpkix-jdk18on = "1.76"
jackson-module-kotlin = "2.14.3"
jackson-module-kotlin = "2.15.0"
jadb = "1.2.1"
kotlin-reflect = "1.9.0"
kotlin-test = "1.8.20-RC"
revanced-patcher = "17.0.0"
binary-compatibility-validator = "0.13.2"
kotlin = "1.9.22"
ktor-client = "2.3.10"
ktor-server-test-host = "2.3.9"
revanced-patcher = "19.3.1"
binary-compatibility-validator = "0.14.0"
android = "8.3.2"
bcpkix-jdk15on = "1.70"
guava = "33.0.0-jre"
libsu = "5.2.2"
core-ktx = "1.12.0"
ktor = "2.3.9"
koin = "3.5.3"
logback = "1.4.14"
[libraries]
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bcpkix-jdk18on" }
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" }
jadb = { module = "app.revanced:jadb", version.ref = "jadb" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
apkzlib = { module = "com.android.tools.build:apkzlib", version.ref = "android" }
apksig = { module = "com.android.tools.build:apksig", version.ref = "android" }
bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" }
libsu-service = { module = "com.github.topjohnwu.libsu:service", version.ref = "libsu" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor-client" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor-client" }
ktor-server-conditional-headers = { module = "io.ktor:ktor-server-conditional-headers" }
ktor-server-core = { module = "io.ktor:ktor-server-core" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation" }
ktor-server-auth = { module = "io.ktor:ktor-server-auth" }
ktor-server-auth-jwt = { module = "io.ktor:ktor-server-auth-jwt" }
ktor-server-cors = { module = "io.ktor:ktor-server-cors" }
ktor-server-caching-headers = { module = "io.ktor:ktor-server-caching-headers" }
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty" }
ktor-server-websockets = { module = "io.ktor:ktor-server-websockets" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json" }
koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" }
[plugins]
ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
android-library = { id = "com.android.library", version.ref = "android" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

Binary file not shown.

View File

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

281
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#!/usr/bin/env sh
#
# Copyright © 2015-2021 the original authors.
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,98 +17,67 @@
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
MAX_FD="maximum"
warn () {
echo "$*"
} >&2
}
die () {
echo
echo "$*"
echo
exit 1
} >&2
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -118,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD=$JAVA_HOME/bin/java
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -129,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
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
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=$( cygpath --unix "$JAVACMD" )
JAVACMD=`cygpath --unix "$JAVACMD"`
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
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.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

15
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,8 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@@ -0,0 +1,143 @@
public final class app/revanced/library/networking/Server {
public final fun start ()Lio/ktor/server/engine/ApplicationEngine;
public final fun stop ()V
}
public final class app/revanced/library/networking/Server$DependenciesConfiguration {
public fun <init> (Lapp/revanced/library/networking/configuration/repository/StorageRepository;Lapp/revanced/library/networking/configuration/repository/PatchSetRepository;Lapp/revanced/library/networking/configuration/repository/AppRepository;Lapp/revanced/library/networking/configuration/repository/InstallerRepository;)V
}
public final class app/revanced/library/networking/Server$SecurityConfiguration {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/library/networking/Server$SerializersConfiguration {
public fun <init> ()V
public fun <init> (Ljava/util/Map;)V
public synthetic fun <init> (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/library/networking/ServerBuilder {
public fun <init> ()V
public final fun configureDependencies (Lkotlin/jvm/functions/Function1;)Lapp/revanced/library/networking/ServerBuilder;
public final fun configureSecurity (Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/networking/ServerBuilder;
public final fun configureSerializers (Lkotlin/jvm/functions/Function1;)Lapp/revanced/library/networking/ServerBuilder;
}
public final class app/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder {
public final fun build ()Lapp/revanced/library/networking/Server$DependenciesConfiguration;
public final fun configureAppRepository (Lapp/revanced/library/networking/configuration/repository/AppRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
public final fun configureInstallerRepository (Lapp/revanced/library/networking/configuration/repository/InstallerRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
public final fun configurePatchSetRepository (Lapp/revanced/library/networking/configuration/repository/PatchSetRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
public final fun configureStorageRepository (Lapp/revanced/library/networking/configuration/repository/StorageRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
}
public final class app/revanced/library/networking/ServerBuilder$SerializersConfigurationBuilder {
public final fun build ()Lapp/revanced/library/networking/Server$SerializersConfiguration;
public final fun configurePatchOptionSerializers ([Lkotlin/Pair;)V
}
public final class app/revanced/library/networking/ServerKt {
public static final fun main ()V
public static synthetic fun main ([Ljava/lang/String;)V
public static final fun server (Ljava/lang/String;ILio/ktor/server/engine/ApplicationEngineFactory;Lkotlin/jvm/functions/Function1;)Lapp/revanced/library/networking/Server;
public static synthetic fun server$default (Ljava/lang/String;ILio/ktor/server/engine/ApplicationEngineFactory;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/library/networking/Server;
}
public final class app/revanced/library/networking/configuration/SerializationKt {
public static final fun configureSerialization (Lio/ktor/server/application/Application;Lapp/revanced/library/networking/Server$SerializersConfiguration;)V
}
public abstract class app/revanced/library/networking/configuration/repository/AppRepository {
public fun <init> ()V
}
public abstract class app/revanced/library/networking/configuration/repository/InstallerRepository {
public fun <init> ()V
}
public abstract class app/revanced/library/networking/configuration/repository/PatchSetRepository {
public fun <init> (Lapp/revanced/library/networking/configuration/repository/StorageRepository;)V
}
public abstract class app/revanced/library/networking/configuration/repository/StorageRepository {
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/io/File;)V
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAaptBinaryPath ()Ljava/io/File;
public final fun getKeystoreFilePath ()Ljava/io/File;
public final fun getOutputFilePath ()Ljava/io/File;
public final fun getTemporaryFilesPath ()Ljava/io/File;
}
public class app/revanced/library/networking/models/App {
public static final field Companion Lapp/revanced/library/networking/models/App$Companion;
public synthetic fun <init> (ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public static final synthetic fun write$Self (Lapp/revanced/library/networking/models/App;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}
public final class app/revanced/library/networking/models/App$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lapp/revanced/library/networking/models/App$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lapp/revanced/library/networking/models/App;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lapp/revanced/library/networking/models/App;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/App$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/Patch {
public static final field Companion Lapp/revanced/library/networking/models/Patch$Companion;
}
public final class app/revanced/library/networking/models/Patch$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lapp/revanced/library/networking/models/Patch$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lapp/revanced/library/networking/models/Patch;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lapp/revanced/library/networking/models/Patch;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/Patch$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/Patch$KeyValuePatchOption {
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;)V
public final fun getKey ()Ljava/lang/String;
public final fun getValue ()Ljava/lang/Object;
public final fun getValueType ()Ljava/lang/String;
}
public final class app/revanced/library/networking/models/Patch$PatchOption {
public static final field Companion Lapp/revanced/library/networking/models/Patch$PatchOption$Companion;
}
public final class app/revanced/library/networking/models/Patch$PatchOption$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public synthetic fun <init> (Lkotlinx/serialization/KSerializer;)V
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lapp/revanced/library/networking/models/Patch$PatchOption;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lapp/revanced/library/networking/models/Patch$PatchOption;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/Patch$PatchOption$Companion {
public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/PatchBundle {
public final fun getPatchBundleFile ()Ljava/io/File;
public final fun getPatchBundleIntegrationsFile ()Ljava/io/File;
}

View File

@@ -0,0 +1,97 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.binary.compatibility.validator)
alias(libs.plugins.ktor)
`maven-publish`
signing
}
dependencies {
implementation(project(":library"))
implementation(libs.revanced.patcher)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio)
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.content.negotiation)
implementation(libs.ktor.server.auth)
implementation(libs.ktor.server.auth.jwt)
implementation(libs.ktor.server.cors)
implementation(libs.ktor.server.caching.headers)
implementation(libs.ktor.server.host.common)
implementation(libs.ktor.server.netty)
implementation(libs.ktor.server.conditional.headers)
implementation(libs.ktor.server.websockets)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.koin.ktor)
implementation(libs.logback.classic)
}
tasks {
processResources {
expand("projectVersion" to project.version)
}
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
java {
targetCompatibility = JavaVersion.VERSION_11
}
publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-library")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
publications {
create<MavenPublication>("revanced-library-networking-publication") {
version = project.version.toString()
pom {
name = "ReVanced Networking Library"
description = "Library to interface to common utilities for ReVanced over a network."
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-library.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
url = "https://github.com/revanced/revanced-library"
}
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications["revanced-library-networking-publication"])
}

View File

@@ -0,0 +1,6 @@
package app.revanced.library.networking
import io.ktor.server.application.*
import io.ktor.util.pipeline.*
internal val PipelineContext<*, ApplicationCall>.parameters get() = call.parameters

View File

@@ -0,0 +1,281 @@
@file:Suppress("unused")
package app.revanced.library.networking
import app.revanced.library.installation.installer.AdbInstaller
import app.revanced.library.networking.configuration.configureDependencies
import app.revanced.library.networking.configuration.configureHTTP
import app.revanced.library.networking.configuration.configureSecurity
import app.revanced.library.networking.configuration.configureSerialization
import app.revanced.library.networking.configuration.repository.AppRepository
import app.revanced.library.networking.configuration.repository.InstallerRepository
import app.revanced.library.networking.configuration.repository.PatchSetRepository
import app.revanced.library.networking.configuration.repository.StorageRepository
import app.revanced.library.networking.configuration.routing.configureRouting
import app.revanced.library.networking.models.App
import app.revanced.library.networking.models.PatchBundle
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.PatchSet
import app.revanced.patcher.patch.options.PatchOption
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import java.io.File
import java.time.LocalDateTime
import kotlin.reflect.KType
import kotlin.reflect.typeOf
/**
* A server.
*
* @param host The host.
* @param port The port.
* @param engineFactory The engine factory.
* @param securityConfiguration The security configuration.
* @param dependenciesConfiguration The dependencies configuration.
* @param serializersConfiguration The serializers configuration.
*/
class Server internal constructor(
host: String,
port: Int,
engineFactory: ApplicationEngineFactory<*, *>,
securityConfiguration: SecurityConfiguration,
dependenciesConfiguration: DependenciesConfiguration,
serializersConfiguration: SerializersConfiguration,
) {
private val applicationEngine = embeddedServer(engineFactory, port, host) {
configureHTTP(allowedHost = host)
configureSecurity(securityConfiguration)
configureDependencies(dependenciesConfiguration)
configureSerialization(serializersConfiguration)
configureRouting()
}
/**
* Starts the server and blocks the current thread.
*/
fun start() = applicationEngine.start(wait = true)
/**
* Stops the server.
*/
fun stop() = applicationEngine.stop()
/**
* The security configuration.
*
* @property username The username.
* @property password The password.
*/
class SecurityConfiguration(
internal val username: String,
internal val password: String,
)
/**
* The dependencies configuration.
*
* @property storageRepository The storage repository.
* @property patchSetRepository The patch set repository.
* @property appRepository The app repository.
* @property installerRepository The installer repository.
*/
class DependenciesConfiguration(
internal val storageRepository: StorageRepository,
internal val patchSetRepository: PatchSetRepository,
internal val appRepository: AppRepository,
internal val installerRepository: InstallerRepository,
)
/**
* The serializers configuration.
*
* @property patchOptionValueTypes A map of [PatchOption.valueType] to [KType] to add serializers for patch options
* additional to the default ones.
*/
class SerializersConfiguration(
internal val patchOptionValueTypes: Map<String, KType> = emptyMap(),
)
}
/**
* A server builder.
*
* @property host The host.
* @property port The port.
* @property engineFactory The engine factory.
* @property securityConfiguration The security configuration.
* @property dependenciesConfiguration The dependencies configuration.
*/
class ServerBuilder internal constructor(
private val host: String = "localhost",
private val port: Int = 8080,
private val engineFactory: ApplicationEngineFactory<*, *> = Netty,
) {
private lateinit var securityConfiguration: Server.SecurityConfiguration
private lateinit var dependenciesConfiguration: Server.DependenciesConfiguration
private var serializersConfiguration = Server.SerializersConfiguration()
/**
* Configures the security.
*
* @param basicUsername The basic username.
* @param basicPassword The basic password.
*
* @return The server builder.
*/
fun configureSecurity(
basicUsername: String,
basicPassword: String,
) = apply {
securityConfiguration = Server.SecurityConfiguration(
username = basicUsername,
password = basicPassword,
)
}
/**
* Configures the dependencies.
*
* @param block The block to configure the dependencies.
*
* @return The server builder.
*/
fun configureDependencies(block: DependenciesConfigurationBuilder.() -> Unit) = apply {
dependenciesConfiguration = DependenciesConfigurationBuilder().apply(block).build()
}
/**
* Configures the serializers.
*
* @param block The block to configure the serializers.
*
* @return The server builder.
*/
fun configureSerializers(block: SerializersConfigurationBuilder.() -> Unit) = apply {
serializersConfiguration = SerializersConfigurationBuilder().apply(block).build()
}
class DependenciesConfigurationBuilder internal constructor() {
private lateinit var storageRepository: StorageRepository
private lateinit var patchSetRepository: PatchSetRepository
private lateinit var appRepository: AppRepository
private lateinit var installerRepository: InstallerRepository
fun configureStorageRepository(storageRepository: StorageRepository) = apply {
this.storageRepository = storageRepository
}
fun configurePatchSetRepository(patchSetRepository: PatchSetRepository) = apply {
this.patchSetRepository = patchSetRepository
}
fun configureAppRepository(appRepository: AppRepository) = apply {
this.appRepository = appRepository
}
fun configureInstallerRepository(installerRepository: InstallerRepository) = apply {
this.installerRepository = installerRepository
}
fun build() = Server.DependenciesConfiguration(
storageRepository,
patchSetRepository,
appRepository,
installerRepository,
)
}
class SerializersConfigurationBuilder internal constructor() {
private lateinit var patchOptionValueTypes: Map<String, KType>
fun configurePatchOptionSerializers(vararg pairs: Pair<String, KType>) {
this.patchOptionValueTypes = mapOf(*pairs)
}
fun build() = Server.SerializersConfiguration(patchOptionValueTypes)
}
/**
* Builds the server.
*
* @return The server.
*/
internal fun build() = Server(
host,
port,
engineFactory,
securityConfiguration,
dependenciesConfiguration,
serializersConfiguration,
)
}
/**
* Creates a server.
*
* @param host The host.
* @param port The port.
* @param engineFactory The engine factory.
* @param block The block to build the server.
*
* @return The server.
*/
fun server(
host: String = "localhost",
port: Int = 8080,
engineFactory: ApplicationEngineFactory<*, *> = Netty,
block: ServerBuilder.() -> Unit = {},
) = ServerBuilder(host, port, engineFactory).apply(block).build()
fun main() {
server {
configureSecurity("username", "password")
val storageRepository = object : StorageRepository(
temporaryFilesPath = File("temp"),
keystoreFilePath = File("keystore.jks"),
) {
override fun readPatchBundles() = setOf(
PatchBundle(
"ReVanced Patches",
File("D:\\ReVanced\\revanced-patches\\build\\libs\\revanced-patches-4.7.0-dev.2.jar"),
),
)
override fun writePatchBundles(patchBundles: Set<PatchBundle>) {
// TODO("Not yet implemented")
}
override fun newPatchBundle(patchBundleName: String, withIntegrations: Boolean): PatchBundle {
TODO("Not yet implemented")
}
}
val patchSetRepository = object : PatchSetRepository(storageRepository) {
override fun readPatchSet(patchBundles: Set<PatchBundle>): PatchSet {
return PatchBundleLoader.Jar(*patchBundles.map { it.patchBundleFile }.toTypedArray())
}
}
val appRepository = object : AppRepository() {
override fun readInstalledApps() = emptySet<App>()
}
val installerRepository = object : InstallerRepository() {
override val installer = AdbInstaller("127.0.0.1:58526")
}
configureDependencies {
configureStorageRepository(storageRepository)
configurePatchSetRepository(patchSetRepository)
configureAppRepository(appRepository)
configureInstallerRepository(installerRepository)
}
configureSerializers {
configurePatchOptionSerializers(
"LocalDateTime" to typeOf<PatchOption<LocalDateTime>>(),
)
}
}.start()
}

View File

@@ -0,0 +1,43 @@
package app.revanced.library.networking.configuration
import app.revanced.library.networking.Server
import app.revanced.library.networking.services.HttpClientService
import app.revanced.library.networking.services.PatchBundleService
import app.revanced.library.networking.services.PatcherService
import io.ktor.server.application.*
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
import org.koin.ktor.plugin.Koin
/**
* Configure the dependencies for the application.
*
* @param dependenciesConfiguration The dependencies configuration.
*/
internal fun Application.configureDependencies(
dependenciesConfiguration: Server.DependenciesConfiguration,
) {
val globalModule = module {
single { dependenciesConfiguration.storageRepository }
single { dependenciesConfiguration.patchSetRepository }
single { dependenciesConfiguration.appRepository }
single { dependenciesConfiguration.installerRepository }
}
val patchBundleModule = module {
single { HttpClientService() }
singleOf(::PatchBundleService)
}
val patcherModule = module {
singleOf(::PatcherService)
}
install(Koin) {
modules(
globalModule,
patchBundleModule,
patcherModule,
)
}
}

View File

@@ -0,0 +1,33 @@
package app.revanced.library.networking.configuration
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.plugins.cachingheaders.*
import io.ktor.server.plugins.conditionalheaders.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.websocket.*
import kotlin.time.Duration.Companion.minutes
/**
* Configures HTTP for the application.
*
* @param allowedHost The allowed host for the application.
*/
internal fun Application.configureHTTP(
allowedHost: String,
) {
install(ConditionalHeaders)
install(CORS) {
allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
allowMethod(HttpMethod.Patch)
allowHeader(HttpHeaders.Authorization)
allowHost(allowedHost)
}
install(WebSockets)
install(CachingHeaders) {
options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) }
}
}

View File

@@ -0,0 +1,28 @@
package app.revanced.library.networking.configuration
import app.revanced.library.networking.Server
import io.ktor.server.application.*
import io.ktor.server.auth.*
/**
* Configures the security for the application.
*
* @param securityConfiguration The security configuration.
*/
internal fun Application.configureSecurity(
securityConfiguration: Server.SecurityConfiguration,
) {
install(Authentication) {
basic {
validate { credentials ->
if (credentials.name == securityConfiguration.username &&
credentials.password == securityConfiguration.password
) {
UserIdPrincipal(credentials.name)
} else {
null
}
}
}
}
}

View File

@@ -0,0 +1,92 @@
package app.revanced.library.networking.configuration
import app.revanced.library.networking.Server
import app.revanced.library.networking.models.Patch
import app.revanced.patcher.patch.options.PatchOption
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.decodeStructure
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.SerializersModuleBuilder
import kotlinx.serialization.modules.contextual
import kotlinx.serialization.serializer
import java.io.Serializable
import kotlin.reflect.KType
import kotlin.reflect.typeOf
/**
* Configures the serialization for the application.
*
* @param serializersConfiguration The serializers configuration.
*/
fun Application.configureSerialization(serializersConfiguration: Server.SerializersConfiguration) {
install(ContentNegotiation) {
json(
Json {
serializersModule = SerializersModule {
configurePatchOptionSerializers(serializersConfiguration.patchOptionValueTypes)
}
},
)
}
}
/**
* Configures the patch option serializers.
*
* @param patchOptionValueTypes A map of [PatchOption.valueType] to [KType] to add serializers for patch options
* additional to the default ones.
*/
private fun SerializersModuleBuilder.configurePatchOptionSerializers(patchOptionValueTypes: Map<String, KType>) {
val knownPatchOptionValueTypes = mapOf(
"String" to typeOf<Patch.PatchOption<String>>(),
"Int" to typeOf<Patch.PatchOption<Int>>(),
"Boolean" to typeOf<Patch.PatchOption<Boolean>>(),
"Long" to typeOf<Patch.PatchOption<Long>>(),
"Float" to typeOf<Patch.PatchOption<Float>>(),
"StringArray" to typeOf<Patch.PatchOption<Array<String>>>(),
"IntArray" to typeOf<Patch.PatchOption<IntArray>>(),
"BooleanArray" to typeOf<Patch.PatchOption<BooleanArray>>(),
"LongArray" to typeOf<Patch.PatchOption<LongArray>>(),
"FloatArray" to typeOf<Patch.PatchOption<FloatArray>>(),
) + patchOptionValueTypes
/**
* Gets the [KType] for a patch option value type.
*
* @param valueType The value type of the patch option.
*
* @return The [KType] for the patch option value type.
*/
fun patchOptionTypeOf(valueType: String) = knownPatchOptionValueTypes[valueType]
?: error("Unknown patch option value type: $valueType")
/**
* Serializer for [Patch.PatchOption].
* Uses the [Patch.PatchOption.valueType] to determine the serializer for the generic type.
*/
val patchOptionSerializer = object : KSerializer<Patch.PatchOption<*>> {
override val descriptor = serializer(typeOf<Patch.PatchOption<Serializable>>()).descriptor
override fun serialize(encoder: Encoder, value: Patch.PatchOption<*>) = serializer(
patchOptionTypeOf(value.valueType),
).serialize(encoder, value)
override fun deserialize(decoder: Decoder) = serializer(
patchOptionTypeOf(
decoder.decodeStructure(descriptor) {
decodeStringElement(descriptor, descriptor.getElementIndex("valueType"))
},
),
).deserialize(decoder) as Patch.PatchOption<*>
}
contextual(patchOptionSerializer)
contextual(SetSerializer(patchOptionSerializer))
}

View File

@@ -0,0 +1,32 @@
package app.revanced.library.networking.configuration.repository
import app.revanced.library.networking.models.App
/**
* A repository for apps and installers.
*/
abstract class AppRepository {
/**
* The set of [App] installed.
*/
internal lateinit var installedApps: Set<App>
private set
init {
readAndSetInstalledApps()
}
/**
* Read a set of [App] from a storage.
*
* @return The set of [App] read.
*/
internal abstract fun readInstalledApps(): Set<App>
/**
* Read a set of [App] using [readInstalledApps] and set [installedApps] to it.
*/
internal fun readAndSetInstalledApps() {
this.installedApps = readInstalledApps()
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.library.networking.configuration.repository
import app.revanced.library.installation.installer.Installer
import app.revanced.library.installation.installer.MountInstaller
import app.revanced.library.networking.models.App
abstract class InstallerRepository {
/**
* The installer to use for installing and uninstalling [App]s.
*/
internal abstract val installer: Installer<*, *>
/**
* The root installer to use for mounting and unmounting [App]s.
*/
internal open val mountInstaller: MountInstaller? = null
}

View File

@@ -0,0 +1,41 @@
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package app.revanced.library.networking.configuration.repository
import app.revanced.library.networking.models.PatchBundle
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.PatchSet
import app.revanced.patcher.patch.Patch
/**
* A repository for patches from a set of [PatchBundle]s.
*
* @param storageRepository The [StorageRepository] to read the [PatchBundle]s from.
*/
abstract class PatchSetRepository(
private val storageRepository: StorageRepository,
) {
/**
* The set of [Patch]es loaded from [StorageRepository.patchBundles].
*/
internal lateinit var patchSet: PatchSet
private set
init {
readAndSetPatchSet()
}
/**
* Read a [PatchSet] from a set of [patchBundles] using a [PatchBundleLoader].
*
* @param patchBundles The set of [PatchBundle]s to read the [PatchSet] from.
*/
internal abstract fun readPatchSet(patchBundles: Set<PatchBundle>): PatchSet
/**
* Read a [PatchSet] from patch bundles from [storageRepository] using [readPatchSet] and set [patchSet] to it.
*/
internal fun readAndSetPatchSet() {
this.patchSet = readPatchSet(storageRepository.patchBundles.values.toSet())
}
}

View File

@@ -0,0 +1,93 @@
package app.revanced.library.networking.configuration.repository
import app.revanced.library.networking.models.PatchBundle
import app.revanced.patcher.Patcher
import java.io.File
/**
* A repository for storage.
*
* @param temporaryFilesPath The path to the temporary files for [Patcher].
* @param outputFilePath The path to the output file to save patched APKs to.
* @param keystoreFilePath The path to the keystore file to sign patched APKs with.
* @param aaptBinaryPath The path to the aapt binary to use by [Patcher].
*/
abstract class StorageRepository(
val temporaryFilesPath: File,
val outputFilePath: File = File(temporaryFilesPath, "output.apk"),
val keystoreFilePath: File,
val aaptBinaryPath: File? = null,
) {
/**
* The stored [PatchBundle]s mapped by their name.
*/
internal lateinit var patchBundles: MutableMap<String, PatchBundle>
private set
/**
* The path to save the patched, but unsigned APK to.
*/
internal val unsignedApkFilePath = File(temporaryFilesPath, "unsigned.apk")
init {
readAndSetPatchBundles()
}
/**
* Read a set of [patchBundles] from a storage.
*
* @return The set of [PatchBundle] read.
*/
internal abstract fun readPatchBundles(): Set<PatchBundle>
/**
* Write a set of [patchBundles] to a storage.
*
* @param patchBundles The set of patch bundles to write.
*/
internal abstract fun writePatchBundles(patchBundles: Set<PatchBundle>)
/**
* Create a new [PatchBundle] in a storage to write to.
*
* @param patchBundleName The name of the patch bundle.
* @param withIntegrations Whether the patch bundle also has integrations.
*
* @return The new [PatchBundle] created.
*/
internal abstract fun newPatchBundle(patchBundleName: String, withIntegrations: Boolean): PatchBundle
/**
* Read the set of [patchBundles] stored and set it to [patchBundles].
*/
internal fun readAndSetPatchBundles() {
patchBundles = readPatchBundles().associateBy { it.name }.toMutableMap()
}
/**
* Add a [patchBundle] to the map of the stored [patchBundles] and write the set to a storage using [writePatchBundles].
*
* @param patchBundle The patch bundle to add.
*/
internal fun addPersistentlyPatchBundle(patchBundle: PatchBundle) {
patchBundles[patchBundle.name] = patchBundle
writePatchBundles(patchBundles.values.toSet())
}
/**
* Remove a path bundle from the map of [patchBundles] stored and write the set to a storage using [writePatchBundles].
*
* @param patchBundleName The name of the patch bundle to remove.
*/
internal fun removePersistentlyPatchBundle(patchBundleName: String) {
patchBundles.remove(patchBundleName)
writePatchBundles(patchBundles.values.toSet())
}
/**
* Delete the temporary files.
*/
internal fun deleteTemporaryFiles() {
temporaryFilesPath.deleteRecursively()
}
}

View File

@@ -0,0 +1,23 @@
package app.revanced.library.networking.configuration.routing
import app.revanced.library.networking.configuration.routing.routes.configurePatchBundlesRoute
import app.revanced.library.networking.configuration.routing.routes.configurePatcherRoute
import app.revanced.library.networking.configuration.routing.routes.configurePingRoute
import app.revanced.library.networking.configuration.routing.routes.configureRootRoute
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.routing.*
/**
* Configures the routing for the application.
*/
internal fun Application.configureRouting() {
routing {
authenticate {
configureRootRoute()
configurePingRoute()
configurePatchBundlesRoute()
configurePatcherRoute()
}
}
}

View File

@@ -0,0 +1,57 @@
package app.revanced.library.networking.configuration.routing.routes
import app.revanced.library.networking.parameters
import app.revanced.library.networking.services.PatchBundleService
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.routing.get
import io.ktor.server.util.*
import org.koin.ktor.ext.get
/**
* Route to handle all patch bundle related requests such as creating, reading, updating and deleting patch bundles.
*/
internal fun Route.configurePatchBundlesRoute() {
val patchBundleService = get<PatchBundleService>()
route("/patch-bundles") {
get {
call.respond(patchBundleService.patchBundleNames)
}
post("/add") {
val patchBundleName: String by parameters
val patchBundleFilePath = parameters["patchBundleFilePath"]
if (patchBundleFilePath != null) {
val patchBundleIntegrationsFilePath = parameters["patchBundleIntegrationsFilePath"]
patchBundleService.addPersistentlyLocalPatchBundle(
patchBundleName,
patchBundleFilePath,
patchBundleIntegrationsFilePath,
)
} else {
val patchBundleDownloadLink: String by parameters
val patchBundleIntegrationsDownloadLink = parameters["patchBundleIntegrationsDownloadLink"]
patchBundleService.addPersistentlyDownloadPatchBundle(
patchBundleName,
patchBundleDownloadLink,
patchBundleIntegrationsDownloadLink,
)
}
}
post("/remove") {
val patchBundleName: String by parameters
patchBundleService.removePersistentlyPatchBundle(patchBundleName)
}
post("/refresh") {
patchBundleService.refresh()
}
}
}

View File

@@ -0,0 +1,179 @@
package app.revanced.library.networking.configuration.routing.routes
import app.revanced.library.networking.configuration.repository.InstallerRepository
import app.revanced.library.networking.models.Patch
import app.revanced.library.networking.parameters
import app.revanced.library.networking.services.PatcherService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.routing.get
import io.ktor.server.util.*
import org.koin.ktor.ext.get
import java.io.File
/**
* Route to the patcher to handles all patcher related requests such as patching, signing and installing patched apps.
*/
internal fun Route.configurePatcherRoute() {
route("/patcher") {
configureAppsRoute()
configurePatchesRoute()
configurePatchOptionsRoute()
configurePatchRoute()
configureSignRoute()
configureInstallationRoute()
configureCleanRoute()
}
}
/**
* Route to list all patchable apps that can be patched.
*/
private fun Route.configureAppsRoute() {
val patcherService = get<PatcherService>()
get("/apps") {
val universal = parameters.contains("universal")
call.respond(patcherService.getInstalledApps(universal))
}
}
/**
* Route to get all patches for a specific app and version.
*/
private fun Route.configurePatchesRoute() {
val patcherService = get<PatcherService>()
get("/patches") {
val app = parameters["app"]
val version = parameters["version"]
val universal = "universal" in parameters
call.respond(patcherService.getPatches(app, version, universal))
}
}
/**
* Route to get and set patch options.
*/
private fun Route.configurePatchOptionsRoute() {
val patcherService = get<PatcherService>()
route("/options") {
get {
val app: String by parameters
val patch: String by parameters
call.respond(patcherService.getPatchOptions(patchName = patch, app))
}
post {
// Abuse serialization capabilities of Patch.PatchOption
// because Patch.KeyValuePatchOption isn't serializable.
// ONLY the Patch.PatchOption.key and Patch.PatchOption.value properties are used here.
val patchOptions: Set<Patch.PatchOption<*>> by call.receive()
val patch: String by parameters
val app: String by parameters
patcherService.setPatchOptions(
// Use Patch.PatchOption.default for Patch.KeyValuePatchOption.value.
patchOptions = patchOptions.map { Patch.KeyValuePatchOption(it) }.toSet(),
patchName = patch,
app,
)
call.respond(HttpStatusCode.OK)
}
delete {
val patch: String by parameters
val app: String by parameters
patcherService.resetPatchOptions(patchName = patch, app)
call.respond(HttpStatusCode.OK)
}
}
}
/**
* Route to patch an app with a set of patches.
*/
private fun Route.configurePatchRoute() {
val installerRepository = get<InstallerRepository>()
val patcherService = get<PatcherService>()
post("/patch") {
val patchNames = parameters.getAll("patch")?.toSet() ?: emptySet()
val multithreading = "multithreading" in parameters
// TODO: The path to the APK must be local to the server, otherwise it will not work.
val apkPath = parameters["app"]?.let {
installerRepository.installer.getInstallation(it)?.apkFilePath
} ?: parameters["apkPath"]
val apkFile = File(apkPath ?: return@post call.respond(HttpStatusCode.BadRequest))
patcherService.patch(patchNames, multithreading, apkFile)
call.respond(HttpStatusCode.OK)
}
}
/**
* Route to sign the patched APK.
*/
private fun Route.configureSignRoute() {
val patcherService = get<PatcherService>()
post("/sign") {
val signer: String by parameters
val keyStorePassword = parameters["keyStorePassword"]
val keyStoreEntryAlias: String by parameters
val keyStoreEntryPassword: String by parameters
patcherService.sign(signer, keyStorePassword, keyStoreEntryAlias, keyStoreEntryPassword)
call.respond(HttpStatusCode.OK)
}
}
/**
* Route to install or uninstall a patched APK.
*/
private fun Route.configureInstallationRoute() {
val patcherService = get<PatcherService>()
post("/install") {
val mount = parameters["mount"]
patcherService.install(mount)
call.respond(HttpStatusCode.OK)
}
post("/uninstall") {
val packageName: String by parameters
val unmount = "unmount" in parameters
patcherService.uninstall(packageName, unmount)
call.respond(HttpStatusCode.OK)
}
}
/**
* Route to delete temporary files produced by the patcher.
*/
private fun Route.configureCleanRoute() {
val patcherService = get<PatcherService>()
post("/clean") {
patcherService.deleteTemporaryFiles()
call.respond(HttpStatusCode.OK)
}
}

View File

@@ -0,0 +1,15 @@
package app.revanced.library.networking.configuration.routing.routes
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
/**
* Route to check if the server is up.
*/
internal fun Route.configurePingRoute() {
head("/ping") {
call.respond(HttpStatusCode.OK)
}
}

View File

@@ -0,0 +1,60 @@
package app.revanced.library.networking.configuration.routing.routes
import app.revanced.library.logging.Logger
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.runBlocking
import java.util.*
internal fun Route.configureRootRoute() {
route("/") {
configureAboutRoute()
configureLoggingRoute()
}
}
/**
* Route to get information about the server.
*/
private fun Route.configureAboutRoute() {
val name = this::class.java.getResourceAsStream(
"/app/revanced/library/networking/version.properties",
)?.use { stream ->
Properties().apply {
load(stream)
}.let {
"ReVanced Networking Library v${it.getProperty("version")}"
}
} ?: "ReVanced Networking Library"
handle {
call.respondText(name)
}
}
// TODO: Fix clients disconnecting from the server.
/**
* Route to get logs from the server.
*/
private fun Route.configureLoggingRoute() {
val sessions = Collections.synchronizedSet<DefaultWebSocketSession?>(LinkedHashSet())
Logger.addHandler({ log: String, level: java.util.logging.Level, loggerName: String? ->
runBlocking {
sessions.forEach {
try {
it.send("[$loggerName] $level: $log")
} catch (e: Exception) {
sessions -= it
}
}
}
}, {}, {})
webSocket("/logs") {
sessions += this
}
}

View File

@@ -0,0 +1,57 @@
@file:Suppress("unused")
package app.revanced.library.networking.models
import kotlinx.serialization.*
import java.io.File
private typealias PackageName = String
private typealias PackageVersion = String
private typealias PackageVersions = Set<PackageVersion>
private typealias CompatiblePackages = Map<PackageName, PackageVersions?>
@Serializable
open class App(
internal val name: String,
internal val version: String,
internal val packageName: String,
)
@Serializable
class Patch internal constructor(
internal val name: String,
internal val description: String?,
internal val use: Boolean,
internal val compatiblePackages: CompatiblePackages?,
) {
@Serializable
class PatchOption<T> internal constructor(
internal val key: String,
internal val default: T?,
internal val values: Map<String, T?>?,
internal val title: String?,
internal val description: String?,
internal val required: Boolean,
internal val valueType: String,
)
class KeyValuePatchOption<T>(
val key: String,
val value: T?,
val valueType: String,
) {
// Abuse serialization capabilities of Patch.PatchOption which is used in request bodies.
// Use Patch.PatchOption.default as Patch.KeyValuePatchOption.value.
internal constructor(patchOption: PatchOption<T>) : this(
patchOption.key,
patchOption.default,
patchOption.valueType,
)
}
}
class PatchBundle internal constructor(
val name: String,
val patchBundleFile: File,
val patchBundleIntegrationsFile: File? = null,
)

View File

@@ -0,0 +1,26 @@
package app.revanced.library.networking.services
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.utils.io.*
import io.ktor.utils.io.jvm.javaio.*
import java.io.File
/**
* Service for HTTP client.
*/
internal class HttpClientService {
private val client by lazy { HttpClient(CIO) }
/**
* Download a file from a URL to a file.
*
* @param file The file to download to.
* @param url The URL to download from.
*/
internal suspend fun downloadToFile(file: File, url: String) {
client.get(url).body<ByteReadChannel>().copyTo(file.outputStream())
}
}

View File

@@ -0,0 +1,85 @@
package app.revanced.library.networking.services
import app.revanced.library.networking.configuration.repository.PatchSetRepository
import app.revanced.library.networking.configuration.repository.StorageRepository
import app.revanced.library.networking.models.PatchBundle
import java.io.File
/**
* Service for patch bundles.
*
* @property storageRepository The storage repository to get storage paths from.
* @property patchSetRepository The patch set repository to get patches from.
* @property httpClientService The HTTP client service to download patch bundles with.
*/
internal class PatchBundleService(
private val storageRepository: StorageRepository,
private val patchSetRepository: PatchSetRepository,
private val httpClientService: HttpClientService,
) {
/**
* Get the names of the patch bundles stored.
*
* @return The set of patch bundle names.
*/
internal val patchBundleNames: Set<String>
get() = storageRepository.patchBundles.keys.toSet()
/**
* Add a local patch bundle to storage persistently.
*
* @param patchBundleName The name of the patch bundle.
* @param patchBundleFilePath The path to the patch bundle file.
* @param patchBundleIntegrationsFilePath The path to the patch bundle integrations file.
*/
internal fun addPersistentlyLocalPatchBundle(
patchBundleName: String,
patchBundleFilePath: String,
patchBundleIntegrationsFilePath: String?,
) = storageRepository.addPersistentlyPatchBundle(
PatchBundle(
name = patchBundleName,
patchBundleFile = File(patchBundleFilePath),
patchBundleIntegrationsFile = patchBundleIntegrationsFilePath?.let { File(it) },
),
)
/**
* Add a patch bundle that needs to be downloaded to storage persistently.
*
* @param patchBundleName The name of the patch bundle.
* @param patchBundleDownloadLink The download link to the patch bundle.
* @param patchBundleIntegrationsDownloadLink The download link to the patch bundle integrations.
*/
internal suspend fun addPersistentlyDownloadPatchBundle(
patchBundleName: String,
patchBundleDownloadLink: String,
patchBundleIntegrationsDownloadLink: String?,
) {
val withIntegrations = patchBundleIntegrationsDownloadLink != null
storageRepository.newPatchBundle(patchBundleName, withIntegrations).apply {
httpClientService.downloadToFile(patchBundleFile, patchBundleDownloadLink)
if (withIntegrations) {
httpClientService.downloadToFile(patchBundleIntegrationsFile!!, patchBundleIntegrationsDownloadLink!!)
}
}
}
/**
* Remove a patch bundle from storage persistently.
*
* @param name The name of the patch bundle to remove.
*/
internal fun removePersistentlyPatchBundle(name: String) =
storageRepository.removePersistentlyPatchBundle(name)
/**
* Reload the patch bundles from storage and read the patch set from them.
*/
internal fun refresh() {
storageRepository.readAndSetPatchBundles()
patchSetRepository.readAndSetPatchSet()
}
}

View File

@@ -0,0 +1,250 @@
package app.revanced.library.networking.services
import app.revanced.library.ApkUtils
import app.revanced.library.ApkUtils.applyTo
import app.revanced.library.installation.installer.Installer
import app.revanced.library.networking.configuration.repository.AppRepository
import app.revanced.library.networking.configuration.repository.InstallerRepository
import app.revanced.library.networking.configuration.repository.PatchSetRepository
import app.revanced.library.networking.configuration.repository.StorageRepository
import app.revanced.library.networking.models.App
import app.revanced.library.networking.models.Patch
import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherConfig
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.util.logging.Logger
/**
* Service for patching and installing apps.
*
* @property storageRepository The storage repository to get storage paths from.
* @property patchSetRepository The patch set repository to get patches from.
* @property appRepository The app repository to get installed apps from.
* @property installerRepository The installer repository to install apps with.
*/
internal class PatcherService(
private val storageRepository: StorageRepository,
private val patchSetRepository: PatchSetRepository,
private val appRepository: AppRepository,
private val installerRepository: InstallerRepository,
) {
private val logger = Logger.getLogger(PatcherService::class.simpleName)
/**
* Get installed apps.
*
* @param universal Whether to show apps that only have universal patches.
*
* @return The installed apps.
*/
internal fun getInstalledApps(universal: Boolean = true): Set<App> {
// TODO: Show apps, that only have universal patches, only if universal is true.
return appRepository.installedApps
}
/**
* Get patches.
*
* @param app The app to get patches for.
* @param version The version of the app to get patches for.
* @param universal Whether to show patches that are compatible with all apps.
*
* @return The patches.
*/
internal fun getPatches(
app: String? = null,
version: String? = null,
universal: Boolean = true,
) = if (app != null) {
patchSetRepository.patchSet.filter { patch ->
patch.compatiblePackages?.any { pkg ->
pkg.name == app && (version == null || pkg.versions?.contains(version) ?: false)
} ?: universal
}
} else {
patchSetRepository.patchSet.filter { patch ->
patch.compatiblePackages != null || universal
}
}.map { patch ->
Patch(
patch.name!!,
patch.description,
patch.use,
patch.compatiblePackages?.associate { pkg -> pkg.name to pkg.versions },
)
}.toSet()
/**
* Patch an app.
* Due to the likely-hood, that patches for the same app have the same name, duplicates are unhandled.
*
* @param patchNames The names of the patches to apply.
* @param multithreading Whether to use multi-threading for dex file writing.
* @param apkFile The APK file to patch.
*/
internal suspend fun patch(
patchNames: Set<String>,
multithreading: Boolean = false,
apkFile: File,
) = Patcher(
PatcherConfig(
apkFile = apkFile,
temporaryFilesPath = storageRepository.temporaryFilesPath,
aaptBinaryPath = storageRepository.aaptBinaryPath?.absolutePath,
frameworkFileDirectory = storageRepository.temporaryFilesPath.absolutePath,
multithreadingDexFileWriter = multithreading,
),
).use { patcher ->
val packageName = patcher.context.packageMetadata.packageName
patcher.apply {
acceptPatches(
patchSetRepository.patchSet.filter { patch ->
patch.name in patchNames && patch.compatiblePackages?.any { it.name == packageName } ?: true
}.toSet(),
)
// TODO: Only accept integrations from patch bundles that contain selected patches.
acceptIntegrations(
storageRepository.patchBundles.values.mapNotNull {
it.patchBundleIntegrationsFile
}.toSet(),
)
}
patcher.apply(false).collect { patchResult ->
patchResult.exception?.let {
StringWriter().use { writer ->
it.printStackTrace(PrintWriter(writer))
logger.severe("${patchResult.patch.name} failed:\n$writer")
}
} ?: logger.info("${patchResult.patch.name} succeeded")
}
patcher.get()
}.let { patcherResult ->
apkFile.copyTo(storageRepository.unsignedApkFilePath, overwrite = true).apply {
patcherResult.applyTo(this)
}
}
/**
* Sign an APK.
*
* @param signer The signer to use.
* @param keyStorePassword The password of the keystore.
* @param keyStoreEntryAlias The alias of the keystore entry.
* @param keyStoreEntryPassword The password of the keystore entry.
*/
internal fun sign(
signer: String,
keyStorePassword: String?,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
) = ApkUtils.signApk(
storageRepository.unsignedApkFilePath,
storageRepository.outputFilePath,
signer,
ApkUtils.KeyStoreDetails(
storageRepository.keystoreFilePath,
keyStorePassword,
keyStoreEntryAlias,
keyStoreEntryPassword,
),
)
/**
* Install an APK.
*
* @param mount The package name to mount the APK to.
*/
internal suspend fun install(mount: String?) {
if (mount != null) {
if (installerRepository.mountInstaller == null) {
throw IllegalArgumentException("Mount installer not available")
}
installerRepository.mountInstaller!! to Installer.Apk(
storageRepository.unsignedApkFilePath,
packageName = mount,
)
} else {
installerRepository.installer to Installer.Apk(storageRepository.outputFilePath)
}.let { (installer, apk) ->
installer.install(apk)
}
}
/**
* Uninstall an APK.
*
* @param packageName The package name of the APK to uninstall.
* @param unmount Whether to uninstall a mounted APK.
*/
internal suspend fun uninstall(packageName: String, unmount: Boolean) = if (unmount) {
installerRepository.mountInstaller!!
} else {
installerRepository.installer
}.uninstall(packageName)
/**
* Get patch options from [PatchSetRepository.patchSet].
* The [app] parameter is necessary in case there are patches with the same name.
* Due to the likely-hood, that patches for the same app have the same name, duplicates are unhandled.
*
* @param patchName The name of the patch to get options for.
* @param app The app to get options for.
*
* @return The patch options for the patch.
*/
internal fun getPatchOptions(patchName: String, app: String) = patchSetRepository.patchSet.single { patch ->
patch.name == patchName && patch.compatiblePackages?.any { it.name == app } ?: true
}.options.map { (key, option) ->
Patch.PatchOption(
key,
option.default,
option.values,
option.title,
option.description,
option.required,
option.valueType,
)
}.toSet()
/**
* Set patch options.
* The [app] parameter is necessary in case there are patches with the same name.
* Due to the likely-hood, that patches for the same app have the same name, duplicates are unhandled.
*
* @param patchOptions The options to set.
* @param patchName The name of the patch to set options for.
* @param app The app to set options for.
*/
internal fun setPatchOptions(
patchOptions: Set<Patch.KeyValuePatchOption<*>>,
patchName: String,
app: String,
) = patchSetRepository.patchSet.single { patch ->
patch.name == patchName && patch.compatiblePackages?.any { it.name == app } ?: true
}.options.let { options ->
patchOptions.forEach { option ->
options[option.key] = option.value
}
}
/**
* Reset patch options and persist them to the storage.
*
* @param patchName The name of the patch to reset options for.
* @param app The app to reset options for.
*/
internal fun resetPatchOptions(patchName: String, app: String) {
patchSetRepository.patchSet.single { patch ->
patch.name == patchName && patch.compatiblePackages?.any { it.name == app } ?: true
}.options.forEach { (_, option) -> option.reset() }
}
internal fun deleteTemporaryFiles() = storageRepository.deleteTemporaryFiles()
}

View File

@@ -0,0 +1 @@
version=${projectVersion}

View File

@@ -0,0 +1,337 @@
public final class app/revanced/library/ApkSigner {
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/lang/String;Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Set;)V
public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore;
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
public final fun getPrivateKey ()Ljava/security/PrivateKey;
}
public final class app/revanced/library/ApkSigner$Signer {
public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V
public final fun signApk (Ljava/io/File;)V
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
}
public final class app/revanced/library/ApkUtils {
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
public final fun newPrivateKeyCertificatePair (Lapp/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readPrivateKeyCertificatePairFromKeyStore (Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun sign (Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
public final fun sign (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V
}
public final class app/revanced/library/ApkUtils$KeyStoreDetails {
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getKeyStore ()Ljava/io/File;
public final fun getKeyStorePassword ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
}
public final class app/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails {
public fun <init> ()V
public fun <init> (Ljava/lang/String;Ljava/util/Date;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getCommonName ()Ljava/lang/String;
public final fun getValidUntil ()Ljava/util/Date;
}
public final class app/revanced/library/ApkUtils$SigningOptions {
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getKeyStore ()Ljava/io/File;
public final fun getKeyStorePassword ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getSigner ()Ljava/lang/String;
}
public final class app/revanced/library/Options {
public static final field INSTANCE Lapp/revanced/library/Options;
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch;
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
public static synthetic fun serialize$default (Lapp/revanced/library/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String;
public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V
public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V
}
public final class app/revanced/library/Options$Patch {
public final fun getOptions ()Ljava/util/List;
public final fun getPatchName ()Ljava/lang/String;
}
public final class app/revanced/library/Options$Patch$Option {
public final fun getKey ()Ljava/lang/String;
public final fun getValue ()Ljava/lang/Object;
}
public final class app/revanced/library/PatchUtils {
public static final field INSTANCE Lapp/revanced/library/PatchUtils;
public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
public static synthetic fun getMostCommonCompatibleVersions$default (Lapp/revanced/library/PatchUtils;Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
}
public final class app/revanced/library/PatchUtils$Json {
public static final field INSTANCE Lapp/revanced/library/PatchUtils$Json;
public final fun deserialize (Ljava/io/InputStream;Ljava/lang/Class;)Ljava/util/Set;
public final fun serialize (Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;)V
public static synthetic fun serialize$default (Lapp/revanced/library/PatchUtils$Json;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;ILjava/lang/Object;)V
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch : app/revanced/library/PatchUtils$Json$JsonPatch {
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$Companion;
public final fun getCompatiblePackages ()Ljava/util/Set;
public final fun getDependencies ()Ljava/util/Set;
public final fun getDescription ()Ljava/lang/String;
public final fun getName ()Ljava/lang/String;
public final fun getOptions ()Ljava/util/Map;
public final fun getRequiresIntegrations ()Z
public final fun getUse ()Z
public final fun setRequiresIntegrations (Z)V
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$Companion {
public final fun fromPatch (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch;
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption {
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion;
public final fun getDefault ()Ljava/lang/Object;
public final fun getDescription ()Ljava/lang/String;
public final fun getKey ()Ljava/lang/String;
public final fun getRequired ()Z
public final fun getTitle ()Ljava/lang/String;
public final fun getValueType ()Ljava/lang/String;
public final fun getValues ()Ljava/util/Map;
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion {
public final fun fromPatchOption (Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption;
}
public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch {
}
public final class app/revanced/library/Utils {
public static final field INSTANCE Lapp/revanced/library/Utils;
public final fun isAndroidEnvironment ()Z
}
public abstract class app/revanced/library/adb/AdbManager {
public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion;
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
protected abstract fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/adb/AdbManager$Apk {
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getFile ()Ljava/io/File;
public final fun getPackageName ()Ljava/lang/String;
}
public final class app/revanced/library/adb/AdbManager$Companion {
public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/library/adb/AdbManager;
public static synthetic fun getAdbManager$default (Lapp/revanced/library/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/library/adb/AdbManager;
}
public final class app/revanced/library/adb/AdbManager$DeviceNotFoundException : java/lang/Exception {
public fun <init> ()V
}
public final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager {
public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils;
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils {
}
public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager {
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner {
}
public abstract interface class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService : android/os/IInterface {
public static final field DESCRIPTOR Ljava/lang/String;
public abstract fun getFileSystemService ()Landroid/os/IBinder;
}
public class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Default : app/revanced/library/installation/command/ILocalShellCommandRunnerRootService {
public fun <init> ()V
public fun asBinder ()Landroid/os/IBinder;
public fun getFileSystemService ()Landroid/os/IBinder;
}
public abstract class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Stub : android/os/Binder, app/revanced/library/installation/command/ILocalShellCommandRunnerRootService {
public fun <init> ()V
public fun asBinder ()Landroid/os/IBinder;
public static fun asInterface (Landroid/os/IBinder;)Lapp/revanced/library/installation/command/ILocalShellCommandRunnerRootService;
public fun onTransact (ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
}
public final class app/revanced/library/installation/command/LocalShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner, android/content/ServiceConnection, java/io/Closeable {
public fun close ()V
public fun onServiceConnected (Landroid/content/ComponentName;Landroid/os/IBinder;)V
public fun onServiceDisconnected (Landroid/content/ComponentName;)V
}
public abstract interface class app/revanced/library/installation/command/RunResult {
public abstract fun getError ()Ljava/lang/String;
public abstract fun getExitCode ()I
public abstract fun getOutput ()Ljava/lang/String;
public abstract fun waitFor ()V
}
public final class app/revanced/library/installation/command/RunResult$DefaultImpls {
public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V
}
public abstract class app/revanced/library/installation/command/ShellCommandRunner {
protected final fun getLogger ()Ljava/util/logging/Logger;
protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
}
public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult {
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult {
public final fun getException ()Ljava/lang/Exception;
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult {
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
}
public final class app/revanced/library/installation/installer/AdbMountInstaller : app/revanced/library/installation/installer/MountInstaller {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public class app/revanced/library/installation/installer/Installation {
public final fun getApkFilePath ()Ljava/lang/String;
}
public abstract class app/revanced/library/installation/installer/Installer {
public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getLogger ()Ljava/util/logging/Logger;
public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/library/installation/installer/Installer$Apk {
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getFile ()Ljava/io/File;
public final fun getPackageName ()Ljava/lang/String;
}
public final class app/revanced/library/installation/installer/LocalInstaller : app/revanced/library/installation/installer/Installer, java/io/Closeable {
public static final field Companion Lapp/revanced/library/installation/installer/LocalInstaller$Companion;
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
public fun close ()V
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/library/installation/installer/LocalInstaller$Companion {
}
public final class app/revanced/library/installation/installer/LocalInstallerResult {
public final fun getExtra ()Ljava/lang/String;
public final fun getPackageName ()Ljava/lang/String;
public final fun getPmStatus ()I
}
public final class app/revanced/library/installation/installer/LocalInstallerService : android/app/Service {
public fun <init> ()V
public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder;
public fun onStartCommand (Landroid/content/Intent;II)I
}
public final class app/revanced/library/installation/installer/LocalMountInstaller : app/revanced/library/installation/installer/MountInstaller, java/io/Closeable {
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
}
public final class app/revanced/library/installation/installer/MountInstallation : app/revanced/library/installation/installer/Installation {
public final fun getInstalledApkFilePath ()Ljava/lang/String;
public final fun getMounted ()Z
}
public abstract class app/revanced/library/installation/installer/MountInstaller : app/revanced/library/installation/installer/Installer {
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
protected final fun move (Ljava/io/File;Ljava/lang/String;)V
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/library/installation/installer/MountInstallerResult : java/lang/Enum {
public static final field FAILURE Lapp/revanced/library/installation/installer/MountInstallerResult;
public static final field SUCCESS Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun values ()[Lapp/revanced/library/installation/installer/MountInstallerResult;
}
public final class app/revanced/library/logging/Logger {
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
public final fun removeAllHandlers ()V
public final fun setDefault ()V
public final fun setFormat (Ljava/lang/String;)V
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
}

283
library/api/jvm/library.api Normal file
View File

@@ -0,0 +1,283 @@
public final class app/revanced/library/ApkSigner {
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/lang/String;Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Set;)V
public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore;
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
public final fun getPrivateKey ()Ljava/security/PrivateKey;
}
public final class app/revanced/library/ApkSigner$Signer {
public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V
public final fun signApk (Ljava/io/File;)V
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
}
public final class app/revanced/library/ApkUtils {
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
public final fun newPrivateKeyCertificatePair (Lapp/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readPrivateKeyCertificatePairFromKeyStore (Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun sign (Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
public final fun sign (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V
}
public final class app/revanced/library/ApkUtils$KeyStoreDetails {
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getKeyStore ()Ljava/io/File;
public final fun getKeyStorePassword ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
}
public final class app/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails {
public fun <init> ()V
public fun <init> (Ljava/lang/String;Ljava/util/Date;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getCommonName ()Ljava/lang/String;
public final fun getValidUntil ()Ljava/util/Date;
}
public final class app/revanced/library/ApkUtils$SigningOptions {
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getKeyStore ()Ljava/io/File;
public final fun getKeyStorePassword ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getSigner ()Ljava/lang/String;
}
public final class app/revanced/library/Options {
public static final field INSTANCE Lapp/revanced/library/Options;
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch;
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
public static synthetic fun serialize$default (Lapp/revanced/library/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String;
public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V
public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V
}
public final class app/revanced/library/Options$Patch {
public final fun getOptions ()Ljava/util/List;
public final fun getPatchName ()Ljava/lang/String;
}
public final class app/revanced/library/Options$Patch$Option {
public final fun getKey ()Ljava/lang/String;
public final fun getValue ()Ljava/lang/Object;
}
public final class app/revanced/library/PatchUtils {
public static final field INSTANCE Lapp/revanced/library/PatchUtils;
public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
public static synthetic fun getMostCommonCompatibleVersions$default (Lapp/revanced/library/PatchUtils;Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
}
public final class app/revanced/library/PatchUtils$Json {
public static final field INSTANCE Lapp/revanced/library/PatchUtils$Json;
public final fun deserialize (Ljava/io/InputStream;Ljava/lang/Class;)Ljava/util/Set;
public final fun serialize (Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;)V
public static synthetic fun serialize$default (Lapp/revanced/library/PatchUtils$Json;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;ILjava/lang/Object;)V
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch : app/revanced/library/PatchUtils$Json$JsonPatch {
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$Companion;
public final fun getCompatiblePackages ()Ljava/util/Set;
public final fun getDependencies ()Ljava/util/Set;
public final fun getDescription ()Ljava/lang/String;
public final fun getName ()Ljava/lang/String;
public final fun getOptions ()Ljava/util/Map;
public final fun getRequiresIntegrations ()Z
public final fun getUse ()Z
public final fun setRequiresIntegrations (Z)V
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$Companion {
public final fun fromPatch (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch;
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption {
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion;
public final fun getDefault ()Ljava/lang/Object;
public final fun getDescription ()Ljava/lang/String;
public final fun getKey ()Ljava/lang/String;
public final fun getRequired ()Z
public final fun getTitle ()Ljava/lang/String;
public final fun getValueType ()Ljava/lang/String;
public final fun getValues ()Ljava/util/Map;
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion {
public final fun fromPatchOption (Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption;
}
public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch {
}
public final class app/revanced/library/Utils {
public static final field INSTANCE Lapp/revanced/library/Utils;
public final fun isAndroidEnvironment ()Z
}
public abstract class app/revanced/library/adb/AdbManager {
public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion;
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
protected abstract fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/adb/AdbManager$Apk {
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getFile ()Ljava/io/File;
public final fun getPackageName ()Ljava/lang/String;
}
public final class app/revanced/library/adb/AdbManager$Companion {
public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/library/adb/AdbManager;
public static synthetic fun getAdbManager$default (Lapp/revanced/library/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/library/adb/AdbManager;
}
public final class app/revanced/library/adb/AdbManager$DeviceNotFoundException : java/lang/Exception {
public fun <init> ()V
}
public final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager {
public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils;
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils {
}
public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager {
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner {
}
public abstract interface class app/revanced/library/installation/command/RunResult {
public abstract fun getError ()Ljava/lang/String;
public abstract fun getExitCode ()I
public abstract fun getOutput ()Ljava/lang/String;
public abstract fun waitFor ()V
}
public final class app/revanced/library/installation/command/RunResult$DefaultImpls {
public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V
}
public abstract class app/revanced/library/installation/command/ShellCommandRunner {
protected final fun getLogger ()Ljava/util/logging/Logger;
protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
}
public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult {
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult {
public final fun getException ()Ljava/lang/Exception;
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult {
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
}
public final class app/revanced/library/installation/installer/AdbMountInstaller : app/revanced/library/installation/installer/MountInstaller {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public class app/revanced/library/installation/installer/Installation {
public final fun getApkFilePath ()Ljava/lang/String;
}
public abstract class app/revanced/library/installation/installer/Installer {
public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getLogger ()Ljava/util/logging/Logger;
public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/library/installation/installer/Installer$Apk {
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getFile ()Ljava/io/File;
public final fun getPackageName ()Ljava/lang/String;
}
public final class app/revanced/library/installation/installer/MountInstallation : app/revanced/library/installation/installer/Installation {
public final fun getInstalledApkFilePath ()Ljava/lang/String;
public final fun getMounted ()Z
}
public abstract class app/revanced/library/installation/installer/MountInstaller : app/revanced/library/installation/installer/Installer {
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
protected final fun move (Ljava/io/File;Ljava/lang/String;)V
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/library/installation/installer/MountInstallerResult : java/lang/Enum {
public static final field FAILURE Lapp/revanced/library/installation/installer/MountInstallerResult;
public static final field SUCCESS Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun values ()[Lapp/revanced/library/installation/installer/MountInstallerResult;
}
public final class app/revanced/library/logging/Logger {
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
public final fun removeAllHandlers ()V
public final fun setDefault ()V
public final fun setFormat (Ljava/lang/String;)V
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
}

123
library/build.gradle.kts Normal file
View File

@@ -0,0 +1,123 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.library)
alias(libs.plugins.binary.compatibility.validator)
`maven-publish`
signing
}
kotlin {
jvm {
compilations.all {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
}
}
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
}
publishLibraryVariants("release")
}
sourceSets {
androidMain.dependencies {
implementation(libs.libsu.nio)
implementation(libs.libsu.service)
implementation(libs.core.ktx)
}
commonMain.dependencies {
implementation(libs.revanced.patcher)
implementation(libs.kotlin.reflect)
implementation(libs.jadb) // Fork with Shell v2 support.
implementation(libs.bcpkix.jdk15on)
implementation(libs.jackson.module.kotlin)
implementation(libs.apkzlib)
implementation(libs.apksig)
implementation(libs.guava)
}
commonTest.dependencies {
implementation(libs.revanced.patcher)
implementation(libs.kotlin.test.junit)
}
}
}
android {
namespace = "app.revanced.library"
compileSdk = 34
defaultConfig {
minSdk = 26
}
buildFeatures {
aidl = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
java {
targetCompatibility = JavaVersion.VERSION_11
}
publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-library")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
publications {
create<MavenPublication>("revanced-library-publication") {
version = project.version.toString()
pom {
name = "ReVanced Library"
description = "Library containing common utilities for ReVanced"
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-library.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
url = "https://github.com/revanced/revanced-library"
}
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications["revanced-library-publication"])
}

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

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.MountInstaller.NoRootPermissionException
import com.topjohnwu.superuser.ipc.RootService
import java.io.Closeable
/**
* [LocalMountInstaller] for installing and uninstalling [Apk] files locally with using root permissions by mounting.
*
* @param context The [Context] to use for binding to the [RootService].
* @param onReady A callback to be invoked when [LocalMountInstaller] is ready to be used.
*
* @throws NoRootPermissionException If the device does not have root permission.
*
* @see Installer
* @see LocalShellCommandRunner
*/
@Suppress("unused")
class LocalMountInstaller(
context: Context,
onReady: LocalMountInstaller.() -> Unit = {},
) : MountInstaller(
{ installer ->
LocalShellCommandRunner(context) {
(installer as LocalMountInstaller).onReady()
}
},
),
Closeable {
override fun close() = (shellCommandRunner as LocalShellCommandRunner).close()
}

View File

@@ -0,0 +1,369 @@
package app.revanced.library
import com.android.apksig.ApkSigner.SignerConfig
import com.android.tools.build.apkzlib.sign.SigningExtension
import com.android.tools.build.apkzlib.sign.SigningOptions
import com.android.tools.build.apkzlib.zip.ZFile
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.math.BigInteger
import java.security.*
import java.security.cert.X509Certificate
import java.util.*
import java.util.logging.Logger
/**
* Utility class for reading or writing keystore files and entries as well as signing APK files.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object ApkSigner {
private val logger = Logger.getLogger(ApkSigner::class.java.name)
init {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(BouncyCastleProvider())
}
}
private fun newKeyStoreInstance() = KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME)
/**
* Create a new keystore with a new keypair.
*
* @param entries The entries to add to the keystore.
*
* @return The created keystore.
*
* @see KeyStoreEntry
* @see KeyStore
*/
fun newKeyStore(entries: Set<KeyStoreEntry>): KeyStore {
logger.fine("Creating keystore")
return newKeyStoreInstance().apply {
load(null)
entries.forEach { entry ->
// Add all entries to the keystore.
setKeyEntry(
entry.alias,
entry.privateKeyCertificatePair.privateKey,
entry.password.toCharArray(),
arrayOf(entry.privateKeyCertificatePair.certificate),
)
}
}
}
/**
* Read a keystore from the given [keyStoreInputStream].
*
* @param keyStoreInputStream The stream to read the keystore from.
* @param keyStorePassword The password for the keystore.
*
* @return The keystore.
*
* @throws IllegalArgumentException If the keystore password is invalid.
*
* @see KeyStore
*/
fun readKeyStore(
keyStoreInputStream: InputStream,
keyStorePassword: String?,
): KeyStore {
logger.fine("Reading keystore")
return newKeyStoreInstance().apply {
try {
load(keyStoreInputStream, keyStorePassword?.toCharArray())
} catch (exception: IOException) {
if (exception.cause is UnrecoverableKeyException) {
throw IllegalArgumentException("Invalid keystore password")
} else {
throw exception
}
}
}
}
/**
* Create a new private key and certificate pair.
*
* @param commonName The common name of the certificate.
* @param validUntil The date until which the certificate is valid.
*
* @return The newly created private key and certificate pair.
*
* @see PrivateKeyCertificatePair
*/
fun newPrivateKeyCertificatePair(
commonName: String,
validUntil: Date,
): PrivateKeyCertificatePair {
logger.fine("Creating certificate for $commonName")
// Generate a new key pair.
val keyPair = KeyPairGenerator.getInstance("RSA").apply {
initialize(4096)
}.generateKeyPair()
val contentSigner = JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private)
val name = X500Name("CN=$commonName")
val certificateHolder = X509v3CertificateBuilder(
name,
BigInteger.valueOf(SecureRandom().nextLong()),
Date(System.currentTimeMillis()),
validUntil,
Locale.ENGLISH,
name,
SubjectPublicKeyInfo.getInstance(keyPair.public.encoded),
).build(contentSigner)
val certificate = JcaX509CertificateConverter().getCertificate(certificateHolder)
return PrivateKeyCertificatePair(keyPair.private, certificate)
}
/**
* Read a [PrivateKeyCertificatePair] from a keystore entry.
*
* @param keyStore The keystore to read the entry from.
* @param keyStoreEntryAlias The alias of the key store entry to read.
* @param keyStoreEntryPassword The password for recovering the signing key.
*
* @return The read [PrivateKeyCertificatePair].
*
* @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid.
*
* @see PrivateKeyCertificatePair
* @see KeyStore
*/
fun readPrivateKeyCertificatePair(
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
): PrivateKeyCertificatePair {
logger.fine("Reading key and certificate pair from keystore entry $keyStoreEntryAlias")
if (!keyStore.containsAlias(keyStoreEntryAlias)) {
throw IllegalArgumentException("Keystore does not contain entry with alias $keyStoreEntryAlias")
}
// Read the private key and certificate from the keystore.
val privateKey =
try {
keyStore.getKey(keyStoreEntryAlias, keyStoreEntryPassword.toCharArray()) as PrivateKey
} catch (exception: UnrecoverableKeyException) {
throw IllegalArgumentException("Invalid password for keystore entry $keyStoreEntryAlias")
}
val certificate = keyStore.getCertificate(keyStoreEntryAlias) as X509Certificate
return PrivateKeyCertificatePair(privateKey, certificate)
}
/**
* Create a new [Signer].
*
* @param signer The name of the signer.
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
*
* @return The new [Signer].
*
* @see PrivateKeyCertificatePair
* @see Signer
*/
fun newApkSigner(
signer: String,
privateKeyCertificatePair: PrivateKeyCertificatePair,
) = Signer(
com.android.apksig.ApkSigner.Builder(
listOf(
SignerConfig.Builder(
signer,
privateKeyCertificatePair.privateKey,
listOf(privateKeyCertificatePair.certificate),
).build(),
),
),
)
/**
* Read a [PrivateKeyCertificatePair] from a keystore entry.
*
* @param keyStore The keystore to read the entry from.
* @param keyStoreEntryAlias The alias of the key store entry to read.
* @param keyStoreEntryPassword The password for recovering the signing key.
*
* @return The read [PrivateKeyCertificatePair].
*
* @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid.
*/
@Deprecated("This method will be removed in the future.")
fun readKeyCertificatePair(
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
) = readPrivateKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword)
/**
* Create a new keystore with a new keypair and saves it to the given [keyStoreOutputStream].
*
* @param keyStoreOutputStream The stream to write the keystore to.
* @param keyStorePassword The password for the keystore.
* @param entries The entries to add to the keystore.
*/
@Deprecated("This method will be removed in the future.")
fun newKeyStore(
keyStoreOutputStream: OutputStream,
keyStorePassword: String?,
entries: Set<KeyStoreEntry>,
) = newKeyStore(entries).store(
keyStoreOutputStream,
keyStorePassword?.toCharArray(),
)
/**
* Create a new [Signer].
*
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
*
* @return The new [Signer].
*
* @see PrivateKeyCertificatePair
* @see Signer
*/
@Deprecated("This method will be removed in the future.")
fun newApkSigner(privateKeyCertificatePair: PrivateKeyCertificatePair) =
Signer(
SigningExtension(
SigningOptions.builder()
.setMinSdkVersion(21) // TODO: Extracting from the target APK would be ideal.
.setV1SigningEnabled(true)
.setV2SigningEnabled(true)
.setCertificates(privateKeyCertificatePair.certificate)
.setKey(privateKeyCertificatePair.privateKey)
.build(),
),
)
/**
* Create a new [Signer].
*
* @param signer The name of the signer.
* @param keyStore The keystore to use for signing.
* @param keyStoreEntryAlias The alias of the key store entry to use for signing.
* @param keyStoreEntryPassword The password for recovering the signing key.
*
* @return The new [Signer].
*
* @see KeyStore
* @see Signer
*/
@Deprecated("This method will be removed in the future.")
fun newApkSigner(
signer: String,
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
) = newApkSigner(signer, readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))
/**
* Create a new [Signer].
*
* @param keyStore The keystore to use for signing.
* @param keyStoreEntryAlias The alias of the key store entry to use for signing.
* @param keyStoreEntryPassword The password for recovering the signing key.
*
* @return The new [Signer].
*
* @see KeyStore
* @see Signer
*/
@Deprecated("This method will be removed in the future.")
fun newApkSigner(
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
) = newApkSigner("ReVanced", readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))
/**
* An entry in a keystore.
*
* @param alias The alias of the entry.
* @param password The password for recovering the signing key.
* @param privateKeyCertificatePair The private key and certificate pair.
*
* @see PrivateKeyCertificatePair
*/
class KeyStoreEntry(
val alias: String,
val password: String,
val privateKeyCertificatePair: PrivateKeyCertificatePair,
)
/**
* A private key and certificate pair.
*
* @param privateKey The private key.
* @param certificate The certificate.
*/
class PrivateKeyCertificatePair(
val privateKey: PrivateKey,
val certificate: X509Certificate,
)
class Signer {
private val signerBuilder: com.android.apksig.ApkSigner.Builder?
private val signingExtension: SigningExtension?
internal constructor(signerBuilder: com.android.apksig.ApkSigner.Builder) {
this.signerBuilder = signerBuilder
signingExtension = null
}
fun signApk(inputApkFile: File, outputApkFile: File) {
logger.info("Signing APK")
signerBuilder?.setInputApk(inputApkFile)?.setOutputApk(outputApkFile)?.build()?.sign()
}
@Deprecated("This constructor will be removed in the future.")
internal constructor(signingExtension: SigningExtension) {
signerBuilder = null
this.signingExtension = signingExtension
}
/**
* Sign an APK file.
*
* @param apkFile The APK file to sign.
*/
@Deprecated("This method will be removed in the future.")
fun signApk(apkFile: File) = ZFile.openReadWrite(apkFile).use {
@Suppress("DEPRECATION")
signApk(it)
}
/**
* Sign an APK file.
*
* @param apkZFile The APK [ZFile] to sign.
*/
@Deprecated("This method will be removed in the future.")
fun signApk(apkZFile: ZFile) {
logger.info("Signing ${apkZFile.file.name}")
signingExtension?.register(apkZFile)
}
}
}

View File

@@ -0,0 +1,281 @@
package app.revanced.library
import app.revanced.library.ApkSigner.newPrivateKeyCertificatePair
import app.revanced.patcher.PatcherResult
import com.android.tools.build.apkzlib.zip.AlignmentRules
import com.android.tools.build.apkzlib.zip.StoredEntry
import com.android.tools.build.apkzlib.zip.ZFile
import com.android.tools.build.apkzlib.zip.ZFileOptions
import java.io.File
import java.util.*
import java.util.logging.Logger
import kotlin.time.Duration.Companion.days
/**
* Utility functions to work with APK files.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object ApkUtils {
private val logger = Logger.getLogger(ApkUtils::class.java.name)
private const val LIBRARY_EXTENSION = ".so"
// Alignment for native libraries.
private const val LIBRARY_ALIGNMENT = 1024 * 4
// Alignment for all other files.
private const val DEFAULT_ALIGNMENT = 4
// Prefix for resources.
private const val RES_PREFIX = "res/"
private val zFileOptions =
ZFileOptions().setAlignmentRule(
AlignmentRules.compose(
AlignmentRules.constantForSuffix(LIBRARY_EXTENSION, LIBRARY_ALIGNMENT),
AlignmentRules.constant(DEFAULT_ALIGNMENT),
),
)
/**
* Applies the [PatcherResult] to the given [apkFile].
*
* The order of operation is as follows:
* 1. Write patched dex files.
* 2. Delete all resources in the target APK
* 3. Merge resources.apk compiled by AAPT.
* 4. Write raw resources.
* 5. Delete resources staged for deletion.
* 6. Realign the APK.
*
* @param apkFile The file to apply the patched files to.
*/
fun PatcherResult.applyTo(apkFile: File) {
ZFile.openReadWrite(apkFile, zFileOptions).use { targetApkZFile ->
dexFiles.forEach { dexFile ->
targetApkZFile.add(dexFile.name, dexFile.stream)
}
resources?.let { resources ->
// Add resources compiled by AAPT.
resources.resourcesApk?.let { resourcesApk ->
ZFile.openReadOnly(resourcesApk).use { resourcesApkZFile ->
// Delete all resources in the target APK before merging the new ones.
// This is necessary because the resources.apk renames resources.
// So unless, the old resources are deleted, there will be orphaned resources in the APK.
// It is not necessary, but for the sake of cleanliness, it is done.
targetApkZFile.entries().filter { entry ->
entry.centralDirectoryHeader.name.startsWith(RES_PREFIX)
}.forEach(StoredEntry::delete)
targetApkZFile.mergeFrom(resourcesApkZFile) { false }
}
}
// Add resources not compiled by AAPT.
resources.otherResources?.let { otherResources ->
targetApkZFile.addAllRecursively(otherResources) { file ->
file.relativeTo(otherResources).invariantSeparatorsPath !in resources.doNotCompress
}
}
// Delete resources that were staged for deletion.
if (resources.deleteResources.isNotEmpty()) {
targetApkZFile.entries().filter { entry ->
resources.deleteResources.any { shouldDelete -> shouldDelete(entry.centralDirectoryHeader.name) }
}.forEach(StoredEntry::delete)
}
}
logger.info("Aligning APK")
targetApkZFile.realign()
logger.fine("Writing changes")
}
}
/**
* Creates a new private key and certificate pair and saves it to the keystore in [keyStoreDetails].
*
* @param privateKeyCertificatePairDetails The details for the private key and certificate pair.
* @param keyStoreDetails The details for the keystore.
*
* @return The newly created private key and certificate pair.
*/
@Deprecated("This method will be removed in the future.")
fun newPrivateKeyCertificatePair(
privateKeyCertificatePairDetails: PrivateKeyCertificatePairDetails,
keyStoreDetails: KeyStoreDetails,
) = newPrivateKeyCertificatePair(
privateKeyCertificatePairDetails.commonName,
privateKeyCertificatePairDetails.validUntil,
).also { privateKeyCertificatePair ->
ApkSigner.newKeyStore(
setOf(
ApkSigner.KeyStoreEntry(
keyStoreDetails.alias,
keyStoreDetails.password,
privateKeyCertificatePair,
),
),
).store(
keyStoreDetails.keyStore.outputStream(),
keyStoreDetails.keyStorePassword?.toCharArray(),
)
}
/**
* Reads the private key and certificate pair from an existing keystore.
*
* @param keyStoreDetails The details for the keystore.
*
* @return The private key and certificate pair.
*/
@Deprecated("This method will be removed in the future.")
fun readPrivateKeyCertificatePairFromKeyStore(
keyStoreDetails: KeyStoreDetails,
) = ApkSigner.readPrivateKeyCertificatePair(
ApkSigner.readKeyStore(
keyStoreDetails.keyStore.inputStream(),
keyStoreDetails.keyStorePassword,
),
keyStoreDetails.alias,
keyStoreDetails.password,
)
/**
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
* If [KeyStoreDetails.keyStore] does not exist,
* a new private key and certificate pair will be created and saved to the keystore.
*
* @param inputApkFile The apk file to sign.
* @param outputApkFile The file to save the signed apk to.
* @param signer The name of the signer.
* @param keyStoreDetails The details for the keystore.
*/
fun signApk(
inputApkFile: File,
outputApkFile: File,
signer: String,
keyStoreDetails: KeyStoreDetails,
) = ApkSigner.newApkSigner(
signer,
if (keyStoreDetails.keyStore.exists()) {
readPrivateKeyCertificatePairFromKeyStore(keyStoreDetails)
} else {
newPrivateKeyCertificatePair(PrivateKeyCertificatePairDetails(), keyStoreDetails)
},
).signApk(inputApkFile, outputApkFile)
@Deprecated("This method will be removed in the future.")
private fun readOrNewPrivateKeyCertificatePair(
signingOptions: SigningOptions,
): ApkSigner.PrivateKeyCertificatePair {
val privateKeyCertificatePairDetails = PrivateKeyCertificatePairDetails(
signingOptions.alias,
PrivateKeyCertificatePairDetails().validUntil,
)
val keyStoreDetails = KeyStoreDetails(
signingOptions.keyStore,
signingOptions.keyStorePassword,
signingOptions.alias,
signingOptions.password,
)
return if (keyStoreDetails.keyStore.exists()) {
readPrivateKeyCertificatePairFromKeyStore(keyStoreDetails)
} else {
newPrivateKeyCertificatePair(privateKeyCertificatePairDetails, keyStoreDetails)
}
}
/**
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
*
* @param inputApkFile The apk file to sign.
* @param outputApkFile The file to save the signed apk to.
* @param signer The name of the signer.
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
*/
@Deprecated("This method will be removed in the future.")
fun sign(
inputApkFile: File,
outputApkFile: File,
signer: String,
privateKeyCertificatePair: ApkSigner.PrivateKeyCertificatePair,
) = ApkSigner.newApkSigner(
signer,
privateKeyCertificatePair,
).signApk(inputApkFile, outputApkFile)
/**
* Signs the apk file with the given options.
*
* @param signingOptions The options to use for signing.
*/
@Deprecated("This method will be removed in the future.")
fun File.sign(signingOptions: SigningOptions) = ApkSigner.newApkSigner(
signingOptions.signer,
readOrNewPrivateKeyCertificatePair(signingOptions),
).signApk(this)
/**
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
*
* @param inputApkFile The apk file to sign.
* @param outputApkFile The file to save the signed apk to.
* @param signingOptions The options to use for signing.
*/
@Deprecated("This method will be removed in the future.")
fun sign(inputApkFile: File, outputApkFile: File, signingOptions: SigningOptions) = sign(
inputApkFile,
outputApkFile,
signingOptions.signer,
readOrNewPrivateKeyCertificatePair(signingOptions),
)
/**
* Options for signing an apk.
*
* @param keyStore The keystore to use for signing.
* @param keyStorePassword The password for the keystore.
* @param alias The alias of the key store entry to use for signing.
* @param password The password for recovering the signing key.
* @param signer The name of the signer.
*/
@Deprecated("This class will be removed in the future.")
class SigningOptions(
val keyStore: File,
val keyStorePassword: String?,
val alias: String = "ReVanced Key",
val password: String = "",
val signer: String = "ReVanced",
)
/**
* Details for a keystore.
*
* @param keyStore The file to save the keystore to.
* @param keyStorePassword The password for the keystore.
* @param alias The alias of the key store entry to use for signing.
* @param password The password for recovering the signing key.
*/
class KeyStoreDetails(
val keyStore: File,
val keyStorePassword: String? = null,
val alias: String = "ReVanced Key",
val password: String = "",
)
/**
* Details for a private key and certificate pair.
*
* @param commonName The common name for the certificate saved in the keystore.
* @param validUntil The date until which the certificate is valid.
*/
class PrivateKeyCertificatePairDetails(
val commonName: String = "ReVanced",
val validUntil: Date = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24),
)
}

View File

@@ -0,0 +1,43 @@
@file:Suppress("DeprecatedCallableAddReplaceWith")
package app.revanced.library
import app.revanced.library.installation.command.AdbShellCommandRunner
import se.vidstige.jadb.JadbDevice
import se.vidstige.jadb.ShellProcessBuilder
import java.io.File
@Deprecated("Do not use this anymore. Instead use AdbCommandRunner.")
internal fun JadbDevice.buildCommand(
command: String,
su: Boolean = true,
): ShellProcessBuilder {
if (su) return shellProcessBuilder("su -c \'$command\'")
val args = command.split(" ") as ArrayList<String>
val cmd = args.removeFirst()
return shellProcessBuilder(cmd, *args.toTypedArray())
}
@Suppress("DEPRECATION")
@Deprecated("Use AdbShellCommandRunner instead.")
internal fun JadbDevice.run(
command: String,
su: Boolean = true,
) = buildCommand(command, su).start()
@Deprecated("Use AdbShellCommandRunner instead.")
internal fun JadbDevice.hasSu() = AdbShellCommandRunner(this).hasRootPermission()
@Deprecated("Use AdbShellCommandRunner instead.")
internal fun JadbDevice.push(
file: File,
targetFilePath: String,
) = AdbShellCommandRunner(this).move(file, targetFilePath)
@Deprecated("Use AdbShellCommandRunner instead.")
internal fun JadbDevice.createFile(
targetFile: String,
content: String,
) = AdbShellCommandRunner(this).write(content.byteInputStream(), targetFile)

View File

@@ -2,58 +2,69 @@
package app.revanced.library
import app.revanced.library.Options.Patch.Option
import app.revanced.patcher.PatchClass
import app.revanced.patcher.PatchSet
import app.revanced.patcher.patch.options.PatchOptionException
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import java.io.File
import java.util.logging.Logger
private typealias PatchList = List<PatchClass>
@Suppress("unused")
object Options {
private val logger = Logger.getLogger(Options::class.java.name)
private var mapper = jacksonObjectMapper()
private val mapper = jacksonObjectMapper()
/**
* Serializes the options for the patches in the list.
* Serializes the options for a set of patches.
*
* @param patches The list of patches to serialize.
* @param patches The set of patches to serialize.
* @param prettyPrint Whether to pretty print the JSON.
* @return The JSON string containing the options.
*/
fun serialize(patches: PatchSet, prettyPrint: Boolean = false): String = patches
.filter { it.options.any() }
.map { patch ->
Patch(
patch.name!!,
patch.options.values.map { option -> Option(option.key, option.value) }
)
}
// See https://github.com/revanced/revanced-patches/pull/2434/commits/60e550550b7641705e81aa72acfc4faaebb225e7.
.distinctBy { it.patchName }
.let {
if (prettyPrint)
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(it)
else
mapper.writeValueAsString(it)
}
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 for the patches in the list.
* Deserializes the options to a set of patches.
*
* @param json The JSON string containing the options.
* @return The list of [Patch]s.
* @return A set of [Patch]s.
* @see Patch
* @see PatchList
*/
fun deserialize(json: String): Array<Patch> = mapper.readValue(json, Array<Patch>::class.java)
/**
* Sets the options for the patches in the list.
* Sets the options for a set of patches.
*
* @param json The JSON string containing the options.
*/
@@ -61,9 +72,10 @@ object Options {
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 }
}
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 ->
@@ -71,7 +83,7 @@ object Options {
try {
patch.options[option] = value
} catch (e: PatchOptionException) {
logger.severe(e.toString())
logger.warning("Could not set option value for the ${patch.name} patch: ${e.message}")
}
}
}
@@ -80,7 +92,7 @@ object Options {
}
/**
* Sets the options for the patches in the list.
* Sets the options for a set of patches.
*
* @param file The file containing the JSON string containing the options.
* @see setOptions
@@ -95,9 +107,8 @@ object Options {
*/
class Patch internal constructor(
val patchName: String,
val options: List<Option>
val options: List<Option>,
) {
/**
* Data class for patch option.
*
@@ -106,4 +117,4 @@ object Options {
*/
class Option internal constructor(val key: String, val value: Any?)
}
}
}

View File

@@ -0,0 +1,169 @@
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

@@ -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,144 @@
@file:Suppress("DEPRECATION")
package app.revanced.library.adb
import app.revanced.library.adb.AdbManager.Apk
import app.revanced.library.installation.installer.AdbInstaller
import app.revanced.library.installation.installer.AdbMountInstaller
import app.revanced.library.installation.installer.Constants.PLACEHOLDER
import app.revanced.library.installation.installer.Installer
import app.revanced.library.run
import se.vidstige.jadb.JadbDevice
import java.io.File
/**
* [AdbManager] to install and uninstall [Apk] files.
*
* @param deviceSerial The serial of the device. If null, the first connected device will be used.
*/
@Deprecated("Use an implementation of Installer instead.")
@Suppress("unused")
sealed class AdbManager private constructor(
@Suppress("UNUSED_PARAMETER") deviceSerial: String?,
) {
protected abstract val installer: Installer<*, *>
/**
* Installs the [Apk] file.
*
* @param apk The [Apk] file.
*/
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use Installer.install instead.")
open fun install(apk: Apk) = suspend {
installer.install(Installer.Apk(apk.file, apk.packageName))
}
/**
* Uninstalls the package.
*
* @param packageName The package name.
*/
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use Installer.uninstall instead.")
open fun uninstall(packageName: String) = suspend {
installer.uninstall(packageName)
}
@Deprecated("Use Installer instead.")
companion object {
/**
* Gets an [AdbManager] for the supplied device serial.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
* @param root Whether to use root or not.
* @return The [AdbManager].
* @throws DeviceNotFoundException If the device can not be found.
*/
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("This is deprecated.")
fun getAdbManager(
deviceSerial: String? = null,
root: Boolean = false,
): AdbManager = if (root) RootAdbManager(deviceSerial) else UserAdbManager(deviceSerial)
}
/**
* Adb manager for rooted devices.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*/
@Deprecated("Use AdbMountInstaller instead.", ReplaceWith("AdbMountInstaller(deviceSerial)"))
class RootAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) {
override val installer = AdbMountInstaller(deviceSerial)
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use AdbMountInstaller.install instead.")
override fun install(apk: Apk) = suspend {
installer.install(Installer.Apk(apk.file, apk.packageName))
}
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use AdbMountInstaller.uninstall instead.")
override fun uninstall(packageName: String) = suspend {
installer.uninstall(packageName)
}
@Deprecated("This is deprecated.")
companion object Utils {
private fun JadbDevice.run(
command: String,
with: String,
) = run(command.applyReplacement(with))
private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
}
}
/**
* Adb manager for non-rooted devices.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*/
@Deprecated("Use AdbInstaller instead.")
class UserAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) {
override val installer = AdbInstaller(deviceSerial)
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use AdbInstaller.install instead.")
override fun install(apk: Apk) = suspend {
installer.install(Installer.Apk(apk.file, apk.packageName))
}
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use AdbInstaller.uninstall instead.")
override fun uninstall(packageName: String) = suspend {
installer.uninstall(packageName)
}
}
/**
* Apk file for [AdbManager].
*
* @param file The [Apk] file.
* @param packageName The package name of the [Apk] file.
*/
@Deprecated("Use Installer.Apk instead.")
class Apk(val file: File, val packageName: String? = null)
@Deprecated("Use AdbCommandRunner.DeviceNotFoundException instead.")
class DeviceNotFoundException internal constructor(deviceSerial: String? = null) :
Exception(
deviceSerial?.let {
"The device with the ADB device serial \"$deviceSerial\" can not be found"
} ?: "No ADB device found",
)
@Deprecated("Use MountInstaller.FailedToFindInstalledPackageException instead.")
class FailedToFindInstalledPackageException internal constructor(packageName: String) :
Exception("Failed to find installed package \"$packageName\" because no activity was found")
@Deprecated("Use MountInstaller.PackageNameRequiredException instead.")
class PackageNameRequiredException internal constructor() :
Exception("Package name is required")
}

View File

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

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

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.MountInstaller.NoRootPermissionException
/**
* [AdbMountInstaller] for installing and uninstalling [Apk] files with using ADB root permissions by mounting.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*
* @throws NoRootPermissionException If the device does not have root permission.
*
* @see MountInstaller
* @see AdbShellCommandRunner
*/
class AdbMountInstaller(
deviceSerial: String? = null,
) : MountInstaller({ AdbShellCommandRunner(deviceSerial) }) {
init {
logger.fine("Connected to $deviceSerial")
}
}

View File

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

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
/**
* [MountInstallation] of the apk file that is mounted to [installedApkFilePath] with root permissions.
*
* @param installedApkFilePath The installed apk file path or null if the apk is not installed.
* @param apkFilePath The mounting apk file path.
* @param mounted Whether the apk is mounted to [installedApkFilePath].
*/
@Suppress("MemberVisibilityCanBePrivate")
class MountInstallation internal constructor(
val installedApkFilePath: String?,
apkFilePath: String,
val mounted: Boolean,
) : Installation(apkFilePath)

View File

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

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 [MountInstaller].
*
* @see MountInstaller
*/
enum class MountInstallerResult {
/**
* The result of installing an [Apk] successfully.
*/
SUCCESS,
/**
* The result of installing an [Apk] unsuccessfully.
*/
FAILURE,
}

View File

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

View File

@@ -5,8 +5,19 @@ 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>(
// 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("")
/**
@@ -40,13 +51,14 @@ object Logger {
fun addHandler(
publishHandler: (log: String, level: Level, loggerName: String?) -> Unit,
flushHandler: () -> Unit,
closeHandler: () -> Unit
closeHandler: () -> Unit,
) = object : Handler() {
override fun publish(record: LogRecord) = publishHandler(
formatter.format(record),
record.level,
record.loggerName
)
override fun publish(record: LogRecord) =
publishHandler(
formatter.format(record),
record.level,
record.loggerName,
)
override fun flush() = flushHandler()
@@ -64,13 +76,16 @@ object Logger {
removeAllHandlers()
val publishHandler = handler@{ log: String, level: Level, loggerName: String? ->
if (loggerName?.startsWith("app.revanced") != true) return@handler
loggerName?.let { name ->
if (allowedLoggersRules.none { it(name) }) return@handler
}
log.toByteArray().let {
if (level.intValue() > Level.WARNING.intValue())
if (level.intValue() > Level.WARNING.intValue()) {
System.err.write(it)
else
} else {
System.out.write(it)
}
}
}
@@ -81,4 +96,4 @@ object Logger {
addHandler(publishHandler, flushHandler, flushHandler)
}
}
}

View File

@@ -0,0 +1,41 @@
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 kotlin.test.Test
class PatchOptionsTest {
private var patches = setOf(PatchOptionsTestPatch)
private val serializedJson =
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":null},{\"key\":\"key2\"," +
"\"value\":true}]}]"
private val changedJson =
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":\"test\"},{\"key\":\"key2" +
"\",\"value\":false}]}]"
@Test
fun `serializes and deserializes`() {
assert(serializedJson == Options.serialize(patches))
patches.setOptions(changedJson)
assert(PatchOptionsTestPatch.option1 == "test")
assert(PatchOptionsTestPatch.option2 == 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
}
}
}

View File

@@ -0,0 +1,199 @@
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 java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import kotlin.test.Test
import kotlin.test.assertEquals
internal class PatchUtilsTest {
private val patches =
arrayOf(
newPatch("some.package", setOf("a")) { stringPatchOption("string", "value") },
newPatch("some.package", setOf("a", "b"), use = false),
newPatch("some.package", setOf("a", "b", "c"), use = false),
newPatch("some.other.package", setOf("b"), use = false),
newPatch("some.other.package", setOf("b", "c")) { booleanPatchOption("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", setOf("a")),
newPatch("some.other.other.package", setOf("b")),
newPatch("some.other.other.other.package", use = false),
newPatch("some.other.other.other.package", use = false),
).toSet()
@Test
fun `empty because package is incompatible with any version`() {
assertEqualsVersions(
expected = emptyMap(),
patches = setOf(newPatch("some.package", emptySet(), use = true)),
compatiblePackageNames = setOf("some.package"),
)
}
@Test
fun `empty list of versions because package is unconstrained to any version`() {
assertEqualsVersions(
expected = mapOf("some.package" to linkedMapOf()),
patches = setOf(newPatch("some.package")),
compatiblePackageNames = setOf("some.package"),
countUnusedPatches = true,
)
}
@Test
fun `empty because no known package was supplied`() {
assertEqualsVersions(
expected = emptyMap(),
patches,
compatiblePackageNames = setOf("unknown.package"),
)
}
@Test
fun `common versions correctly ordered for each package`() {
fun assertEqualsExpected(compatiblePackageNames: Set<String>?) =
assertEqualsVersions(
expected =
mapOf(
"some.package" to linkedMapOf("a" to 3, "b" to 2, "c" to 1),
"some.other.package" to linkedMapOf("b" to 3, "c" to 2, "d" to 1),
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
"some.other.other.other.package" to linkedMapOf(),
),
patches,
compatiblePackageNames,
countUnusedPatches = true,
)
assertEqualsExpected(
compatiblePackageNames =
setOf(
"some.package",
"some.other.package",
"some.other.other.package",
"some.other.other.other.package",
),
)
assertEqualsExpected(
compatiblePackageNames = null,
)
}
@Test
fun `common versions correctly ordered for each package without counting unused patches`() {
assertEqualsVersions(
expected =
mapOf(
"some.package" to linkedMapOf("a" to 1),
"some.other.package" to linkedMapOf("b" to 2, "c" to 2, "d" to 1),
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
),
patches,
compatiblePackageNames =
setOf(
"some.package",
"some.other.package",
"some.other.other.package",
"some.other.other.other.package",
),
countUnusedPatches = false,
)
}
@Test
fun `return 'a' because it is the most common version`() {
val patches =
arrayOf("a", "a", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d")
.map { version -> newPatch("some.package", setOf(version)) }
.toSet()
assertEqualsVersion("a", patches, "some.package")
}
@Test
fun `return null because no patches were supplied`() {
assertEqualsVersion(null, emptySet<BytecodePatch>(), "some.package")
}
@Test
fun `return null because no patch is compatible with the supplied package name`() {
val patches = setOf(newPatch("some.package", setOf("a")))
assertEqualsVersion(null, patches, "other.package")
}
@Test
fun `return null because no compatible package is constrained to a version`() {
val patches =
setOf(
newPatch("other.package"),
newPatch("other.package"),
)
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,
compatiblePackageNames: Set<String>?,
countUnusedPatches: Boolean = false,
) = assertEquals(
expected,
PatchUtils.getMostCommonCompatibleVersions(patches, compatiblePackageNames, countUnusedPatches),
)
private fun assertEqualsVersion(
expected: String?,
patches: PatchSet,
compatiblePackageName: String,
) {
assertEquals(
expected,
PatchUtils.getMostCommonCompatibleVersions(patches, setOf(compatiblePackageName))
.entries.firstOrNull()?.value?.keys?.firstOrNull(),
)
}
private fun newPatch(
packageName: String,
versions: Set<String>? = null,
use: Boolean = true,
options: Patch<*>.() -> Unit = {},
) = object : BytecodePatch(
name = "test",
compatiblePackages = setOf(CompatiblePackage(packageName, versions?.toSet())),
use = use,
) {
init {
options()
}
override fun execute(context: BytecodeContext) {}
// Needed to make the patches unique.
override fun equals(other: Any?) = false
}
}

5379
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
{
"devDependencies": {
"@saithodev/semantic-release-backmerge": "^3.1.0",
"@semantic-release/changelog": "^6.0.2",
"@saithodev/semantic-release-backmerge": "^4.0.1",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.7.6",
"semantic-release": "^20.1.0"
"gradle-semantic-release-plugin": "^1.9.1",
"semantic-release": "^23.0.2"
}
}

View File

@@ -1,10 +1,15 @@
dependencyResolutionManagement {
buildCache {
local {
isEnabled = "CI" !in System.getenv()
}
}
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
mavenLocal()
maven { url = uri("https://jitpack.io") }
google()
}
}
rootProject.name = "revanced-library"
include(":library", ":library-networking")

View File

@@ -1,266 +0,0 @@
package app.revanced.library
import com.android.apksig.ApkSigner
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.math.BigInteger
import java.security.*
import java.security.cert.X509Certificate
import java.util.*
import java.util.logging.Logger
import kotlin.time.Duration.Companion.days
/**
* Utility class for writing or reading keystore files and entries as well as signing APK files.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object ApkSigner {
private val logger = Logger.getLogger(app.revanced.library.ApkSigner::class.java.name)
init {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null)
Security.addProvider(BouncyCastleProvider())
}
/**
* Create a new [PrivateKeyCertificatePair].
*
* @param commonName The common name of the certificate.
* @param validUntil The date until the certificate is valid.
* @return The created [PrivateKeyCertificatePair].
*/
fun newPrivateKeyCertificatePair(
commonName: String = "ReVanced",
validUntil: Date = Date(System.currentTimeMillis() + 356.days.inWholeMilliseconds * 24)
): PrivateKeyCertificatePair {
logger.fine("Creating certificate for $commonName")
// Generate a new key pair.
val keyPair = KeyPairGenerator.getInstance("RSA").apply {
initialize(4096)
}.generateKeyPair()
var serialNumber: BigInteger
do serialNumber = BigInteger.valueOf(SecureRandom().nextLong())
while (serialNumber < BigInteger.ZERO)
val name = X500Name("CN=$commonName")
// Create a new certificate.
val certificate = JcaX509CertificateConverter().getCertificate(
X509v3CertificateBuilder(
name,
serialNumber,
Date(System.currentTimeMillis()),
validUntil,
Locale.ENGLISH,
name,
SubjectPublicKeyInfo.getInstance(keyPair.public.encoded)
).build(JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private))
)
return PrivateKeyCertificatePair(keyPair.private, certificate)
}
/**
* Read a [PrivateKeyCertificatePair] from a keystore entry.
*
* @param keyStore The keystore to read the entry from.
* @param keyStoreEntryAlias The alias of the key store entry to read.
* @param keyStoreEntryPassword The password for recovering the signing key.
* @return The read [PrivateKeyCertificatePair].
* @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid.
*/
fun readKeyCertificatePair(
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
): PrivateKeyCertificatePair {
logger.fine("Reading key and certificate pair from keystore entry $keyStoreEntryAlias")
if (!keyStore.containsAlias(keyStoreEntryAlias))
throw IllegalArgumentException("Keystore does not contain alias $keyStoreEntryAlias")
// Read the private key and certificate from the keystore.
val privateKey = try {
keyStore.getKey(keyStoreEntryAlias, keyStoreEntryPassword.toCharArray()) as PrivateKey
} catch (exception: UnrecoverableKeyException) {
throw IllegalArgumentException("Invalid password for keystore entry $keyStoreEntryAlias")
}
val certificate = keyStore.getCertificate(keyStoreEntryAlias) as X509Certificate
return PrivateKeyCertificatePair(privateKey, certificate)
}
/**
* Create a new keystore with a new keypair.
*
* @param entries The entries to add to the keystore.
* @return The created keystore.
* @see KeyStoreEntry
*/
fun newKeyStore(
entries: List<KeyStoreEntry>
): KeyStore {
logger.fine("Creating keystore")
return KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME).apply {
load(null)
entries.forEach { entry ->
// Add all entries to the keystore.
setKeyEntry(
entry.alias,
entry.privateKeyCertificatePair.privateKey,
entry.password.toCharArray(),
arrayOf(entry.privateKeyCertificatePair.certificate)
)
}
}
}
/**
* Create a new keystore with a new keypair and saves it to the given [keyStoreOutputStream].
*
* @param keyStoreOutputStream The stream to write the keystore to.
* @param keyStorePassword The password for the keystore.
* @param entries The entries to add to the keystore.
*/
fun newKeyStore(
keyStoreOutputStream: OutputStream,
keyStorePassword: String,
entries: List<KeyStoreEntry>
) = newKeyStore(entries).store(
keyStoreOutputStream,
keyStorePassword.toCharArray()
) // Save the keystore.
/**
* Read a keystore from the given [keyStoreInputStream].
*
* @param keyStoreInputStream The stream to read the keystore from.
* @param keyStorePassword The password for the keystore.
* @return The keystore.
* @throws IllegalArgumentException If the keystore password is invalid.
*/
fun readKeyStore(
keyStoreInputStream: InputStream,
keyStorePassword: String?
): KeyStore {
logger.fine("Reading keystore")
return KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME).apply {
try {
load(keyStoreInputStream, keyStorePassword?.toCharArray())
} catch (exception: IOException) {
if (exception.cause is UnrecoverableKeyException)
throw IllegalArgumentException("Invalid keystore password")
else
throw exception
}
}
}
/**
* Create a new [ApkSigner.Builder].
*
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
* @param signer The name of the signer.
* @param createdBy The value for the `Created-By` attribute in the APK's manifest.
* @return The created [ApkSigner.Builder] instance.
*/
fun newApkSignerBuilder(
privateKeyCertificatePair: PrivateKeyCertificatePair,
signer: String,
createdBy: String
): ApkSigner.Builder {
logger.fine(
"Creating new ApkSigner " +
"with $signer as signer and " +
"$createdBy as Created-By attribute in the APK's manifest"
)
// Create the signer config.
val signerConfig = ApkSigner.SignerConfig.Builder(
signer,
privateKeyCertificatePair.privateKey,
listOf(privateKeyCertificatePair.certificate)
).build()
// Create the signer.
return ApkSigner.Builder(listOf(signerConfig)).apply {
setCreatedBy(createdBy)
}
}
/**
* Create a new [ApkSigner.Builder].
*
* @param keyStore The keystore to use for signing.
* @param keyStoreEntryAlias The alias of the key store entry to use for signing.
* @param keyStoreEntryPassword The password for recovering the signing key.
* @param signer The name of the signer.
* @param createdBy The value for the `Created-By` attribute in the APK's manifest.
* @return The created [ApkSigner.Builder] instance.
* @see KeyStoreEntry
* @see PrivateKeyCertificatePair
* @see ApkSigner.Builder.setCreatedBy
* @see ApkSigner.Builder.signApk
*/
fun newApkSignerBuilder(
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
signer: String,
createdBy: String,
) = newApkSignerBuilder(
readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword),
signer,
createdBy
)
fun ApkSigner.Builder.signApk(input: File, output: File) {
logger.info("Signing ${input.name}")
setInputApk(input)
setOutputApk(output)
build().sign()
}
/**
* An entry in a keystore.
*
* @param alias The alias of the entry.
* @param password The password for recovering the signing key.
* @param privateKeyCertificatePair The private key and certificate pair.
* @see PrivateKeyCertificatePair
*/
class KeyStoreEntry(
val alias: String,
val password: String,
val privateKeyCertificatePair: PrivateKeyCertificatePair = newPrivateKeyCertificatePair()
)
/**
* A private key and certificate pair.
*
* @param privateKey The private key.
* @param certificate The certificate.
*/
class PrivateKeyCertificatePair(
val privateKey: PrivateKey,
val certificate: X509Certificate,
)
}

View File

@@ -1,104 +0,0 @@
package app.revanced.library
import app.revanced.library.ApkSigner.signApk
import app.revanced.library.zip.ZipFile
import app.revanced.library.zip.structures.ZipEntry
import app.revanced.patcher.PatcherResult
import java.io.File
import java.util.logging.Logger
import kotlin.io.path.deleteIfExists
/**
* Utility functions for working with apks.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object ApkUtils {
private val logger = Logger.getLogger(ApkUtils::class.java.name)
/**
* Creates a new apk from [apkFile] and [patchedEntriesSource] and writes it to [outputFile].
*
* @param apkFile The apk to copy entries from.
* @param outputFile The apk to write the new entries to.
* @param patchedEntriesSource The result of the patcher to add the patched dex files and resources.
*/
fun copyAligned(apkFile: File, outputFile: File, patchedEntriesSource: PatcherResult) {
logger.info("Aligning ${apkFile.name}")
outputFile.toPath().deleteIfExists()
ZipFile(outputFile).use { file ->
patchedEntriesSource.dexFiles.forEach {
file.addEntryCompressData(
ZipEntry(it.name), it.stream.readBytes()
)
}
patchedEntriesSource.resourceFile?.let {
file.copyEntriesFromFileAligned(
ZipFile(it), ZipFile.apkZipEntryAlignment
)
}
// TODO: Do not compress result.doNotCompress
// TODO: Fix copying resources that are not needed anymore.
file.copyEntriesFromFileAligned(
ZipFile(apkFile), ZipFile.apkZipEntryAlignment
)
}
}
/**
* Signs the [apk] file and writes it to [output].
*
* @param apk The apk to sign.
* @param output The apk to write the signed apk to.
* @param signingOptions The options to use for signing.
*/
fun sign(
apk: File,
output: File,
signingOptions: SigningOptions,
) {
// Get the keystore from the file or create a new one.
val keyStore = if (signingOptions.keyStore.exists()) {
ApkSigner.readKeyStore(signingOptions.keyStore.inputStream(), signingOptions.keyStorePassword)
} else {
val entry = ApkSigner.KeyStoreEntry(signingOptions.alias, signingOptions.password)
// Create a new keystore with a new keypair and saves it.
ApkSigner.newKeyStore(listOf(entry)).also { keyStore ->
keyStore.store(
signingOptions.keyStore.outputStream(),
signingOptions.keyStorePassword?.toCharArray()
)
}
}
ApkSigner.newApkSignerBuilder(
keyStore,
signingOptions.alias,
signingOptions.password,
signingOptions.signer,
signingOptions.signer
).signApk(apk, output)
}
/**
* Options for signing an apk.
*
* @param keyStore The keystore to use for signing.
* @param keyStorePassword The password for the keystore.
* @param alias The alias of the key store entry to use for signing.
* @param password The password for recovering the signing key.
* @param signer The name of the signer.
*/
class SigningOptions(
val keyStore: File,
val keyStorePassword: String?,
val alias: String = "ReVanced Key",
val password: String = "",
val signer: String = "ReVanced",
)
}

View File

@@ -1,28 +0,0 @@
package app.revanced.library
import app.revanced.patcher.PatchSet
/**
* Utility functions for working with patches.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object PatchUtils {
/**
* Get the version that is most common for [packageName] in the supplied set of [patches].
*
* @param patches The set of patches to check.
* @param packageName The name of the compatible package.
* @return The most common version of.
*/
fun getMostCommonCompatibleVersion(patches: PatchSet, packageName: String) = patches
.mapNotNull {
// Map all patches to their compatible packages with version constraints.
it.compatiblePackages?.firstOrNull { compatiblePackage ->
compatiblePackage.name == packageName && compatiblePackage.versions?.isNotEmpty() == true
}
}
.flatMap { it.versions!! }
.groupingBy { it }
.eachCount()
.maxByOrNull { it.value }?.key
}

View File

@@ -1,154 +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.INSTALLATION_PATH
import app.revanced.library.adb.Constants.INSTALL_MOUNT
import app.revanced.library.adb.Constants.INSTALL_PATCHED_APK
import app.revanced.library.adb.Constants.MOUNT_PATH
import app.revanced.library.adb.Constants.MOUNT_SCRIPT
import app.revanced.library.adb.Constants.PATCHED_APK_PATH
import app.revanced.library.adb.Constants.PLACEHOLDER
import app.revanced.library.adb.Constants.RESOLVE_ACTIVITY
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.
*/
sealed class AdbManager private constructor(deviceSerial: String? = null) {
protected val logger: Logger = Logger.getLogger(AdbManager::class.java.name)
protected val device = JadbConnection().devices.find { device -> device.serial == deviceSerial }
?: throw DeviceNotFoundException(deviceSerial)
init {
logger.fine("Established connection to $deviceSerial")
}
/**
* Installs the [Apk] file.
*
* @param apk The [Apk] file.
*/
open fun install(apk: Apk) {
logger.info("Finished installing ${apk.file.name}")
}
/**
* Uninstalls the package.
*
* @param packageName The package name.
*/
open fun uninstall(packageName: String) {
logger.info("Finished uninstalling $packageName")
}
companion object {
/**
* Gets an [AdbManager] for the supplied device serial.
*
* @param deviceSerial The device serial.
* @param root Whether to use root or not.
* @return The [AdbManager].
* @throws DeviceNotFoundException If the device can not be found.
*/
fun getAdbManager(deviceSerial: String, root: Boolean = false): AdbManager =
if (root) RootAdbManager(deviceSerial) else UserAdbManager(deviceSerial)
}
class RootAdbManager internal constructor(deviceSerial: String) : AdbManager(deviceSerial) {
init {
if (!device.hasSu()) throw IllegalArgumentException("Root required on $deviceSerial. Task failed")
}
override fun install(apk: Apk) {
logger.info("Installing by mounting")
val packageName = apk.packageName ?: throw PackageNameRequiredException()
device.run(RESOLVE_ACTIVITY, packageName).inputStream.bufferedReader().readLine().let { line ->
if (line != "No activity found") return@let
throw throw FailedToFindInstalledPackageException(packageName)
}
device.push(apk.file, TMP_PATH)
device.run("$CREATE_DIR $INSTALLATION_PATH")
device.run(INSTALL_PATCHED_APK, packageName)
device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement(packageName))
device.run(INSTALL_MOUNT, packageName)
device.run(UMOUNT, packageName) // Sanity check.
device.run(MOUNT_PATH, packageName)
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_PATH)
device.run(DELETE, TMP_PATH)
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)
}
}
class UserAdbManager internal constructor(deviceSerial: String) : AdbManager(deviceSerial) {
private val packageManager = PackageManager(device)
override fun install(apk: Apk) {
PackageManager(device).install(apk.file)
super.install(apk)
}
override fun uninstall(packageName: String) {
logger.info("Uninstalling $packageName")
packageManager.uninstall(Package(packageName))
super.uninstall(packageName)
}
}
/**
* Apk file for [AdbManager].
*
* @param file The [Apk] file.
* @param packageName The package name of the [Apk] file.
*/
class Apk(val file: File, val packageName: String? = null)
class DeviceNotFoundException internal constructor(deviceSerial: String?) :
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,34 +0,0 @@
package app.revanced.library.adb
import se.vidstige.jadb.JadbDevice
import se.vidstige.jadb.RemoteFile
import se.vidstige.jadb.ShellProcess
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): ShellProcess {
return this.buildCommand(command, su).start()!!
}
internal fun JadbDevice.hasSu() =
this.startCommand("su -h", false).waitFor() == 0
internal fun JadbDevice.push(file: File, targetFilePath: String) =
push(file, RemoteFile(targetFilePath))
internal fun JadbDevice.createFile(targetFile: String, content: String) =
push(content.byteInputStream(), System.currentTimeMillis(), 644, RemoteFile(targetFile))
private fun JadbDevice.startCommand(command: String, su: Boolean) =
shellProcessBuilder(if (su) "su -c '$command'" else command).start()

View File

@@ -1,41 +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_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 RESOLVE_ACTIVITY = "pm resolve-activity --brief $PLACEHOLDER"
internal const val RESTART = "$RESOLVE_ACTIVITY | tail -n 1 | " +
"xargs am start -n && kill ${'$'}(pidof -s $PLACEHOLDER)"
internal const val INSTALL_PATCHED_APK = "base_path=\"$PATCHED_APK_PATH\" && " +
"mv $TMP_PATH ${'$'}base_path && " +
"chmod 644 ${'$'}base_path && " +
"chown system:system ${'$'}base_path && " +
"chcon u:object_r:apk_data_file:s0 ${'$'}base_path"
internal const val UMOUNT =
"grep $PLACEHOLDER /proc/mounts | while read -r line; do echo ${'$'}line | cut -d \" \" -f 2 | sed 's/apk.*/apk/' | xargs -r umount -l; done"
internal const val INSTALL_MOUNT = "mv $TMP_PATH $MOUNT_PATH && chmod +x $MOUNT_PATH"
internal val MOUNT_SCRIPT =
"""
#!/system/bin/sh
MAGISKTMP="${'$'}(magisk --path)" || MAGISKTMP=/sbin
MIRROR="${'$'}MAGISKTMP/.magisk/mirror"
while [ "${'$'}(getprop sys.boot_completed | tr -d '\r')" != "1" ]; do sleep 1; done
base_path="$PATCHED_APK_PATH"
stock_path=${'$'}( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
chcon u:object_r:apk_data_file:s0 ${'$'}base_path
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
""".trimIndent()
}

View File

@@ -1,33 +0,0 @@
package app.revanced.library.zip
import java.io.DataInput
import java.io.DataOutput
import java.nio.ByteBuffer
internal fun UInt.toLittleEndian() =
(((this.toInt() and 0xff000000.toInt()) shr 24) or ((this.toInt() and 0x00ff0000) shr 8) or ((this.toInt() and 0x0000ff00) shl 8) or (this.toInt() shl 24)).toUInt()
internal fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort()
internal fun UInt.toBigEndian() = (((this.toInt() and 0xff) shl 24) or ((this.toInt() and 0xff00) shl 8)
or ((this.toInt() and 0x00ff0000) ushr 8) or (this.toInt() ushr 24)).toUInt()
internal fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort()
internal fun ByteBuffer.getUShort() = this.getShort().toUShort()
internal fun ByteBuffer.getUInt() = this.getInt().toUInt()
internal fun ByteBuffer.putUShort(ushort: UShort) = this.putShort(ushort.toShort())
internal fun ByteBuffer.putUInt(uint: UInt) = this.putInt(uint.toInt())
internal fun DataInput.readUShort() = this.readShort().toUShort()
internal fun DataInput.readUInt() = this.readInt().toUInt()
internal fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt())
internal fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt())
internal fun DataInput.readUShortLE() = this.readUShort().toBigEndian()
internal fun DataInput.readUIntLE() = this.readUInt().toBigEndian()
internal fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian())
internal fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian())

View File

@@ -1,197 +0,0 @@
package app.revanced.library.zip
import app.revanced.library.zip.structures.ZipEndRecord
import app.revanced.library.zip.structures.ZipEntry
import java.io.Closeable
import java.io.File
import java.io.RandomAccessFile
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import java.util.zip.CRC32
import java.util.zip.Deflater
class ZipFile(file: File) : Closeable {
private var entries: MutableList<ZipEntry> = mutableListOf()
// Open file for writing if it doesn't exist (because the intention is to write) or is writable.
private val filePointer: RandomAccessFile = RandomAccessFile(
file,
if (!file.exists() || file.canWrite()) "rw" else "r"
)
private var centralDirectoryNeedsRewrite = false
private val compressionLevel = 5
init {
// If file isn't empty try to load entries.
if (file.length() > 0) {
val endRecord = findEndRecord()
if (endRecord.diskNumber > 0u || endRecord.totalEntries != endRecord.diskEntries)
throw IllegalArgumentException("Multi-file archives are not supported")
entries = readEntries(endRecord).toMutableList()
}
// Seek back to start for writing.
filePointer.seek(0)
}
private fun findEndRecord(): ZipEndRecord {
// Look from end to start since end record is at the end.
for (i in filePointer.length() - 1 downTo 0) {
filePointer.seek(i)
// Possible beginning of signature.
if (filePointer.readByte() == 0x50.toByte()) {
// Seek back to get the full int.
filePointer.seek(i)
val possibleSignature = filePointer.readUIntLE()
if (possibleSignature == ZipEndRecord.ECD_SIGNATURE) {
filePointer.seek(i)
return ZipEndRecord.fromECD(filePointer)
}
}
}
throw Exception("Couldn't find end record")
}
private fun readEntries(endRecord: ZipEndRecord): List<ZipEntry> {
filePointer.seek(endRecord.centralDirectoryStartOffset.toLong())
val numberOfEntries = endRecord.diskEntries.toInt()
return buildList(numberOfEntries) {
for (i in 1..numberOfEntries) {
add(
ZipEntry.fromCDE(filePointer).also
{
//for some reason the local extra field can be different from the central one
it.readLocalExtra(
filePointer.channel.map(
FileChannel.MapMode.READ_ONLY,
it.localHeaderOffset.toLong() + 28,
2
)
)
})
}
}
}
private fun writeCD() {
val centralDirectoryStartOffset = filePointer.channel.position().toUInt()
entries.forEach {
filePointer.channel.write(it.toCDE())
}
val entriesCount = entries.size.toUShort()
val endRecord = ZipEndRecord(
0u,
0u,
entriesCount,
entriesCount,
filePointer.channel.position().toUInt() - centralDirectoryStartOffset,
centralDirectoryStartOffset,
""
)
filePointer.channel.write(endRecord.toECD())
}
private fun addEntry(entry: ZipEntry, data: ByteBuffer) {
centralDirectoryNeedsRewrite = true
entry.localHeaderOffset = filePointer.channel.position().toUInt()
filePointer.channel.write(entry.toLFH())
filePointer.channel.write(data)
entries.add(entry)
}
fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
val compressor = Deflater(compressionLevel, true)
compressor.setInput(data)
compressor.finish()
val uncompressedSize = data.size
val compressedData = ByteArray(uncompressedSize) // I'm guessing compression won't make the data bigger.
val compressedDataLength = compressor.deflate(compressedData)
val compressedBuffer =
ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray())
compressor.end()
val crc = CRC32()
crc.update(data)
entry.compression = 8u // Deflate compression.
entry.uncompressedSize = uncompressedSize.toUInt()
entry.compressedSize = compressedDataLength.toUInt()
entry.crc32 = crc.value.toUInt()
addEntry(entry, compressedBuffer)
}
private fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) {
alignment?.let {
// Calculate where data would end up.
val dataOffset = filePointer.filePointer + entry.LFHSize
val mod = dataOffset % alignment
// Wrong alignment.
if (mod != 0L) {
// Add padding at end of extra field.
entry.localExtraField =
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt())
}
}
addEntry(entry, data)
}
private fun getDataForEntry(entry: ZipEntry): ByteBuffer {
return filePointer.channel.map(
FileChannel.MapMode.READ_ONLY,
entry.dataOffset.toLong(),
entry.compressedSize.toLong()
)
}
/**
* Copies all entries from [file] to this file but skip already existing entries.
*
* @param file The file to copy entries from.
* @param entryAlignment A function that returns the alignment for a given entry.
*/
fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) {
for (entry in file.entries) {
if (entries.any { it.fileName == entry.fileName }) continue // Skip duplicates
val data = file.getDataForEntry(entry)
addEntryCopyData(entry, data, entryAlignment(entry))
}
}
override fun close() {
if (centralDirectoryNeedsRewrite) writeCD()
filePointer.close()
}
companion object ApkZipFile {
private const val DEFAULT_ALIGNMENT = 4
private const val LIBRARY_ALIGNMENT = 4096
val apkZipEntryAlignment = { entry: ZipEntry ->
if (entry.compression.toUInt() != 0u) null
else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT
else DEFAULT_ALIGNMENT
}
}
}

View File

@@ -1,77 +0,0 @@
package app.revanced.library.zip.structures
import app.revanced.library.zip.putUInt
import app.revanced.library.zip.putUShort
import app.revanced.library.zip.readUIntLE
import app.revanced.library.zip.readUShortLE
import java.io.DataInput
import java.nio.ByteBuffer
import java.nio.ByteOrder
internal class ZipEndRecord(
val diskNumber: UShort,
val startingDiskNumber: UShort,
val diskEntries: UShort,
val totalEntries: UShort,
val centralDirectorySize: UInt,
val centralDirectoryStartOffset: UInt,
val fileComment: String,
) {
companion object {
const val ECD_HEADER_SIZE = 22
const val ECD_SIGNATURE = 0x06054b50u
fun fromECD(input: DataInput): ZipEndRecord {
val signature = input.readUIntLE()
if (signature != ECD_SIGNATURE)
throw IllegalArgumentException("Input doesn't start with end record signature")
val diskNumber = input.readUShortLE()
val startingDiskNumber = input.readUShortLE()
val diskEntries = input.readUShortLE()
val totalEntries = input.readUShortLE()
val centralDirectorySize = input.readUIntLE()
val centralDirectoryStartOffset = input.readUIntLE()
val fileCommentLength = input.readUShortLE()
var fileComment = ""
if (fileCommentLength > 0u) {
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
input.readFully(fileCommentBytes)
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
}
return ZipEndRecord(
diskNumber,
startingDiskNumber,
diskEntries,
totalEntries,
centralDirectorySize,
centralDirectoryStartOffset,
fileComment
)
}
}
fun toECD(): ByteBuffer {
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size).also { it.order(ByteOrder.LITTLE_ENDIAN) }
buffer.putUInt(ECD_SIGNATURE)
buffer.putUShort(diskNumber)
buffer.putUShort(startingDiskNumber)
buffer.putUShort(diskEntries)
buffer.putUShort(totalEntries)
buffer.putUInt(centralDirectorySize)
buffer.putUInt(centralDirectoryStartOffset)
buffer.putUShort(commentBytes.size.toUShort())
buffer.put(commentBytes)
buffer.flip()
return buffer
}
}

View File

@@ -1,187 +0,0 @@
package app.revanced.library.zip.structures
import app.revanced.library.zip.*
import java.io.DataInput
import java.nio.ByteBuffer
import java.nio.ByteOrder
class ZipEntry private constructor(
internal val version: UShort,
internal val versionNeeded: UShort,
internal val flags: UShort,
internal var compression: UShort,
internal val modificationTime: UShort,
internal val modificationDate: UShort,
internal var crc32: UInt,
internal var compressedSize: UInt,
internal var uncompressedSize: UInt,
internal val diskNumber: UShort,
internal val internalAttributes: UShort,
internal val externalAttributes: UInt,
internal var localHeaderOffset: UInt,
internal val fileName: String,
internal val extraField: ByteArray,
internal val fileComment: String,
internal var localExtraField: ByteArray = ByteArray(0), //separate for alignment
) {
internal val LFHSize: Int
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
internal val dataOffset: UInt
get() = localHeaderOffset + LFHSize.toUInt()
constructor(fileName: String) : this(
0x1403u, //made by unix, version 20
0u,
0u,
0u,
0x0821u, //seems to be static time google uses, no idea
0x0221u, //same as above
0u,
0u,
0u,
0u,
0u,
0u,
0u,
fileName,
ByteArray(0),
""
)
companion object {
internal const val CDE_HEADER_SIZE = 46
internal const val CDE_SIGNATURE = 0x02014b50u
internal const val LFH_HEADER_SIZE = 30
internal const val LFH_SIGNATURE = 0x04034b50u
internal fun fromCDE(input: DataInput): ZipEntry {
val signature = input.readUIntLE()
if (signature != CDE_SIGNATURE)
throw IllegalArgumentException("Input doesn't start with central directory entry signature")
val version = input.readUShortLE()
val versionNeeded = input.readUShortLE()
var flags = input.readUShortLE()
val compression = input.readUShortLE()
val modificationTime = input.readUShortLE()
val modificationDate = input.readUShortLE()
val crc32 = input.readUIntLE()
val compressedSize = input.readUIntLE()
val uncompressedSize = input.readUIntLE()
val fileNameLength = input.readUShortLE()
var fileName = ""
val extraFieldLength = input.readUShortLE()
val extraField = ByteArray(extraFieldLength.toInt())
val fileCommentLength = input.readUShortLE()
var fileComment = ""
val diskNumber = input.readUShortLE()
val internalAttributes = input.readUShortLE()
val externalAttributes = input.readUIntLE()
val localHeaderOffset = input.readUIntLE()
val variableFieldsLength =
fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
if (variableFieldsLength > 0) {
val fileNameBytes = ByteArray(fileNameLength.toInt())
input.readFully(fileNameBytes)
fileName = fileNameBytes.toString(Charsets.UTF_8)
input.readFully(extraField)
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
input.readFully(fileCommentBytes)
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
}
flags = (flags and 0b1000u.inv()
.toUShort()) //disable data descriptor flag as they are not used
return ZipEntry(
version,
versionNeeded,
flags,
compression,
modificationTime,
modificationDate,
crc32,
compressedSize,
uncompressedSize,
diskNumber,
internalAttributes,
externalAttributes,
localHeaderOffset,
fileName,
extraField,
fileComment,
)
}
}
internal fun readLocalExtra(buffer: ByteBuffer) {
buffer.order(ByteOrder.LITTLE_ENDIAN)
localExtraField = ByteArray(buffer.getUShort().toInt())
}
internal fun toLFH(): ByteBuffer {
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
val buffer = ByteBuffer.allocate(LFH_HEADER_SIZE + nameBytes.size + localExtraField.size)
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
buffer.putUInt(LFH_SIGNATURE)
buffer.putUShort(versionNeeded)
buffer.putUShort(flags)
buffer.putUShort(compression)
buffer.putUShort(modificationTime)
buffer.putUShort(modificationDate)
buffer.putUInt(crc32)
buffer.putUInt(compressedSize)
buffer.putUInt(uncompressedSize)
buffer.putUShort(nameBytes.size.toUShort())
buffer.putUShort(localExtraField.size.toUShort())
buffer.put(nameBytes)
buffer.put(localExtraField)
buffer.flip()
return buffer
}
internal fun toCDE(): ByteBuffer {
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
val buffer =
ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size)
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
buffer.putUInt(CDE_SIGNATURE)
buffer.putUShort(version)
buffer.putUShort(versionNeeded)
buffer.putUShort(flags)
buffer.putUShort(compression)
buffer.putUShort(modificationTime)
buffer.putUShort(modificationDate)
buffer.putUInt(crc32)
buffer.putUInt(compressedSize)
buffer.putUInt(uncompressedSize)
buffer.putUShort(nameBytes.size.toUShort())
buffer.putUShort(extraField.size.toUShort())
buffer.putUShort(commentBytes.size.toUShort())
buffer.putUShort(diskNumber)
buffer.putUShort(internalAttributes)
buffer.putUInt(externalAttributes)
buffer.putUInt(localHeaderOffset)
buffer.put(nameBytes)
buffer.put(extraField)
buffer.put(commentBytes)
buffer.flip()
return buffer
}
}

View File

@@ -1,46 +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.options.types.BooleanPatchOption.Companion.booleanPatchOption
import app.revanced.patcher.patch.options.types.StringPatchOption.Companion.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}]}]"
object PatchOptionsTestPatch : BytecodePatch(name = "PatchOptionsTestPatch") {
var option1 by stringPatchOption("key1", null, "title1", "description1")
var option2 by booleanPatchOption("key2", true, "title2", "description2")
override fun execute(context: BytecodeContext) {
// Do nothing
}
}
}

View File

@@ -1,50 +0,0 @@
package app.revanced.library
import app.revanced.patcher.PatchSet
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
internal object PatchUtilsTest {
@Test
fun `return 'a' because it is the most common version`() {
val patches = arrayOf("a", "a", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d")
.map { version -> newPatch("some.package", version) }
.toSet()
assertEqualsVersion("a", patches, "some.package")
}
@Test
fun `return null because no patches were supplied`() {
assertEqualsVersion(null, emptySet<BytecodePatch>(), "some.package")
}
@Test
fun `return null because no patch is compatible with the supplied package name`() {
val patches = setOf(newPatch("some.package", "a"))
assertEqualsVersion(null, patches, "other.package")
}
@Test
fun `return null because no patch compatible package is constrained to a version`() {
val patches = setOf(
newPatch("other.package"),
newPatch("other.package"),
)
assertEqualsVersion(null, patches, "other.package")
}
private fun assertEqualsVersion(
expected: String?, patches: PatchSet, compatiblePackageName: String
) = assertEquals(expected, PatchUtils.getMostCommonCompatibleVersion(patches, compatiblePackageName))
private fun newPatch(packageName: String, vararg versions: String) = object : BytecodePatch(
compatiblePackages = setOf(CompatiblePackage(packageName, versions.toSet()))
) {
override fun execute(context: BytecodeContext) {}
}
}