mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-12 06:16:18 +00:00
Compare commits
26 Commits
v2.2.0-dev
...
v1.0.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51fb59a43c | ||
|
|
a78715133c | ||
|
|
e8f6973938 | ||
|
|
3cb1e01587 | ||
|
|
cb4ee207e1 | ||
|
|
ca6b94d943 | ||
|
|
6cdb6887d4 | ||
|
|
ab6453ca8a | ||
|
|
e8182c17ad | ||
|
|
49beec9fc6 | ||
|
|
3ab42a932c | ||
|
|
4d98cbc9e8 | ||
|
|
87bbde5e06 | ||
|
|
8db8893ab1 | ||
|
|
00c6ab7faf | ||
|
|
460d62a24c | ||
|
|
89e4b9f762 | ||
|
|
a8fd7c00c3 | ||
|
|
1769132a9e | ||
|
|
6c0f0823c9 | ||
|
|
23e897a7a9 | ||
|
|
7e67daf878 | ||
|
|
593c83f29f | ||
|
|
72e123dd01 | ||
|
|
599a401ed9 | ||
|
|
3f8500b059 |
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -21,16 +20,16 @@ jobs:
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
java-version: '8'
|
||||
distribution: 'adopt'
|
||||
cache: gradle
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
- name: Make gradlew executable
|
||||
run: chmod +x gradlew
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew build
|
||||
- name: Setup semantic-release
|
||||
run: npm install -g semantic-release @semantic-release/git @semantic-release/changelog gradle-semantic-release-plugin -D
|
||||
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
6
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="UnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -4,7 +4,7 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="azul-17" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -7,11 +7,7 @@
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
["@semantic-release/commit-analyzer", {
|
||||
"releaseRules": [
|
||||
{"type": "build", "release": "patch"}
|
||||
]
|
||||
}],
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"gradle-semantic-release-plugin",
|
||||
|
||||
583
CHANGELOG.md
583
CHANGELOG.md
@@ -1,586 +1,3 @@
|
||||
# [2.2.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v2.1.2...v2.2.0-dev.1) (2022-07-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* remove deprecated functions ([ada5a03](https://github.com/revanced/revanced-patcher/commit/ada5a033de3cf94e7255ec2d522520f86431f001))
|
||||
|
||||
## [2.1.2](https://github.com/revanced/revanced-patcher/compare/v2.1.1...v2.1.2) (2022-06-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* invert fingerprint resolution condition of `customFingerprint` ([e2faf4c](https://github.com/revanced/revanced-patcher/commit/e2faf4ca9b6de23300b20ab471ee9dc365b04339))
|
||||
|
||||
## [2.1.1](https://github.com/revanced/revanced-patcher/compare/v2.1.0...v2.1.1) (2022-06-28)
|
||||
|
||||
# [2.1.0](https://github.com/revanced/revanced-patcher/compare/v2.0.4...v2.1.0) (2022-06-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* log failed patches due to failed dependencies ([a467fbb](https://github.com/revanced/revanced-patcher/commit/a467fbb704eebe812cdec14025398dab2af43959))
|
||||
|
||||
## [2.0.4](https://github.com/revanced/revanced-patcher/compare/v2.0.3...v2.0.4) (2022-06-27)
|
||||
|
||||
## [2.0.3](https://github.com/revanced/revanced-patcher/compare/v2.0.2...v2.0.3) (2022-06-27)
|
||||
|
||||
## [2.0.2](https://github.com/revanced/revanced-patcher/compare/v2.0.1...v2.0.2) (2022-06-27)
|
||||
|
||||
## [2.0.1](https://github.com/revanced/revanced-patcher/compare/v2.0.0...v2.0.1) (2022-06-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use `Exception` instead of `MethodNotFoundException` ([2fc4ec4](https://github.com/revanced/revanced-patcher/commit/2fc4ec40217a917ea6106ddc87be332f725aa13c))
|
||||
|
||||
# [2.0.0](https://github.com/revanced/revanced-patcher/compare/v1.11.0...v2.0.0) (2022-06-26)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* migrate from `Signature` to `Fingerprint` ([efa8ea1](https://github.com/revanced/revanced-patcher/commit/efa8ea144528fcff588e782468845c315a7d6abd))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Not backwards compatible, since a lot of classes where renamed.
|
||||
|
||||
# [1.11.0](https://github.com/revanced/revanced-patcher/compare/v1.10.2...v1.11.0) (2022-06-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add replace and remove extensions ([#50](https://github.com/revanced/revanced-patcher/issues/50)) ([92ac5e4](https://github.com/revanced/revanced-patcher/commit/92ac5e4dc25f612856e2b5e528cf5fd48a5f20af))
|
||||
|
||||
## [1.10.2](https://github.com/revanced/revanced-patcher/compare/v1.10.1...v1.10.2) (2022-06-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dexlib must be propagated ([b738dcd](https://github.com/revanced/revanced-patcher/commit/b738dcd7ea04f5fe56e66af46fb11541fe54f6af))
|
||||
|
||||
## [1.10.1](https://github.com/revanced/revanced-patcher/compare/v1.10.0...v1.10.1) (2022-06-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* callback only when inteded ([e3bf367](https://github.com/revanced/revanced-patcher/commit/e3bf367ad6615b30b06027d65f906b2588567a7f))
|
||||
* mutability of local variable `modified` ([0e87ef5](https://github.com/revanced/revanced-patcher/commit/0e87ef56c418d5c37d58abb9b27f85e25fd44f81))
|
||||
|
||||
# [1.10.0](https://github.com/revanced/revanced-patcher/compare/v1.9.0...v1.10.0) (2022-06-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve logging ([c20dfe1](https://github.com/revanced/revanced-patcher/commit/c20dfe12d5c737264b844e6634de11bf1e1629f0))
|
||||
|
||||
# [1.9.0](https://github.com/revanced/revanced-patcher/compare/v1.8.0...v1.9.0) (2022-06-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* callback for each file instead of class ([930768d](https://github.com/revanced/revanced-patcher/commit/930768dfb31dc5fa6c248050b08ac117c40ee0a3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* yield the patch result ([dde5385](https://github.com/revanced/revanced-patcher/commit/dde5385232abddc8a85d6e9a939549b71dd9130e))
|
||||
|
||||
# [1.8.0](https://github.com/revanced/revanced-patcher/compare/v1.7.2...v1.8.0) (2022-06-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* logging class ([caf2745](https://github.com/revanced/revanced-patcher/commit/caf2745805ffd4b59fa81e79cc489b1a1a5c5d89))
|
||||
|
||||
## [1.7.2](https://github.com/revanced/revanced-patcher/compare/v1.7.1...v1.7.2) (2022-06-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add execute permission to `./gradlew` file ([#46](https://github.com/revanced/revanced-patcher/issues/46)) ([34f607a](https://github.com/revanced/revanced-patcher/commit/34f607aa24d89a777d906cc887203f343ce3fd07))
|
||||
|
||||
## [1.7.1](https://github.com/revanced/revanced-patcher/compare/v1.7.0...v1.7.1) (2022-06-22)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* revert "feat: use of `java.util.logging.Logger`" ([e8488b3](https://github.com/revanced/revanced-patcher/commit/e8488b3e86e0132011824f8ecba29e64f8db0573))
|
||||
|
||||
# [1.7.0](https://github.com/revanced/revanced-patcher/compare/v1.6.0...v1.7.0) (2022-06-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* migrate logger to `slf4j` ([8f66f9f](https://github.com/revanced/revanced-patcher/commit/8f66f9f606a785ac947b0e553822877f211d82df))
|
||||
|
||||
# [1.6.0](https://github.com/revanced/revanced-patcher/compare/v1.5.0...v1.6.0) (2022-06-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use of `java.util.logging.Logger` ([9c39c9e](https://github.com/revanced/revanced-patcher/commit/9c39c9efdb5d48ddaffce7f711c275e732b0b2d9))
|
||||
|
||||
# [1.5.0](https://github.com/revanced/revanced-patcher/compare/v1.4.0...v1.5.0) (2022-06-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use streams to write the dex files ([64bae88](https://github.com/revanced/revanced-patcher/commit/64bae884dcb72550a3218e149f3ca0fd0ca03aaf))
|
||||
|
||||
# [1.4.0](https://github.com/revanced/revanced-patcher/compare/v1.3.4...v1.4.0) (2022-06-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* return a `File` instance instead of `ExtFile` ([68174bb](https://github.com/revanced/revanced-patcher/commit/68174bbd6b4df47a91b610c2b97dbae55b594163))
|
||||
|
||||
## [1.3.4](https://github.com/revanced/revanced-patcher/compare/v1.3.3...v1.3.4) (2022-06-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `String.toInstructions` defaulting `forStaticMethod` to `false` ([5a2f02b](https://github.com/revanced/revanced-patcher/commit/5a2f02b97dcde95dbe901fa68cca6c6c0219cb82)), closes [revanced/revanced-patches#46](https://github.com/revanced/revanced-patches/issues/46)
|
||||
|
||||
## [1.3.3](https://github.com/revanced/revanced-patcher/compare/v1.3.2...v1.3.3) (2022-06-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add docs (trigger release) ([6628b78](https://github.com/revanced/revanced-patcher/commit/6628b7870fc052da40be0d50a7e2b0b6c57743cc))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* propagate dependencies ([365e1d7](https://github.com/revanced/revanced-patcher/commit/365e1d7a4507b918a4c8170ce2c88f6c8ff1d474))
|
||||
|
||||
## [1.3.2](https://github.com/revanced/revanced-patcher/compare/v1.3.1...v1.3.2) (2022-06-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* return resourceFile to caller ([1f75777](https://github.com/revanced/revanced-patcher/commit/1f75777cf985bf08483033ec541937d3e733347b))
|
||||
|
||||
## [1.3.1](https://github.com/revanced/revanced-patcher/compare/v1.3.0...v1.3.1) (2022-06-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `InlineSmaliCompiler.compile` using 0 registers instead of 1 by default ([835a421](https://github.com/revanced/revanced-patcher/commit/835a421cc0588b92c2995e9d74727069d14b1750))
|
||||
|
||||
# [1.3.0](https://github.com/revanced/revanced-patcher/compare/v1.2.9...v1.3.0) (2022-06-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* `parametersCount` for `InlineSmaliCompiler` instead of `parameters` ([ad6c5c8](https://github.com/revanced/revanced-patcher/commit/ad6c5c827389d10eae473dc66557a699df8c3280))
|
||||
* simplify adding instructions ([e47b67d](https://github.com/revanced/revanced-patcher/commit/e47b67d7ec521f288644afb89baf4146dc9bc87d))
|
||||
|
||||
## [1.2.9](https://github.com/revanced/revanced-patcher/compare/v1.2.8...v1.2.9) (2022-06-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update apktool ([ab866bb](https://github.com/revanced/revanced-patcher/commit/ab866bb8ef4792d8f2a51edc79e687b5b636c621))
|
||||
|
||||
## [1.2.8](https://github.com/revanced/revanced-patcher/compare/v1.2.7...v1.2.8) (2022-06-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update apktool ([051afd9](https://github.com/revanced/revanced-patcher/commit/051afd98d065f71556392139d77c20b4c2dc7dd1))
|
||||
|
||||
## [1.2.7](https://github.com/revanced/revanced-patcher/compare/v1.2.6...v1.2.7) (2022-06-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* version not working with apktool due to cache ([03f5ee0](https://github.com/revanced/revanced-patcher/commit/03f5ee088b1b96b88cb7aeb323443b6209a13950))
|
||||
|
||||
## [1.2.6](https://github.com/revanced/revanced-patcher/compare/v1.2.5...v1.2.6) (2022-06-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove javadoc jar (also trigger release) ([56f6ca3](https://github.com/revanced/revanced-patcher/commit/56f6ca38919b522c0d5558eabffa4aee41cc0b0b))
|
||||
|
||||
## [1.2.5](https://github.com/revanced/revanced-patcher/compare/v1.2.4...v1.2.5) (2022-06-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* goodbye security ([8f3ac77](https://github.com/revanced/revanced-patcher/commit/8f3ac7702a2b3ee98c55aeac6a1b9972f99664cc))
|
||||
|
||||
## [1.2.4](https://github.com/revanced/revanced-patcher/compare/v1.2.3...v1.2.4) (2022-06-15)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* "fix: enforce aapt v1" ([dfd8a24](https://github.com/revanced/revanced-patcher/commit/dfd8a245124f85b1b028bbba197c70c8dca689b6))
|
||||
|
||||
## [1.2.3](https://github.com/revanced/revanced-patcher/compare/v1.2.2...v1.2.3) (2022-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* enforce aapt v1 ([cff87ff](https://github.com/revanced/revanced-patcher/commit/cff87ff0770d774d7ef79eec5a22462eadbcb9c5))
|
||||
|
||||
## [1.2.2](https://github.com/revanced/revanced-patcher/compare/v1.2.1...v1.2.2) (2022-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* enforce aapt v2 ([b68b0bf](https://github.com/revanced/revanced-patcher/commit/b68b0bf3d735f54b92ad7dad8132f77e9007063f))
|
||||
|
||||
## [1.2.1](https://github.com/revanced/revanced-patcher/compare/v1.2.0...v1.2.1) (2022-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Patcher setting BuildOptions too late ([6a5c873](https://github.com/revanced/revanced-patcher/commit/6a5c8735fb8a5d6f7e9c606734b6684c7fa99e7f))
|
||||
|
||||
# [1.2.0](https://github.com/revanced/revanced-patcher/compare/v1.1.0...v1.2.0) (2022-06-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow custom framework path to be specified ([d3a580e](https://github.com/revanced/revanced-patcher/commit/d3a580ea19d7c2d5d8c97650b1e6396ea0a7fc25))
|
||||
|
||||
# [1.1.0](https://github.com/revanced/revanced-patcher/compare/v1.0.0...v1.1.0) (2022-06-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* resource patcher ([31815ca](https://github.com/revanced/revanced-patcher/commit/31815ca9ea990f16b3600d61fd570c1805be1c82))
|
||||
* update apktool to fork ([566ecef](https://github.com/revanced/revanced-patcher/commit/566ecefa2bd4cde5ebfb2b22dc56cd8bf9f396bd))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow custom aapt path to be specified ([8eb4a8f](https://github.com/revanced/revanced-patcher/commit/8eb4a8f87ae7679a272f3224273a37a31d4bb121))
|
||||
|
||||
# 1.0.0 (2022-06-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `compareSignatureToMethod` not matching correctly in case opcodes are null ([cca12aa](https://github.com/revanced/revanced-patcher/commit/cca12aa34a60d766c02e55241df847f7d230d4d7))
|
||||
* `ConcurrentModificationException` while iterating through `proxies` and modifying it ([6cb7cdb](https://github.com/revanced/revanced-patcher/commit/6cb7cdb0b2a2b954adb04033e0f2d3ccb4604545))
|
||||
* `JarPatchBundle` loading non-class files to class loader ([849616d](https://github.com/revanced/revanced-patcher/commit/849616dc2b6e30ec1fa1d8a8f9c1f881fc11676a))
|
||||
* `PackageMetadata` ([7399450](https://github.com/revanced/revanced-patcher/commit/739945013962fd80d2635fff126d84046870f956))
|
||||
* `replaceWith` not replacing classes with used class proxies ([4178a1e](https://github.com/revanced/revanced-patcher/commit/4178a1eedce1436ffeb3ddd6952ce0b6ec87d5a0))
|
||||
* adding existing classes to the patchers cache ([9659a61](https://github.com/revanced/revanced-patcher/commit/9659a61c5c3a84714160b78b32cc337a97c8caa9))
|
||||
* always return PatchResultSuccess on patch success ([996c4ac](https://github.com/revanced/revanced-patcher/commit/996c4acb2061db776430ad8b07bfdb3fe32861f6))
|
||||
* applying no patches throwing error ([5ca5a1c](https://github.com/revanced/revanced-patcher/commit/5ca5a1c29e087ce7e4b6d5e593b775365803151d))
|
||||
* applyPatches not returning successful patches ([f806cb3](https://github.com/revanced/revanced-patcher/commit/f806cb38c571cdd22016396ee1874ee18c91b79f))
|
||||
* avoid ignoring test resources (fixes [#1](https://github.com/revanced/revanced-patcher/issues/1)) ([d5a3c76](https://github.com/revanced/revanced-patcher/commit/d5a3c76389ba902c22ddc8b7ba1a110b7ff852df))
|
||||
* Classes not being written properly because of array shifting ([6e4db11](https://github.com/revanced/revanced-patcher/commit/6e4db110c8fdd16fb0c0ce81f427d84f2a3b6ee0))
|
||||
* current must be calculated after increment ([5f12bab](https://github.com/revanced/revanced-patcher/commit/5f12bab5df97fbe6e2e62c1bf2814a2e682ab4f3))
|
||||
* failing tests temporarily ([fc05fe7](https://github.com/revanced/revanced-patcher/commit/fc05fe79deec2486bb746d33e803ad052e68f8de))
|
||||
* fix classes having multiple instances of fields ([7cc8a7d](https://github.com/revanced/revanced-patcher/commit/7cc8a7dec321774c1d3f2f1a87ac91f952c4fb7e))
|
||||
* fix classes having multiple method instances ([398239d](https://github.com/revanced/revanced-patcher/commit/398239dc10a3ea04e46adb3be176c897876e5587))
|
||||
* Fixed writer & signature resolver, improved tests & speed, minor refactoring ([e6c2501](https://github.com/revanced/revanced-patcher/commit/e6c2501539540301d5b70014de460e5452a09b04))
|
||||
* fuzzy resolver warning params were turned around ([e5bea06](https://github.com/revanced/revanced-patcher/commit/e5bea06353805f004d607124a8ebed138f84d583))
|
||||
* give ClassWriter a ClassReader for symtable ([41749ba](https://github.com/revanced/revanced-patcher/commit/41749ba8290b2dec5dd2ab6e0bc9d714887a1a05))
|
||||
* **gradle:** publish source and javadocs ([c236ebe](https://github.com/revanced/revanced-patcher/commit/c236ebe0789f9c78d610769f0feda2b64fa4a128))
|
||||
* incorrect pattern offset ([f3b5f67](https://github.com/revanced/revanced-patcher/commit/f3b5f67b395167c1b9411b2374f3ef584b57b6cf))
|
||||
* **Io:** fix finding classes by name ([b957501](https://github.com/revanced/revanced-patcher/commit/b957501e709028005c4d6c7857022980205b6861))
|
||||
* **Io:** JAR loading and saving ([#8](https://github.com/revanced/revanced-patcher/issues/8)) ([310a7c4](https://github.com/revanced/revanced-patcher/commit/310a7c446b547d84b02c5da2161958e77ce69f0d))
|
||||
* make `methodMetadata` nullable in `MethodSignatureMetadata` ([4e56652](https://github.com/revanced/revanced-patcher/commit/4e566524299674426fb0344d09db3b0c1cb3d300))
|
||||
* make warnings nullable instead of lateinit ([8f1a629](https://github.com/revanced/revanced-patcher/commit/8f1a629191668e05917dc797e486647e55276d59))
|
||||
* match to correct signature method parameters ([1ee2e4b](https://github.com/revanced/revanced-patcher/commit/1ee2e4ba56097c5e06c93c9ce04cb5543f0e4a67))
|
||||
* **MethodResolver:** fix cd57a8c9a0db7e3ae5ad0bca202e5955930319ab ([cbd8df2](https://github.com/revanced/revanced-patcher/commit/cbd8df2df008ef37c6b43e2a8442c41f24be9358))
|
||||
* **MethodResolver:** strip labels and line numbers so opcode patterns match ([699c730](https://github.com/revanced/revanced-patcher/commit/699c730a7cecf31878827d645e845490a37de4cb))
|
||||
* **MethodResolver:** strip labels nodes so opcode patterns match ([82c5306](https://github.com/revanced/revanced-patcher/commit/82c530650f926dd026d263cfe23a7d67cb27bbf2))
|
||||
* MethodSignature#resolved throwing an exception ([c612676](https://github.com/revanced/revanced-patcher/commit/c612676543282155143471b71a095e26023806ea))
|
||||
* Move proxy package out of cache package ([ce21bd6](https://github.com/revanced/revanced-patcher/commit/ce21bd60f34d78b94d6d85f2c5375bc934ed4091))
|
||||
* null check causing an exception ([338bd9f](https://github.com/revanced/revanced-patcher/commit/338bd9f7394afd84e5e195a7f8155c813812cfb5))
|
||||
* nullable signature members ([#10](https://github.com/revanced/revanced-patcher/issues/10)) ([674461f](https://github.com/revanced/revanced-patcher/commit/674461f08daabbf92cb54e4eadb408226fac47af))
|
||||
* Patch should have access to the Cache ([4dd820f](https://github.com/revanced/revanced-patcher/commit/4dd820ffdf1b98fe41b50f7cb2670b89acfbb99d))
|
||||
* Patcher not writing resolved methods ([fac44a5](https://github.com/revanced/revanced-patcher/commit/fac44a50c39d8c102bd3e7ca4dd1bb86d29f7b57))
|
||||
* qualifying `Element` with wrong package ([024fa86](https://github.com/revanced/revanced-patcher/commit/024fa867e115f984cfa3e395b78f4f43aa81709b))
|
||||
* reaching all constructors not possible ([c459beb](https://github.com/revanced/revanced-patcher/commit/c459beb5f898d797f2f03ed36326bd9cfad03d31))
|
||||
* reformat (trigger release) ([bf48945](https://github.com/revanced/revanced-patcher/commit/bf4894592bf9ee9c6233abc91f538b7b8ef986a0))
|
||||
* remove broken code ([0e72a6e](https://github.com/revanced/revanced-patcher/commit/0e72a6e85ff9a6035510680fc5e33ab0cd14144f))
|
||||
* remove dependency to fork of Apktool ([11abc67](https://github.com/revanced/revanced-patcher/commit/11abc67d9ab7d7b273fd4cd4c53af54008a80585))
|
||||
* remove leftover debug code ([0f30eac](https://github.com/revanced/revanced-patcher/commit/0f30eac32ce66d8b90906c02ef7e7854feeecc33))
|
||||
* return mutable set of classes ([66a9b76](https://github.com/revanced/revanced-patcher/commit/66a9b768457e98fdde0b61f9a8d6aed4c1872027))
|
||||
* returning failure on success ([48c4ea2](https://github.com/revanced/revanced-patcher/commit/48c4ea2f6d9de319383a49ea2d4c6ffb4f687a2b))
|
||||
* Search method map for existing class proxy ([a1e909b](https://github.com/revanced/revanced-patcher/commit/a1e909b16337c538f8f8b475801d8b1804163bfe))
|
||||
* set index for insertAt to 0 by default ([d5b4c99](https://github.com/revanced/revanced-patcher/commit/d5b4c99c00272e3e5afec2fa0a489ba618f2a81a))
|
||||
* set marklimit to Integer.MAX_VALUE ([e6e468f](https://github.com/revanced/revanced-patcher/commit/e6e468fbb5c20b08c8bd59bafc794acea907e4b4))
|
||||
* string signature in `SignatureResolver` ([e5ae970](https://github.com/revanced/revanced-patcher/commit/e5ae9700096924e63b15a08079dce40ae07202d8))
|
||||
* Suppress unused for addFiles ([3d6a1d3](https://github.com/revanced/revanced-patcher/commit/3d6a1d38f339ce2c5d82b7ac46c208c6702d6d44))
|
||||
* throwing in case the opcode patterns do not match ([3144ec8](https://github.com/revanced/revanced-patcher/commit/3144ec872ac8651b8c0a9311ae508d5c3cc734ce))
|
||||
* use Array instead of Iterable for methodParameters ([dfac8f0](https://github.com/revanced/revanced-patcher/commit/dfac8f03a362fd273527f552d9eae121505fd4e0))
|
||||
* using old instance of `Androlib` when saving ([a4d8be2](https://github.com/revanced/revanced-patcher/commit/a4d8be20fcd444b08ec9c43f9f7029f8bacbbc41))
|
||||
* workflow on dev branch ([428f7f4](https://github.com/revanced/revanced-patcher/commit/428f7f4decb00d28c9bf137ef4cd1d5fd4a0821e))
|
||||
* write all classes ([f068fc8](https://github.com/revanced/revanced-patcher/commit/f068fc87ff8e204826639318af39e48e683254da))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* bump multidexlib2 to 2.5.2.r2 ([a6c6b49](https://github.com/revanced/revanced-patcher/commit/a6c6b4979af42936cb26608541a4f7a66393b3f0))
|
||||
* Change all references from Array to Iterable ([72f3cad](https://github.com/revanced/revanced-patcher/commit/72f3cad3f98001b0109b07373ed9cc57a9001cfa))
|
||||
* convert Patch to abstract class ([cb9b1b9](https://github.com/revanced/revanced-patcher/commit/cb9b1b9416c699c68d0fca228d4f8ca6fb634cb5))
|
||||
* Optimize Signature class ([#11](https://github.com/revanced/revanced-patcher/issues/11)) ([7faa001](https://github.com/revanced/revanced-patcher/commit/7faa001406c1f28dc2182cf6d1ab19504f4e3eb9))
|
||||
* Rename `net.revanced` to `app.revanced` ([7087230](https://github.com/revanced/revanced-patcher/commit/70872307e33282b37dd5fb315b56022ab73bf582))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* `Dependencies` annotation ([893d4c6](https://github.com/revanced/revanced-patcher/commit/893d4c699bad4c70002fc691c261447d01948b5c))
|
||||
* `PatchLoader` ([ec9fd15](https://github.com/revanced/revanced-patcher/commit/ec9fd15f9b9b9968be7fb5cb384eb8ee2a0c9ba3))
|
||||
* Add `findParentMethod` utility method ([#4](https://github.com/revanced/revanced-patcher/issues/4)) ([bbb2c54](https://github.com/revanced/revanced-patcher/commit/bbb2c547aae8dd774a1a883de24fe45da463fa35))
|
||||
* add `MethodWalker` ([7755bbc](https://github.com/revanced/revanced-patcher/commit/7755bbc645773e49053fb9ad2b6fd18a7f488659))
|
||||
* add `p` naming scheme to smali compiler ([79909cf](https://github.com/revanced/revanced-patcher/commit/79909cf260c0578e88ad22d63397957dbaa91702))
|
||||
* add extensions for cloning methods ([01bfbd6](https://github.com/revanced/revanced-patcher/commit/01bfbd656ee06cb2cab951c43d7f76a465a40830))
|
||||
* add findClass method with className ([4087f49](https://github.com/revanced/revanced-patcher/commit/4087f498638ee88ba3eaca792039fe481f404732))
|
||||
* Add first tests ([544bcf7](https://github.com/revanced/revanced-patcher/commit/544bcf76bd8a8c790c2f799606ad8c9ac7d2aa82))
|
||||
* add fuzzy resolver ([7a56dca](https://github.com/revanced/revanced-patcher/commit/7a56dca004cd793121a59ea854c77f4c1a01bd6f))
|
||||
* add immutableMethod ([c63b20f](https://github.com/revanced/revanced-patcher/commit/c63b20fa65aba8bb060a4a7a652747cba7198c2b))
|
||||
* add inline smali compiler ([bfe4e3e](https://github.com/revanced/revanced-patcher/commit/bfe4e3e298ac963936ca9621e12aefbe56260826))
|
||||
* add missing test for fields ([6b8b057](https://github.com/revanced/revanced-patcher/commit/6b8b0573d479e227b45dc36a6abac622c3ccebdd))
|
||||
* add or extension for AccessFlags ([00c85b5](https://github.com/revanced/revanced-patcher/commit/00c85b5d750ccc8de69ad4101220b19eeaf99bcb))
|
||||
* Add patch metadata ([642e903](https://github.com/revanced/revanced-patcher/commit/642e9031eb3727ebdca22c75b7c5c602a8775da0)), closes [ReVancedTeam/revanced-patches#1](https://github.com/ReVancedTeam/revanced-patches/issues/1)
|
||||
* add SafeClassWriter ([6626014](https://github.com/revanced/revanced-patcher/commit/6626014ef3dde2f98a53f75d71eeb0de85189bf3))
|
||||
* Add warnings for Fuzzy resolver ([715a2ad](https://github.com/revanced/revanced-patcher/commit/715a2ad025d127b5a8225ce50202a859f53c7f50))
|
||||
* allow classes to be overwritten in addFiles and resolve signatures when applyPatches is called ([1db735b](https://github.com/revanced/revanced-patcher/commit/1db735b1e2b570bdb1ddce0b9cd724c580113a84))
|
||||
* Allow unknown opcodes using `null` ([0e5f4ba](https://github.com/revanced/revanced-patcher/commit/0e5f4ba2d55288415c4d1be70ab6a8ab8c1c0d10))
|
||||
* Finish first patcher test ([0d8d19e](https://github.com/revanced/revanced-patcher/commit/0d8d19e708a47315e28e7493618568ea40f1e062))
|
||||
* Improve `SignatureResolver` ([139a23b](https://github.com/revanced/revanced-patcher/commit/139a23b7500a2d2577df47caf3fd0c5ec891a8d8))
|
||||
* migrate to `DexPatchBundle` and `JarPatchBundle` ([8615798](https://github.com/revanced/revanced-patcher/commit/8615798711185b30ce622d9d09faba21f3a92f97))
|
||||
* migrate to dexlib ([3651981](https://github.com/revanced/revanced-patcher/commit/36519811610192e299834e9d00627a94faad56a9))
|
||||
* Minor refactor and return proxy, if class has been proxied already ([4b26305](https://github.com/revanced/revanced-patcher/commit/4b26305bd57ba9e3eb3e34218ffe10d6c5a2f598))
|
||||
* optional `forStaticMethod` parameter for `InlineSmaliCompiler.compileMethodInstructions` ([41e8860](https://github.com/revanced/revanced-patcher/commit/41e88605c33d1f0d9e7f5466cac03a3b339afb82))
|
||||
* patch dependencies annotation and `PatcherOptions` ([6c65952](https://github.com/revanced/revanced-patcher/commit/6c65952d80a795a3ef4a37877123e9375025d3ae))
|
||||
* properly manage `ClassProxy` & add `ProxyBackedClassList` ([6cb1fdf](https://github.com/revanced/revanced-patcher/commit/6cb1fdf6171e1ab75b7ee28163965eacc00cc5a0))
|
||||
* remaining mutable `EncodedValue` classes ([3f97cc8](https://github.com/revanced/revanced-patcher/commit/3f97cc8e1fa10546d7069e01e5e66a537b0d6f7e))
|
||||
* string signature ([#22](https://github.com/revanced/revanced-patcher/issues/22)) ([612515a](https://github.com/revanced/revanced-patcher/commit/612515acf8539febf952f258d30aa3d4b631e3b7))
|
||||
* use annotations instead of metadata objects ([d20f7fd](https://github.com/revanced/revanced-patcher/commit/d20f7fd6e1ede6ec7baccb1500ab3fc66d78df73))
|
||||
* utility functions to get metadata of patch & sigs ([54511a4](https://github.com/revanced/revanced-patcher/commit/54511a4fc6417d7fe0c868d441e7d6b0ec9e218d))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* check type instead of class ([c7ef264](https://github.com/revanced/revanced-patcher/commit/c7ef2644d83e1d8e84decb0631a6549d394180fc))
|
||||
* decode manifest only when not using resource patcher ([4f60bea](https://github.com/revanced/revanced-patcher/commit/4f60bea81e0bbe85dc6c3150238980292a1e52ab))
|
||||
* depend on `androlib` instead of `ApkDecoder` ([cc9416d](https://github.com/revanced/revanced-patcher/commit/cc9416dd11b66140c2882021cbe5088659d85371))
|
||||
* do not resolve empty signatures list ([b1eebc9](https://github.com/revanced/revanced-patcher/commit/b1eebc99a71269df33c37f35c1f56ea20a9d6bc0))
|
||||
* lazy-ify all mutable clones ([d18a3b6](https://github.com/revanced/revanced-patcher/commit/d18a3b6a28cae4fcb1c4986903208298ee50b083))
|
||||
* optimize indexOf call away ([9991f39](https://github.com/revanced/revanced-patcher/commit/9991f39c9a4fa22a221aab0bbf9e08ca7f967fa9))
|
||||
* use Set instead of List since there are no dupes ([e65ebd2](https://github.com/revanced/revanced-patcher/commit/e65ebd27c250b1735acf73af0f6b03274b0137f6))
|
||||
* use String List and compare instead of any lambda ([5bd416b](https://github.com/revanced/revanced-patcher/commit/5bd416b409290906a6378344f70391e8692ae27f))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* AccessFlag extensions not working with IDE ([0bfb92a](https://github.com/revanced/revanced-patcher/commit/0bfb92a0cbd72df5ba513264efb583e201cfcf82))
|
||||
* previous commits check for dupes in dexFile, not cache ([e810197](https://github.com/revanced/revanced-patcher/commit/e810197e2aa64534f2e8637165d884cbefbce8ae))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* arrayOf has to be changed to listOf.
|
||||
* Method signature of Patcher#save() was changed to comply with the changes of multidexlib2.
|
||||
* Removed usage of ASM library
|
||||
* Array<Int> was changed to IntArray. This breaks existing patches.
|
||||
* Package name was changed from "net.revanced" to "app.revanced"
|
||||
* Method signature of execute() was changed to include the cache, this will break existing implementations of the Patch class.
|
||||
* Patch class is now an abstract class. You must implement it. You can use anonymous implements, like done in the tests.
|
||||
|
||||
# [1.0.0-dev.18](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.17...v1.0.0-dev.18) (2022-06-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* `Dependencies` annotation ([83d608a](https://github.com/revanced/revanced-patcher/commit/83d608ac06a7d5ceb31b6e0022b501d99edb63a3))
|
||||
* optional `forStaticMethod` parameter for `InlineSmaliCompiler.compileMethodInstructions` ([28b9847](https://github.com/revanced/revanced-patcher/commit/28b98478e4e8e8f238e82f7fa2307aeb1547955d))
|
||||
|
||||
# [1.0.0-dev.17](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.16...v1.0.0-dev.17) (2022-05-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* patch dependencies annotation and `PatcherOptions` ([8442991](https://github.com/revanced/revanced-patcher/commit/84429912900872405b44804943357dda8430a550))
|
||||
|
||||
# [1.0.0-dev.16](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.15...v1.0.0-dev.16) (2022-05-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `JarPatchBundle` loading non-class files to class loader ([3f0c740](https://github.com/revanced/revanced-patcher/commit/3f0c740200dd91a060426638c2f8f516938b4c53))
|
||||
* remove dependency to fork of Apktool ([0fa529f](https://github.com/revanced/revanced-patcher/commit/0fa529fcdf9a7b5ea9a361b9f9f32f3f3fce009f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* migrate to `DexPatchBundle` and `JarPatchBundle` ([7573db2](https://github.com/revanced/revanced-patcher/commit/7573db25757de89824af4f3aea167e500120eabb))
|
||||
|
||||
# [1.0.0-dev.15](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.14...v1.0.0-dev.15) (2022-05-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* utility functions to get metadata of patch & sigs ([72f16b7](https://github.com/revanced/revanced-patcher/commit/72f16b778587c28d8f8e91da502f197e7dc35d6d))
|
||||
|
||||
# [1.0.0-dev.14](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.13...v1.0.0-dev.14) (2022-05-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* reformat (trigger release) ([45a167e](https://github.com/revanced/revanced-patcher/commit/45a167e7856da0306f796953775c7b7543d9bec0))
|
||||
|
||||
# [1.0.0-dev.13](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.12...v1.0.0-dev.13) (2022-05-24)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* decode manifest only when not using resource patcher ([40b1fa4](https://github.com/revanced/revanced-patcher/commit/40b1fa43e1704ace29d3e349df2f4a8ea828c5c2))
|
||||
|
||||
# [1.0.0-dev.12](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.11...v1.0.0-dev.12) (2022-05-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* using old instance of `Androlib` when saving ([5630e49](https://github.com/revanced/revanced-patcher/commit/5630e4966310311cdfd53e2ba128255047626adc))
|
||||
|
||||
# [1.0.0-dev.11](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2022-05-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* `PatchLoader` ([1a99eca](https://github.com/revanced/revanced-patcher/commit/1a99ecaffe5e55977655316e68b014fdeba374a1))
|
||||
* use annotations instead of metadata objects ([6726884](https://github.com/revanced/revanced-patcher/commit/6726884be5af56b6856749e73fb9f4f97559854a))
|
||||
|
||||
# [1.0.0-dev.10](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.9...v1.0.0-dev.10) (2022-05-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* qualifying `Element` with wrong package ([4d74de4](https://github.com/revanced/revanced-patcher/commit/4d74de4061f26c0d7c17fabd849051b429d86033))
|
||||
|
||||
# [1.0.0-dev.9](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.8...v1.0.0-dev.9) (2022-05-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `compareSignatureToMethod` not matching correctly in case opcodes are null ([5ae5e98](https://github.com/revanced/revanced-patcher/commit/5ae5e98f1f8e174d800bcc75723e1ed965d66196))
|
||||
* `ConcurrentModificationException` while iterating through `proxies` and modifying it ([bfeeaf4](https://github.com/revanced/revanced-patcher/commit/bfeeaf443549c9a43279d83a0628c061a382beb9))
|
||||
* `PackageMetadata` ([305a817](https://github.com/revanced/revanced-patcher/commit/305a81793a9a04fe4e8969f2d3b591b0f01e3b63))
|
||||
* `replaceWith` not replacing classes with used class proxies ([f0f3403](https://github.com/revanced/revanced-patcher/commit/f0f34031dd4e618223f016f7c427d7c93ab8456a))
|
||||
* adding existing classes to the patchers cache ([4281546](https://github.com/revanced/revanced-patcher/commit/4281546f69225ee90ec4c003f4313df41edf71a6))
|
||||
* always return PatchResultSuccess on patch success ([866b03a](https://github.com/revanced/revanced-patcher/commit/866b03af217ad97dd2755bfdc0ffe5bcf723c949))
|
||||
* applying no patches throwing error ([f88c118](https://github.com/revanced/revanced-patcher/commit/f88c11820dbdc0d1d52a49c9bcdb4f7caa9eb6eb))
|
||||
* applyPatches not returning successful patches ([8b70bb4](https://github.com/revanced/revanced-patcher/commit/8b70bb42909434a5e59315502f6d54d7c7691f18))
|
||||
* Classes not being written properly because of array shifting ([1471956](https://github.com/revanced/revanced-patcher/commit/147195647c3990ab78ba95e4b3000650e718b713))
|
||||
* failing tests temporarily ([66b08f8](https://github.com/revanced/revanced-patcher/commit/66b08f8b3a8f31844c7e7bab4df4243521d4a431))
|
||||
* fix classes having multiple instances of fields ([b711b80](https://github.com/revanced/revanced-patcher/commit/b711b8001e4845857fa6cc71b107f1c553b31e80))
|
||||
* fix classes having multiple method instances ([12c10d8](https://github.com/revanced/revanced-patcher/commit/12c10d8c64422c4534c23467e367707e3b953f82))
|
||||
* Fixed writer & signature resolver, improved tests & speed, minor refactoring ([bb42fa3](https://github.com/revanced/revanced-patcher/commit/bb42fa3c6f59b78a7223fc70edbe598ec181ee37))
|
||||
* fuzzy resolver warning params were turned around ([d49df10](https://github.com/revanced/revanced-patcher/commit/d49df10a3ca6b472ce4a32d10cfe787ca243d47b))
|
||||
* incorrect pattern offset ([03700ff](https://github.com/revanced/revanced-patcher/commit/03700ffa519e5f20b1a0d0ffe68f3fb504351ee5))
|
||||
* make `methodMetadata` nullable in `MethodSignatureMetadata` ([864e38c](https://github.com/revanced/revanced-patcher/commit/864e38c06906a9e29271fe383d51a8ec6594a46c))
|
||||
* make warnings nullable instead of lateinit ([04b49b8](https://github.com/revanced/revanced-patcher/commit/04b49b8b664e45e64e9561eca3353ffdeda91187))
|
||||
* match to correct signature method parameters ([c49071a](https://github.com/revanced/revanced-patcher/commit/c49071aff78245f27c98a4760b361c30aa6340bc))
|
||||
* MethodSignature#resolved throwing an exception ([82b1e66](https://github.com/revanced/revanced-patcher/commit/82b1e66d54bed1e4c335e0515b7ff3ec901fa6f8))
|
||||
* Move proxy package out of cache package ([6bc4e7e](https://github.com/revanced/revanced-patcher/commit/6bc4e7eab742f5796f3041332c70495e3f993c9b))
|
||||
* null check causing an exception ([560c485](https://github.com/revanced/revanced-patcher/commit/560c485ab08b08a213b58704b11b1e2f5f625080))
|
||||
* Patcher not writing resolved methods ([d15240d](https://github.com/revanced/revanced-patcher/commit/d15240d0330a63c4b568fc5de3de861b8046cba4))
|
||||
* reaching all constructors not possible ([37fa994](https://github.com/revanced/revanced-patcher/commit/37fa9949ec84ffd277f32b1cd554e92be41d35e4))
|
||||
* remove leftover debug code ([4458141](https://github.com/revanced/revanced-patcher/commit/4458141d6d2e1b015c0d70a6e65e6c32a3cf17dc))
|
||||
* return mutable set of classes ([84bc7e0](https://github.com/revanced/revanced-patcher/commit/84bc7e0dc76f0732613383accb803f2c52da98ac))
|
||||
* returning failure on success ([3b68d5c](https://github.com/revanced/revanced-patcher/commit/3b68d5c65ec3082d1aa48525b4ee2a4163895a3b))
|
||||
* Search method map for existing class proxy ([d5e694c](https://github.com/revanced/revanced-patcher/commit/d5e694c306a47f47b8d1078b5c9f8a742445cf7e))
|
||||
* string signature in `SignatureResolver` ([ac36d19](https://github.com/revanced/revanced-patcher/commit/ac36d19693390db8f404ed30963aefb2fb7519e0))
|
||||
* Suppress unused for addFiles ([a0d6d46](https://github.com/revanced/revanced-patcher/commit/a0d6d462170552929039d71eafa813fdfde215cb))
|
||||
* throwing in case the opcode patterns do not match ([f72dd68](https://github.com/revanced/revanced-patcher/commit/f72dd68ec575ee0926ee668911ebb6f85b75f7d1))
|
||||
* use Array instead of Iterable for methodParameters ([312235b](https://github.com/revanced/revanced-patcher/commit/312235b194cac01ddc3f03ecff32c7de4e48c29c))
|
||||
* write all classes ([6ad51aa](https://github.com/revanced/revanced-patcher/commit/6ad51aad9a94d8dd5afb5e270138ef7161ccfb07))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* bump multidexlib2 to 2.5.2.r2 ([32e6458](https://github.com/revanced/revanced-patcher/commit/32e645850d4cc74aa708984da03ae1606e696d20))
|
||||
* Change all references from Array to Iterable ([264989f](https://github.com/revanced/revanced-patcher/commit/264989f48804ed637469436acf8165ac4b7be383))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `MethodWalker` ([659e108](https://github.com/revanced/revanced-patcher/commit/659e1087c9e7a33e04cd7eb728c01ed946335810))
|
||||
* add `p` naming scheme to smali compiler ([38556d6](https://github.com/revanced/revanced-patcher/commit/38556d61ab192dfa84083d935ee3e9eee5450d06))
|
||||
* add extensions for cloning methods ([df7503b](https://github.com/revanced/revanced-patcher/commit/df7503b47b1e2162d6ab666f8586c633c314016f))
|
||||
* add findClass method with className ([78235d1](https://github.com/revanced/revanced-patcher/commit/78235d1abe267e6aaa086662ad69af7132b8ff74))
|
||||
* Add first tests ([6767c8f](https://github.com/revanced/revanced-patcher/commit/6767c8fbc15ea18a61db53e1472483632077f62a))
|
||||
* add fuzzy resolver ([a492808](https://github.com/revanced/revanced-patcher/commit/a4928080217451017a99cf158fd5cc9d650a5a9e))
|
||||
* add immutableMethod ([eed1cfd](https://github.com/revanced/revanced-patcher/commit/eed1cfda7b89f03f4c61ac4401707e1a12e6efb3))
|
||||
* add inline smali compiler ([dbafe2a](https://github.com/revanced/revanced-patcher/commit/dbafe2ab37b25480f3e218d94ced5af2e56cba68))
|
||||
* add missing test for fields ([4022b8b](https://github.com/revanced/revanced-patcher/commit/4022b8b847e8767ace0da3f98ad72ab61a4c242b))
|
||||
* add or extension for AccessFlags ([aec5eeb](https://github.com/revanced/revanced-patcher/commit/aec5eeb597f0e9968b43efa228c96e83175e031c))
|
||||
* Add patch metadata ([8544fc4](https://github.com/revanced/revanced-patcher/commit/8544fc4cbcb5d7c1ac0f6fcae52882a00d2bacf5)), closes [ReVancedTeam/revanced-patches#1](https://github.com/ReVancedTeam/revanced-patches/issues/1)
|
||||
* Add warnings for Fuzzy resolver ([643a14e](https://github.com/revanced/revanced-patcher/commit/643a14e664c7ff86580da683eaff9c486884ee2c))
|
||||
* allow classes to be overwritten in addFiles and resolve signatures when applyPatches is called ([5f71a34](https://github.com/revanced/revanced-patcher/commit/5f71a342ac9c6aa64a4983156f595ae0832c30e8))
|
||||
* Allow unknown opcodes using `null` ([f4a47d4](https://github.com/revanced/revanced-patcher/commit/f4a47d4dc893bb511ca2087a1a63bfc35888663f))
|
||||
* Finish first patcher test ([a9e4e8a](https://github.com/revanced/revanced-patcher/commit/a9e4e8ac3203bdd62abcd1e366f08a2269919571))
|
||||
* Improve `SignatureResolver` ([88a6a27](https://github.com/revanced/revanced-patcher/commit/88a6a2730296883e191543c2666f39f24c05d74d))
|
||||
* migrate to dexlib ([be51f42](https://github.com/revanced/revanced-patcher/commit/be51f42710c1489ef4405700e56ffecee5e6552f))
|
||||
* Minor refactor and return proxy, if class has been proxied already ([2d3c611](https://github.com/revanced/revanced-patcher/commit/2d3c61113dc9b76c43e93928ba11026fe0ad444e))
|
||||
* properly manage `ClassProxy` & add `ProxyBackedClassList` ([2319787](https://github.com/revanced/revanced-patcher/commit/23197879b20906aac7563e5f8107305edd7ccb1b))
|
||||
* remaining mutable `EncodedValue` classes ([7d38bb0](https://github.com/revanced/revanced-patcher/commit/7d38bb0baaeabade6a9e64d97e2dd6c20edd153f))
|
||||
* string signature ([#22](https://github.com/revanced/revanced-patcher/issues/22)) ([c245edb](https://github.com/revanced/revanced-patcher/commit/c245edb0c5317c1bb884ea315a1a04b720f20dd5))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* depend on `androlib` instead of `ApkDecoder` ([e5c054a](https://github.com/revanced/revanced-patcher/commit/e5c054ac2f68b00ac123a45ed56b9f150332a82d))
|
||||
* do not resolve empty signatures list ([1f7bf3a](https://github.com/revanced/revanced-patcher/commit/1f7bf3ac6c77a71abd687f2ff6f7306a40654a1b))
|
||||
* lazy-ify all mutable clones ([05e4400](https://github.com/revanced/revanced-patcher/commit/05e44007d81399791aa1bab1eead66b7ff662043))
|
||||
* optimize indexOf call away ([f8e978a](https://github.com/revanced/revanced-patcher/commit/f8e978af888255d9c104a8275be1d9b091af3f96))
|
||||
* use Set instead of List since there are no dupes ([6221387](https://github.com/revanced/revanced-patcher/commit/622138736dca6c0161171330801b7b5666594ec7))
|
||||
* use String List and compare instead of any lambda ([aed4fd9](https://github.com/revanced/revanced-patcher/commit/aed4fd9a3c9e7f96c1e2c54b831c3fe7d3d720a2))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* AccessFlag extensions not working with IDE ([e161f7f](https://github.com/revanced/revanced-patcher/commit/e161f7fea449883b7ac0fb436ed4f7f2ff78af62))
|
||||
* previous commits check for dupes in dexFile, not cache ([433914f](https://github.com/revanced/revanced-patcher/commit/433914feda3066102a073d6b3bc457d0fae87911))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* arrayOf has to be changed to listOf.
|
||||
* Method signature of Patcher#save() was changed to comply with the changes of multidexlib2.
|
||||
* Removed usage of ASM library
|
||||
|
||||
# [1.0.0-dev.8](https://github.com/ReVancedTeam/revanced-patcher/compare/v1.0.0-dev.7...v1.0.0-dev.8) (2022-03-24)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* check type instead of class ([47eb493](https://github.com/ReVancedTeam/revanced-patcher/commit/47eb493f5425dc27a4d6e79e6b02a36ef760e8da))
|
||||
|
||||
# [1.0.0-dev.7](https://github.com/ReVancedTeam/revanced-patcher/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MethodResolver:** fix cd57a8c9a0db7e3ae5ad0bca202e5955930319ab ([1af31b2](https://github.com/ReVancedTeam/revanced-patcher/commit/1af31b2aa3772a7473c04d27bf835c8eae13438d))
|
||||
|
||||
# [1.0.0-dev.6](https://github.com/ReVancedTeam/revanced-patcher/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MethodResolver:** strip labels nodes so opcode patterns match ([cd57a8c](https://github.com/ReVancedTeam/revanced-patcher/commit/cd57a8c9a0db7e3ae5ad0bca202e5955930319ab))
|
||||
|
||||
# [1.0.0-dev.5](https://github.com/ReVancedTeam/revanced-patcher/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MethodResolver:** strip labels and line numbers so opcode patterns match ([8d1bb5f](https://github.com/ReVancedTeam/revanced-patcher/commit/8d1bb5f3d9da544cf6e3e3848bfcc56327cde810))
|
||||
|
||||
# [1.0.0-dev.4](https://github.com/ReVancedTeam/revanced-patcher/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2022-03-23)
|
||||
|
||||
|
||||
|
||||
@@ -1,60 +1,48 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.7.0"
|
||||
kotlin("jvm") version "1.6.10"
|
||||
java
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
group = "app.revanced"
|
||||
|
||||
val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
|
||||
val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = uri("https://maven.pkg.github.com/revanced/multidexlib2")
|
||||
credentials {
|
||||
username = githubUsername
|
||||
password = githubPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("xpp3:xpp3:1.1.4c")
|
||||
implementation("org.smali:smali:2.5.2")
|
||||
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
||||
implementation("org.apktool:apktool-lib:2.6.9-SNAPSHOT")
|
||||
|
||||
implementation(kotlin("stdlib"))
|
||||
implementation("org.ow2.asm:asm:9.2")
|
||||
implementation("org.ow2.asm:asm-util:9.2")
|
||||
implementation("org.ow2.asm:asm-tree:9.2")
|
||||
implementation("org.ow2.asm:asm-commons:9.2")
|
||||
implementation("io.github.microutils:kotlin-logging:2.1.21")
|
||||
testImplementation("ch.qos.logback:logback-classic:1.2.11") // use your own logger!
|
||||
testImplementation(kotlin("test"))
|
||||
}
|
||||
|
||||
tasks {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events("PASSED", "SKIPPED", "FAILED")
|
||||
}
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events("PASSED", "SKIPPED", "FAILED")
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
if (System.getenv("GITHUB_ACTOR") != null)
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/ReVancedTeam/revanced-patcher")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
else
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
publications {
|
||||
register<MavenPublication>("gpr") {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
kotlin.code.style = official
|
||||
version = 2.2.0-dev.1
|
||||
version = 1.0.0-dev.4
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
257
gradlew
vendored
Executable file → Normal file
257
gradlew
vendored
Executable file → Normal file
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright <20> 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,101 +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 <20>$var<61>, <20>${var}<7D>, <20>${var:-default}<7D>, <20>${var+SET}<7D>,
|
||||
# <20>${var#prefix}<7D>, <20>${var%suffix}<7D>, and <20>$( cmd )<29>;
|
||||
# * compound commands having a testable exit status, especially <20>case<73>;
|
||||
# * various built-in commands including <20>command<6E>, <20>set<65>, and <20>ulimit<69>.
|
||||
#
|
||||
# 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/master/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
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
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
|
||||
;;
|
||||
MSYS* | MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -121,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
|
||||
@@ -132,7 +98,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
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
|
||||
@@ -140,95 +106,80 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
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
|
||||
|
||||
# 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.
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# 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" "$@"
|
||||
|
||||
@@ -1,332 +1,70 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.data.PackageMetadata
|
||||
import app.revanced.patcher.data.impl.findIndexed
|
||||
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.extensions.nullOutputStream
|
||||
import app.revanced.patcher.fingerprint.method.utils.MethodFingerprintUtils.resolve
|
||||
import app.revanced.patcher.cache.Cache
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
import app.revanced.patcher.patch.PatchResultError
|
||||
import app.revanced.patcher.patch.PatchResultSuccess
|
||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||
import app.revanced.patcher.util.ListBackedSet
|
||||
import brut.androlib.Androlib
|
||||
import brut.androlib.meta.UsesFramework
|
||||
import brut.androlib.options.BuildOptions
|
||||
import brut.androlib.res.AndrolibResources
|
||||
import brut.androlib.res.data.ResPackage
|
||||
import brut.androlib.res.decoder.AXmlResourceParser
|
||||
import brut.androlib.res.decoder.ResAttrDecoder
|
||||
import brut.androlib.res.decoder.XmlPullStreamDecoder
|
||||
import brut.androlib.res.xml.ResXmlPatcher
|
||||
import brut.directory.ExtFile
|
||||
import lanchon.multidexlib2.BasicDexFileNamer
|
||||
import lanchon.multidexlib2.DexIO
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import org.jf.dexlib2.Opcodes
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.DexFile
|
||||
import org.jf.dexlib2.writer.io.MemoryDataStore
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
val NAMER = BasicDexFileNamer()
|
||||
import app.revanced.patcher.resolver.MethodResolver
|
||||
import app.revanced.patcher.signature.Signature
|
||||
import app.revanced.patcher.util.Io
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* The ReVanced Patcher.
|
||||
* @param options The options for the patcher.
|
||||
* The Patcher class.
|
||||
* ***It is of utmost importance that the input and output streams are NEVER closed.***
|
||||
*
|
||||
* @param input the input stream to read from, must be a JAR
|
||||
* @param output the output stream to write to
|
||||
* @param signatures the signatures
|
||||
* @sample app.revanced.patcher.PatcherTest
|
||||
* @throws IOException if one of the streams are closed
|
||||
*/
|
||||
class Patcher(private val options: PatcherOptions) {
|
||||
private val logger = options.logger
|
||||
private val opcodes: Opcodes
|
||||
class Patcher(
|
||||
private val input: InputStream,
|
||||
private val output: OutputStream,
|
||||
signatures: Array<Signature>,
|
||||
) {
|
||||
var cache: Cache
|
||||
|
||||
val data: PatcherData
|
||||
private var io: Io
|
||||
private val patches = mutableListOf<Patch>()
|
||||
|
||||
init {
|
||||
val extInputFile = ExtFile(options.inputFile)
|
||||
val outDir = File(options.resourceCacheDirectory)
|
||||
if (outDir.exists()) {
|
||||
logger.info("Deleting existing resource cache directory")
|
||||
outDir.deleteRecursively()
|
||||
}
|
||||
outDir.mkdirs()
|
||||
|
||||
val androlib = Androlib(BuildOptions().also { it.setBuildOptions(options) })
|
||||
val resourceTable = androlib.getResTable(extInputFile, true)
|
||||
|
||||
val packageMetadata = PackageMetadata()
|
||||
|
||||
if (options.patchResources) {
|
||||
logger.info("Decoding resources")
|
||||
|
||||
// decode resources to cache directory
|
||||
androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable)
|
||||
androlib.decodeResourcesFull(extInputFile, outDir, resourceTable)
|
||||
|
||||
// read additional metadata from the resource table
|
||||
packageMetadata.metaInfo.usesFramework = UsesFramework().also { framework ->
|
||||
framework.ids = resourceTable.listFramePackages().map { it.id }.sorted()
|
||||
}
|
||||
|
||||
packageMetadata.metaInfo.doNotCompress = buildList {
|
||||
androlib.recordUncompressedFiles(extInputFile, this)
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.info("Only decoding AndroidManifest.xml because resource patching is disabled")
|
||||
|
||||
// create decoder for the resource table
|
||||
val decoder = ResAttrDecoder()
|
||||
decoder.currentPackage = ResPackage(resourceTable, 0, null)
|
||||
|
||||
// create xml parser with the decoder
|
||||
val axmlParser = AXmlResourceParser()
|
||||
axmlParser.attrDecoder = decoder
|
||||
|
||||
// parse package information with the decoder and parser which will set required values in the resource table
|
||||
// instead of decodeManifest another more low level solution can be created to make it faster/better
|
||||
XmlPullStreamDecoder(
|
||||
axmlParser, AndrolibResources().resXmlSerializer
|
||||
).decodeManifest(
|
||||
extInputFile.directory.getFileInput("AndroidManifest.xml"), nullOutputStream
|
||||
)
|
||||
}
|
||||
|
||||
packageMetadata.packageName = resourceTable.currentResPackage.name
|
||||
packageMetadata.packageVersion = resourceTable.versionInfo.versionName
|
||||
packageMetadata.metaInfo.versionInfo = resourceTable.versionInfo
|
||||
packageMetadata.metaInfo.sdkInfo = resourceTable.sdkInfo
|
||||
|
||||
logger.info("Reading dex files")
|
||||
|
||||
// read dex files
|
||||
val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
|
||||
// get the opcodes
|
||||
opcodes = dexFile.opcodes
|
||||
|
||||
// finally create patcher data
|
||||
data = PatcherData(
|
||||
dexFile.classes.toMutableList(), options.resourceCacheDirectory, packageMetadata
|
||||
)
|
||||
val classes = mutableListOf<ClassNode>()
|
||||
io = Io(input, output, classes)
|
||||
io.readFromJar()
|
||||
cache = Cache(classes, MethodResolver(classes, signatures).resolve())
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional dex file container to the patcher.
|
||||
* @param files The dex file containers to add to the patcher.
|
||||
* @param allowedOverwrites A list of class types that are allowed to be overwritten.
|
||||
* @param throwOnDuplicates If this is set to true, the patcher will throw an exception if a duplicate class has been found.
|
||||
* Saves the output to the output stream.
|
||||
* Calling this method will close the input and output streams,
|
||||
* meaning this method should NEVER be called after.
|
||||
*
|
||||
* @throws IOException if one of the streams are closed
|
||||
*/
|
||||
fun addFiles(
|
||||
files: List<File>,
|
||||
allowedOverwrites: Iterable<String> = emptyList(),
|
||||
throwOnDuplicates: Boolean = false,
|
||||
callback: (File) -> Unit
|
||||
) {
|
||||
for (file in files) {
|
||||
var modified = false
|
||||
for (classDef in MultiDexIO.readDexFile(true, file, NAMER, null, null).classes) {
|
||||
val type = classDef.type
|
||||
fun save() {
|
||||
io.saveAsJar()
|
||||
}
|
||||
|
||||
val existingClass = data.bytecodeData.classes.internalClasses.findIndexed { it.type == type }
|
||||
if (existingClass == null) {
|
||||
if (throwOnDuplicates) throw Exception("Class $type has already been added to the patcher")
|
||||
fun addPatches(vararg patches: Patch) {
|
||||
this.patches.addAll(patches)
|
||||
}
|
||||
|
||||
logger.trace("Merging $type")
|
||||
data.bytecodeData.classes.internalClasses.add(classDef)
|
||||
modified = true
|
||||
|
||||
continue
|
||||
fun applyPatches(stopOnError: Boolean = false): Map<String, Result<Nothing?>> {
|
||||
return buildMap {
|
||||
for (patch in patches) {
|
||||
val result: Result<Nothing?> = try {
|
||||
val pr = patch.execute(cache)
|
||||
if (pr.isSuccess()) continue
|
||||
Result.failure(Exception(pr.error()?.errorMessage() ?: "Unknown error"))
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
|
||||
if (!allowedOverwrites.contains(type)) continue
|
||||
|
||||
logger.trace("Overwriting $type")
|
||||
|
||||
val index = existingClass.second
|
||||
data.bytecodeData.classes.internalClasses[index] = classDef
|
||||
modified = true
|
||||
}
|
||||
if (modified) callback(file)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the patched dex file.
|
||||
*/
|
||||
fun save(): PatcherResult {
|
||||
val packageMetadata = data.packageMetadata
|
||||
val metaInfo = packageMetadata.metaInfo
|
||||
var resourceFile: File? = null
|
||||
|
||||
if (options.patchResources) {
|
||||
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
|
||||
|
||||
val androlibResources = AndrolibResources().also { resources ->
|
||||
resources.buildOptions = BuildOptions().also { buildOptions ->
|
||||
buildOptions.setBuildOptions(options)
|
||||
buildOptions.isFramework = metaInfo.isFrameworkApk
|
||||
buildOptions.resourcesAreCompressed = metaInfo.compressionType
|
||||
buildOptions.doNotCompress = metaInfo.doNotCompress
|
||||
}
|
||||
|
||||
resources.setSdkInfo(metaInfo.sdkInfo)
|
||||
resources.setVersionInfo(metaInfo.versionInfo)
|
||||
resources.setSharedLibrary(metaInfo.sharedLibrary)
|
||||
resources.setSparseResources(metaInfo.sparseResources)
|
||||
}
|
||||
|
||||
val manifestFile = cacheDirectory.resolve("AndroidManifest.xml")
|
||||
|
||||
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifestFile)
|
||||
|
||||
val aaptFile = cacheDirectory.resolve("aapt_temp_file")
|
||||
|
||||
// delete if it exists
|
||||
Files.deleteIfExists(aaptFile.toPath())
|
||||
|
||||
val resDirectory = cacheDirectory.resolve("res")
|
||||
val includedFiles = metaInfo.usesFramework.ids.map { id ->
|
||||
androlibResources.getFrameworkApk(
|
||||
id, metaInfo.usesFramework.tag
|
||||
)
|
||||
}.toTypedArray()
|
||||
|
||||
logger.info("Compiling resources")
|
||||
androlibResources.aaptPackage(
|
||||
aaptFile, manifestFile, resDirectory, null, null, includedFiles
|
||||
)
|
||||
|
||||
resourceFile = aaptFile
|
||||
}
|
||||
|
||||
logger.trace("Creating new dex file")
|
||||
val newDexFile = object : DexFile {
|
||||
override fun getClasses(): Set<ClassDef> {
|
||||
data.bytecodeData.classes.applyProxies()
|
||||
return ListBackedSet(data.bytecodeData.classes.internalClasses)
|
||||
}
|
||||
|
||||
override fun getOpcodes(): Opcodes {
|
||||
return this@Patcher.opcodes
|
||||
this[patch.patchName] = result
|
||||
if (stopOnError && result.isFailure) break
|
||||
}
|
||||
}
|
||||
|
||||
// write modified dex files
|
||||
logger.info("Writing modified dex files")
|
||||
val dexFiles = mutableMapOf<String, MemoryDataStore>()
|
||||
MultiDexIO.writeDexFile(
|
||||
true, -1, // core count
|
||||
dexFiles, NAMER, newDexFile, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
|
||||
)
|
||||
|
||||
return PatcherResult(
|
||||
dexFiles.map {
|
||||
app.revanced.patcher.util.dex.DexFile(it.key, it.value.readAt(0))
|
||||
}, metaInfo.doNotCompress.toList(), resourceFile
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add [Patch]es to the patcher.
|
||||
* @param patches [Patch]es The patches to add.
|
||||
*/
|
||||
fun addPatches(patches: Iterable<Class<out Patch<Data>>>) {
|
||||
data.patches.addAll(patches)
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a [patch] and its dependencies recursively.
|
||||
* @param patch The [patch] to apply.
|
||||
* @param appliedPatches A map of [patch]es paired to a boolean indicating their success, to prevent infinite recursion.
|
||||
* @return The result of executing the [patch].
|
||||
*/
|
||||
private fun applyPatch(
|
||||
patch: Class<out Patch<Data>>,
|
||||
appliedPatches: MutableMap<String, Boolean>
|
||||
): PatchResult {
|
||||
val patchName = patch.patchName
|
||||
|
||||
// if the patch has already applied silently skip it
|
||||
if (appliedPatches.contains(patchName)) {
|
||||
if (!appliedPatches[patchName]!!)
|
||||
return PatchResultError("'$patchName' did not succeed previously")
|
||||
|
||||
logger.trace("Skipping '$patchName' because it has already been applied")
|
||||
|
||||
return PatchResultSuccess()
|
||||
}
|
||||
|
||||
// recursively apply all dependency patches
|
||||
patch.dependencies?.forEach {
|
||||
val patchDependency = it.java
|
||||
|
||||
val result = applyPatch(patchDependency, appliedPatches)
|
||||
|
||||
if (result.isSuccess()) return@forEach
|
||||
|
||||
val errorMessage = result.error()!!.message
|
||||
return PatchResultError("'$patchName' depends on '${patchDependency.patchName}' but the following error was raised: $errorMessage")
|
||||
}
|
||||
|
||||
val patchInstance = patch.getDeclaredConstructor().newInstance()
|
||||
|
||||
// if the current patch is a resource patch but resource patching is disabled, return an error
|
||||
val isResourcePatch = patchInstance is ResourcePatch
|
||||
if (!options.patchResources && isResourcePatch) {
|
||||
return PatchResultError("'$patchName' is a resource patch, but resource patching is disabled")
|
||||
}
|
||||
|
||||
// TODO: find a solution for this
|
||||
val data = if (isResourcePatch) {
|
||||
data.resourceData
|
||||
} else {
|
||||
val bytecodeData = data.bytecodeData
|
||||
(patchInstance as BytecodePatch).fingerprints.resolve(bytecodeData, bytecodeData.classes.internalClasses)
|
||||
bytecodeData
|
||||
}
|
||||
|
||||
logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}")
|
||||
|
||||
return try {
|
||||
val result = patchInstance.execute(data)
|
||||
appliedPatches[patchName] = result.isSuccess()
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
appliedPatches[patchName] = false
|
||||
PatchResultError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply patches loaded into the patcher.
|
||||
* @param stopOnError If true, the patches will stop on the first error.
|
||||
* @return A pair of the name of the [Patch] and its [PatchResult].
|
||||
*/
|
||||
fun applyPatches(stopOnError: Boolean = false) = sequence {
|
||||
logger.trace("Applying all patches")
|
||||
val appliedPatches = mutableMapOf<String, Boolean>() // first is success, second is name
|
||||
|
||||
for (patch in data.patches) {
|
||||
val patchResult = applyPatch(patch, appliedPatches)
|
||||
|
||||
val result = if (patchResult.isSuccess()) {
|
||||
Result.success(patchResult.success()!!)
|
||||
} else {
|
||||
Result.failure(patchResult.error()!!)
|
||||
}
|
||||
|
||||
yield(patch.patchName to result)
|
||||
if (stopOnError && patchResult.isError()) break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BuildOptions.setBuildOptions(options: PatcherOptions) {
|
||||
this.aaptPath = options.aaptPath
|
||||
this.useAapt2 = true
|
||||
this.frameworkFolderLocation = options.frameworkFolderLocation
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.data.PackageMetadata
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.impl.ResourceData
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import java.io.File
|
||||
|
||||
data class PatcherData(
|
||||
internal val internalClasses: MutableList<ClassDef>,
|
||||
internal val resourceCacheDirectory: String,
|
||||
val packageMetadata: PackageMetadata
|
||||
) {
|
||||
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
||||
internal val bytecodeData = BytecodeData(internalClasses)
|
||||
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.logging.Logger
|
||||
import app.revanced.patcher.logging.impl.NopLogger
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Options for the [Patcher].
|
||||
* @param inputFile The input file (usually an apk file).
|
||||
* @param resourceCacheDirectory Directory to cache resources.
|
||||
* @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
|
||||
* @param aaptPath Optional path to a custom aapt binary.
|
||||
* @param frameworkFolderLocation Optional path to a custom framework folder.
|
||||
* @param logger Custom logger implementation for the [Patcher].
|
||||
*/
|
||||
data class PatcherOptions(
|
||||
internal val inputFile: File,
|
||||
internal val resourceCacheDirectory: String,
|
||||
internal val patchResources: Boolean = false,
|
||||
internal val aaptPath: String = "",
|
||||
internal val frameworkFolderLocation: String? = null,
|
||||
internal val logger: Logger = NopLogger
|
||||
)
|
||||
@@ -1,16 +0,0 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.util.dex.DexFile
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* The result of a patcher.
|
||||
* @param dexFiles The patched dex files.
|
||||
* @param doNotCompress List of relative paths to files to exclude from compressing.
|
||||
* @param resourceFile File containing resources that need to be extracted into the APK.
|
||||
*/
|
||||
data class PatcherResult(
|
||||
val dexFiles: List<DexFile>,
|
||||
val doNotCompress: List<String>? = null,
|
||||
val resourceFile: File?
|
||||
)
|
||||
@@ -1,28 +0,0 @@
|
||||
package app.revanced.patcher.annotation
|
||||
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
/**
|
||||
* Annotation to constrain a [Patch] or [MethodFingerprint] to compatible packages.
|
||||
* @param compatiblePackages A list of packages a [Patch] or [MethodFingerprint] is compatible with.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Compatibility(
|
||||
val compatiblePackages: Array<Package>,
|
||||
)
|
||||
|
||||
/**
|
||||
* Annotation to represent packages a patch can be compatible with.
|
||||
* @param name The package identifier name.
|
||||
* @param versions The versions of the package the [Patch] or [MethodFingerprint]is compatible with.
|
||||
*/
|
||||
@Target()
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Package(
|
||||
val name: String,
|
||||
val versions: Array<String>
|
||||
)
|
||||
@@ -1,38 +0,0 @@
|
||||
package app.revanced.patcher.annotation
|
||||
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
/**
|
||||
* Annotation to name a [Patch] or [MethodFingerprint].
|
||||
* @param name A suggestive name for the [Patch] or [MethodFingerprint].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Name(
|
||||
val name: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Annotation to describe a [Patch] or [MethodFingerprint].
|
||||
* @param description A description for the [Patch] or [MethodFingerprint].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Description(
|
||||
val description: String,
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Annotation to version a [Patch] or [MethodFingerprint].
|
||||
* @param version The version of a [Patch] or [MethodFingerprint].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Version(
|
||||
val version: String,
|
||||
)
|
||||
16
src/main/kotlin/app/revanced/patcher/cache/Cache.kt
vendored
Normal file
16
src/main/kotlin/app/revanced/patcher/cache/Cache.kt
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package app.revanced.patcher.cache
|
||||
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
|
||||
class Cache(
|
||||
val classes: List<ClassNode>,
|
||||
val methods: MethodMap
|
||||
)
|
||||
|
||||
class MethodMap : LinkedHashMap<String, PatchData>() {
|
||||
override fun get(key: String): PatchData {
|
||||
return super.get(key) ?: throw MethodNotFoundException("Method $key was not found in the method cache")
|
||||
}
|
||||
}
|
||||
|
||||
class MethodNotFoundException(s: String) : Exception(s)
|
||||
22
src/main/kotlin/app/revanced/patcher/cache/PatchData.kt
vendored
Normal file
22
src/main/kotlin/app/revanced/patcher/cache/PatchData.kt
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package app.revanced.patcher.cache
|
||||
|
||||
import app.revanced.patcher.resolver.MethodResolver
|
||||
import app.revanced.patcher.signature.Signature
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
data class PatchData(
|
||||
val declaringClass: ClassNode,
|
||||
val method: MethodNode,
|
||||
val scanData: PatternScanData
|
||||
) {
|
||||
@Suppress("Unused") // TODO(Sculas): remove this when we have coverage for this method.
|
||||
fun findParentMethod(signature: Signature): PatchData? {
|
||||
return MethodResolver.resolveMethod(declaringClass, signature)
|
||||
}
|
||||
}
|
||||
|
||||
data class PatternScanData(
|
||||
val startIndex: Int,
|
||||
val endIndex: Int
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
package app.revanced.patcher.data
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.impl.ResourceData
|
||||
|
||||
/**
|
||||
* Constraint interface for [BytecodeData] and [ResourceData]
|
||||
*/
|
||||
interface Data
|
||||
@@ -1,13 +0,0 @@
|
||||
package app.revanced.patcher.data
|
||||
|
||||
import brut.androlib.meta.MetaInfo
|
||||
|
||||
/**
|
||||
* Metadata about a package.
|
||||
*/
|
||||
class PackageMetadata {
|
||||
lateinit var packageName: String
|
||||
lateinit var packageVersion: String
|
||||
|
||||
internal val metaInfo: MetaInfo = MetaInfo()
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package app.revanced.patcher.data.impl
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.util.ProxyBackedClassList
|
||||
import app.revanced.patcher.util.method.MethodWalker
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
|
||||
class BytecodeData(
|
||||
internalClasses: MutableList<ClassDef>
|
||||
) : Data {
|
||||
val classes = ProxyBackedClassList(internalClasses)
|
||||
|
||||
/**
|
||||
* Find a class by a given class name.
|
||||
* @param className The name of the class.
|
||||
* @return A proxy for the first class that matches the class name.
|
||||
*/
|
||||
fun findClass(className: String) = findClass { it.type.contains(className) }
|
||||
|
||||
/**
|
||||
* Find a class by a given predicate.
|
||||
* @param predicate A predicate to match the class.
|
||||
* @return A proxy for the first class that matches the predicate.
|
||||
*/
|
||||
fun findClass(predicate: (ClassDef) -> Boolean) =
|
||||
// if we already proxied the class matching the predicate...
|
||||
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
||||
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||
classes.find(predicate)?.let { proxy(it) }
|
||||
|
||||
fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
||||
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
||||
if (proxy == null) {
|
||||
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
||||
this.classes.add(proxy)
|
||||
}
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
internal class MethodNotFoundException(s: String) : Exception(s)
|
||||
|
||||
internal inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
|
||||
for (element in this) {
|
||||
if (predicate(element)) {
|
||||
return element
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [MethodWalker] instance for the current [BytecodeData].
|
||||
* @param startMethod The method to start at.
|
||||
* @return A [MethodWalker] instance.
|
||||
*/
|
||||
fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker {
|
||||
return MethodWalker(this, startMethod)
|
||||
}
|
||||
|
||||
internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair<T, Int>? {
|
||||
for ((index, element) in this.withIndex()) {
|
||||
if (predicate(element)) {
|
||||
return element to index
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package app.revanced.patcher.data.impl
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import org.w3c.dom.Document
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
|
||||
class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<File> {
|
||||
operator fun get(path: String) = resourceCacheDirectory.resolve(path)
|
||||
val xmlEditor = XmlFileHolder()
|
||||
override fun iterator() = resourceCacheDirectory.walkTopDown().iterator()
|
||||
|
||||
inner class XmlFileHolder {
|
||||
operator fun get(path: String) = DomFileEditor(this@ResourceData[path])
|
||||
}
|
||||
}
|
||||
|
||||
class DomFileEditor internal constructor(private val domFile: File) : Closeable {
|
||||
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||
.parse(domFile).also(Document::normalize)
|
||||
|
||||
override fun close() = TransformerFactory.newInstance().newTransformer()
|
||||
.transform(DOMSource(file), StreamResult(domFile.outputStream()))
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.annotation.Compatibility
|
||||
import app.revanced.patcher.annotation.Description
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Recursively find a given annotation on a class.
|
||||
* @param targetAnnotation The annotation to find.
|
||||
* @return The annotation.
|
||||
*/
|
||||
private fun <T : Annotation> Class<*>.recursiveAnnotation(targetAnnotation: KClass<T>) =
|
||||
this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
||||
|
||||
|
||||
private fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
||||
targetAnnotation: Class<T>, traversed: MutableSet<Annotation>
|
||||
): T? {
|
||||
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
|
||||
|
||||
@Suppress("UNCHECKED_CAST") if (found != null) return found as T
|
||||
|
||||
for (annotation in this.annotations) {
|
||||
if (traversed.contains(annotation)) continue
|
||||
traversed.add(annotation)
|
||||
|
||||
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed)) ?: continue
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
object PatchExtensions {
|
||||
val Class<out Patch<Data>>.patchName: String
|
||||
get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||
val Class<out Patch<Data>>.version get() = recursiveAnnotation(Version::class)?.version
|
||||
val Class<out Patch<Data>>.include get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Patch::class)!!.include
|
||||
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
|
||||
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Dependencies::class)?.dependencies
|
||||
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||
}
|
||||
|
||||
object MethodFingerprintExtensions {
|
||||
val MethodFingerprint.name: String
|
||||
get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||
val MethodFingerprint.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
||||
val MethodFingerprint.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
||||
val MethodFingerprint.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||
val MethodFingerprint.matchingMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.MatchingMethod::class)
|
||||
val MethodFingerprint.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod::class)
|
||||
val MethodFingerprint.fuzzyScanThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.util.smali.toInstruction
|
||||
import app.revanced.patcher.util.smali.toInstructions
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.builder.BuilderInstruction
|
||||
import org.jf.dexlib2.builder.MutableMethodImplementation
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.iface.reference.MethodReference
|
||||
import org.jf.dexlib2.immutable.ImmutableMethod
|
||||
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
||||
import org.jf.dexlib2.util.MethodUtil
|
||||
import java.io.OutputStream
|
||||
|
||||
infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value
|
||||
infix fun Int.or(other: AccessFlags) = this or other.value
|
||||
|
||||
fun MutableMethodImplementation.addInstructions(index: Int, instructions: List<BuilderInstruction>) {
|
||||
for (i in instructions.lastIndex downTo 0) {
|
||||
this.addInstruction(index, instructions[i])
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) {
|
||||
for (i in instructions.lastIndex downTo 0) {
|
||||
this.replaceInstruction(index + i, instructions[i])
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) {
|
||||
for (i in count downTo 0) {
|
||||
this.removeInstruction(index + i)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare a method to another, considering constructors and parameters.
|
||||
* @param otherMethod The method to compare against.
|
||||
* @return True if the methods match given the conditions.
|
||||
*/
|
||||
fun Method.softCompareTo(
|
||||
otherMethod: MethodReference
|
||||
): Boolean {
|
||||
if (MethodUtil.isConstructor(this) && !parametersEqual(this.parameterTypes, otherMethod.parameterTypes))
|
||||
return false
|
||||
return this.name == otherMethod.name
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the method.
|
||||
* @param registerCount This parameter allows you to change the register count of the method.
|
||||
* This may be a positive or negative number.
|
||||
* @return The **immutable** cloned method. Call [toMutable] or [cloneMutable] to get a **mutable** copy.
|
||||
*/
|
||||
internal fun Method.clone(
|
||||
registerCount: Int = 0,
|
||||
): ImmutableMethod {
|
||||
val clonedImplementation = implementation?.let {
|
||||
ImmutableMethodImplementation(
|
||||
it.registerCount + registerCount,
|
||||
it.instructions,
|
||||
it.tryBlocks,
|
||||
it.debugItems,
|
||||
)
|
||||
}
|
||||
return ImmutableMethod(
|
||||
returnType,
|
||||
name,
|
||||
parameters,
|
||||
returnType,
|
||||
accessFlags,
|
||||
annotations,
|
||||
hiddenApiRestrictions,
|
||||
clonedImplementation
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a smali instruction to the method.
|
||||
* @param instruction The smali instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(instruction: String) =
|
||||
this.implementation!!.addInstruction(instruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Add a smali instruction to the method.
|
||||
* @param index The index to insert the instruction at.
|
||||
* @param instruction The smali instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(index: Int, instruction: String) =
|
||||
this.implementation!!.addInstruction(index, instruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Replace a smali instruction within the method.
|
||||
* @param index The index to replace the instruction at.
|
||||
* @param instruction The smali instruction to place.
|
||||
*/
|
||||
fun MutableMethod.replaceInstruction(index: Int, instruction: String) =
|
||||
this.implementation!!.replaceInstruction(index, instruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Remove a smali instruction within the method.
|
||||
* @param index The index to delete the instruction at.
|
||||
*/
|
||||
fun MutableMethod.removeInstruction(index: Int) =
|
||||
this.implementation!!.removeInstruction(index)
|
||||
|
||||
/**
|
||||
* Add smali instructions to the method.
|
||||
* @param index The index to insert the instructions at.
|
||||
* @param instructions The smali instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(index: Int, instructions: String) =
|
||||
this.implementation!!.addInstructions(index, instructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Replace smali instructions within the method.
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param instructions The smali instructions to place.
|
||||
*/
|
||||
fun MutableMethod.replaceInstructions(index: Int, instructions: String) =
|
||||
this.implementation!!.replaceInstructions(index, instructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Remove smali instructions from the method.
|
||||
* @param index The index to remove the instructions at.
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethod.removeInstructions(index: Int, count: Int) =
|
||||
this.implementation!!.removeInstructions(index, count)
|
||||
|
||||
/**
|
||||
* Clones the method.
|
||||
* @param registerCount This parameter allows you to change the register count of the method.
|
||||
* This may be a positive or negative number.
|
||||
* @return The **mutable** cloned method. Call [clone] to get an **immutable** copy.
|
||||
*/
|
||||
internal fun Method.cloneMutable(
|
||||
registerCount: Int = 0,
|
||||
) = clone(registerCount).toMutable()
|
||||
|
||||
// FIXME: also check the order of parameters as different order equals different method overload
|
||||
internal fun parametersEqual(
|
||||
parameters1: Iterable<CharSequence>,
|
||||
parameters2: Iterable<CharSequence>
|
||||
): Boolean {
|
||||
return parameters1.count() == parameters2.count() && parameters1.all { parameter ->
|
||||
parameters2.any {
|
||||
it.startsWith(
|
||||
parameter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val nullOutputStream: OutputStream =
|
||||
object : OutputStream() {
|
||||
override fun write(b: Int) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be used to parse a list of parameters represented by their first letter,
|
||||
* or in the case of arrays prefixed with an unspecified amount of '[' character.
|
||||
*/
|
||||
internal fun String.parseParameters(): List<String> {
|
||||
val parameters = mutableListOf<String>()
|
||||
var parameter = ""
|
||||
for (char in this.toCharArray()) {
|
||||
parameter += char
|
||||
if (char == '[') continue
|
||||
|
||||
parameters.add(parameter)
|
||||
parameter = ""
|
||||
}
|
||||
return parameters
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package app.revanced.patcher.fingerprint
|
||||
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
|
||||
/**
|
||||
* A ReVanced fingerprint.
|
||||
* Can be a [MethodFingerprint].
|
||||
*/
|
||||
interface Fingerprint
|
||||
@@ -1,32 +0,0 @@
|
||||
package app.revanced.patcher.fingerprint.method.annotation
|
||||
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
|
||||
/**
|
||||
* Annotations for a method which matches to a [MethodFingerprint].
|
||||
* @param definingClass The defining class name of the method.
|
||||
* @param name A suggestive name for the method which the [MethodFingerprint] was created for.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class MatchingMethod(
|
||||
val definingClass: String = "L<unspecified-class>;",
|
||||
val name: String = "<unspecified-method>"
|
||||
)
|
||||
|
||||
/**
|
||||
* Annotations to scan a pattern [MethodFingerprint] with fuzzy algorithm.
|
||||
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class FuzzyPatternScanMethod(
|
||||
val threshold: Int = 1
|
||||
)
|
||||
|
||||
/**
|
||||
* Annotations to scan a pattern [MethodFingerprint] directly.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class DirectPatternScanMethod
|
||||
@@ -1,98 +0,0 @@
|
||||
package app.revanced.patcher.fingerprint.method.impl
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.impl.MethodNotFoundException
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
|
||||
import app.revanced.patcher.extensions.softCompareTo
|
||||
import app.revanced.patcher.fingerprint.Fingerprint
|
||||
import app.revanced.patcher.fingerprint.method.utils.MethodFingerprintUtils
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
|
||||
/**
|
||||
* Represents the [MethodFingerprint] for a method.
|
||||
* @param returnType The return type of the method.
|
||||
* @param access The access flags of the method.
|
||||
* @param parameters The parameters of the method.
|
||||
* @param opcodes The list of opcodes of the method.
|
||||
* @param strings A list of strings which a method contains.
|
||||
* @param customFingerprint A custom condition for this fingerprint.
|
||||
* A `null` opcode is equals to an unknown opcode.
|
||||
*/
|
||||
abstract class MethodFingerprint(
|
||||
internal val returnType: String?,
|
||||
internal val access: Int?,
|
||||
internal val parameters: Iterable<String>?,
|
||||
internal val opcodes: Iterable<Opcode?>?,
|
||||
internal val strings: Iterable<String>? = null,
|
||||
internal val customFingerprint: ((methodDef: Method) -> Boolean)? = null
|
||||
) : Fingerprint {
|
||||
/**
|
||||
* The result of the [MethodFingerprint] the [Method].
|
||||
* @throws MethodNotFoundException If the resolution of the [Method] has not happened.
|
||||
*/
|
||||
var result: MethodFingerprintResult? = null
|
||||
get() = field ?: throw Exception("${this.name} has not been resolved yet.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the result of a [MethodFingerprintUtils].
|
||||
* @param method The matching method.
|
||||
* @param classDef The [ClassDef] that contains the matching [method].
|
||||
* @param patternScanResult Opcodes pattern scan result.
|
||||
* @param data The [BytecodeData] this [MethodFingerprintResult] is attached to, to create proxies.
|
||||
*/
|
||||
data class MethodFingerprintResult(
|
||||
val method: Method,
|
||||
val classDef: ClassDef,
|
||||
val patternScanResult: PatternScanResult?,
|
||||
internal val data: BytecodeData
|
||||
) {
|
||||
/**
|
||||
* Returns a mutable clone of [classDef]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [classDef] where possible.
|
||||
*/
|
||||
val mutableClass by lazy { data.proxy(classDef).resolve() }
|
||||
|
||||
/**
|
||||
* Returns a mutable clone of [method]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [method] where possible.
|
||||
*/
|
||||
val mutableMethod by lazy {
|
||||
mutableClass.methods.first {
|
||||
it.softCompareTo(this.method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a pattern scan.
|
||||
* @param startIndex The start index of the instructions where to which this pattern matches.
|
||||
* @param endIndex The end index of the instructions where to which this pattern matches.
|
||||
* @param warnings A list of warnings considering this [PatternScanResult].
|
||||
*/
|
||||
data class PatternScanResult(
|
||||
val startIndex: Int,
|
||||
val endIndex: Int,
|
||||
var warnings: List<Warning>? = null
|
||||
) {
|
||||
/**
|
||||
* Represents warnings of the pattern scan.
|
||||
* @param correctOpcode The opcode the instruction list has.
|
||||
* @param wrongOpcode The opcode the pattern list of the signature currently has.
|
||||
* @param instructionIndex The index of the opcode relative to the instruction list.
|
||||
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
|
||||
*/
|
||||
data class Warning(
|
||||
val correctOpcode: Opcode,
|
||||
val wrongOpcode: Opcode,
|
||||
val instructionIndex: Int,
|
||||
val patternIndex: Int,
|
||||
)
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package app.revanced.patcher.fingerprint.method.utils
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold
|
||||
import app.revanced.patcher.extensions.parametersEqual
|
||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult
|
||||
import app.revanced.patcher.fingerprint.method.impl.PatternScanResult
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.iface.instruction.Instruction
|
||||
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import org.jf.dexlib2.iface.reference.StringReference
|
||||
|
||||
/**
|
||||
* Utility class for [MethodFingerprint]
|
||||
*/
|
||||
object MethodFingerprintUtils {
|
||||
/**
|
||||
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
||||
* @param context The classes on which to resolve the [MethodFingerprint].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun Iterable<MethodFingerprint>.resolve(forData: BytecodeData, context: Iterable<ClassDef>) {
|
||||
for (fingerprint in this) // For each fingerprint
|
||||
classes@ for (classDef in context) // search through all classes for the fingerprint
|
||||
if (fingerprint.resolve(forData, classDef))
|
||||
break@classes // if the resolution succeeded, continue with the next fingerprint
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a [MethodFingerprint] against a [ClassDef].
|
||||
* @param context The class on which to resolve the [MethodFingerprint].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: ClassDef): Boolean {
|
||||
for (method in context.methods)
|
||||
if (this.resolve(forData, method, context))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a [MethodFingerprint] against a [Method].
|
||||
* @param context The context on which to resolve the [MethodFingerprint].
|
||||
* @param classDef The class of the matching [Method].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: Method, classDef: ClassDef): Boolean {
|
||||
val methodFingerprint = this
|
||||
|
||||
if (methodFingerprint.returnType != null && !context.returnType.startsWith(methodFingerprint.returnType))
|
||||
return false
|
||||
|
||||
if (methodFingerprint.access != null && methodFingerprint.access != context.accessFlags)
|
||||
return false
|
||||
|
||||
|
||||
if (methodFingerprint.parameters != null && !parametersEqual(
|
||||
methodFingerprint.parameters, // TODO: parseParameters()
|
||||
context.parameterTypes
|
||||
)
|
||||
) return false
|
||||
|
||||
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(context))
|
||||
return false
|
||||
|
||||
if (methodFingerprint.strings != null) {
|
||||
val implementation = context.implementation ?: return false
|
||||
|
||||
val stringsList = methodFingerprint.strings.toMutableList()
|
||||
|
||||
implementation.instructions.forEach { instruction ->
|
||||
if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEach
|
||||
|
||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||
val index = stringsList.indexOfFirst { it == string }
|
||||
if (index != -1) stringsList.removeAt(index)
|
||||
}
|
||||
|
||||
if (stringsList.isNotEmpty()) return false
|
||||
}
|
||||
|
||||
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
||||
context.implementation?.instructions ?: return false
|
||||
|
||||
context.patternScan(methodFingerprint) ?: return false
|
||||
} else null
|
||||
|
||||
methodFingerprint.result = MethodFingerprintResult(context, classDef, patternScanResult, forData)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun Method.patternScan(
|
||||
fingerprint: MethodFingerprint
|
||||
): PatternScanResult? {
|
||||
val instructions = this.implementation!!.instructions
|
||||
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyScanThreshold
|
||||
|
||||
val pattern = fingerprint.opcodes!!
|
||||
val instructionLength = instructions.count()
|
||||
val patternLength = pattern.count()
|
||||
|
||||
for (index in 0 until instructionLength) {
|
||||
var patternIndex = 0
|
||||
var threshold = fingerprintFuzzyPatternScanThreshold
|
||||
|
||||
while (index + patternIndex < instructionLength) {
|
||||
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
||||
val patternOpcode = pattern.elementAt(patternIndex)
|
||||
|
||||
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
||||
// reaching maximum threshold (0) means,
|
||||
// the pattern does not match to the current instructions
|
||||
if (threshold-- == 0) break
|
||||
}
|
||||
|
||||
if (patternIndex < patternLength - 1) {
|
||||
// if the entire pattern has not been scanned yet
|
||||
// continue the scan
|
||||
patternIndex++
|
||||
continue
|
||||
}
|
||||
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
|
||||
val result = PatternScanResult(index, index + patternIndex)
|
||||
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
|
||||
result.warnings = result.createWarnings(pattern, instructions)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun PatternScanResult.createWarnings(
|
||||
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
|
||||
) = buildList {
|
||||
for ((patternIndex, instructionIndex) in (this@createWarnings.startIndex until this@createWarnings.endIndex).withIndex()) {
|
||||
val originalOpcode = instructions.elementAt(instructionIndex).opcode
|
||||
val patternOpcode = pattern.elementAt(patternIndex)
|
||||
|
||||
if (patternOpcode == null || patternOpcode.ordinal == originalOpcode.ordinal) continue
|
||||
|
||||
this.add(PatternScanResult.Warning(originalOpcode, patternOpcode, instructionIndex, patternIndex))
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun ClassDef.component1() = this
|
||||
private operator fun ClassDef.component2() = this.methods
|
||||
@@ -1,8 +0,0 @@
|
||||
package app.revanced.patcher.logging
|
||||
|
||||
interface Logger {
|
||||
fun error(msg: String) {}
|
||||
fun warn(msg: String) {}
|
||||
fun info(msg: String) {}
|
||||
fun trace(msg: String) {}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package app.revanced.patcher.logging.impl
|
||||
|
||||
import app.revanced.patcher.logging.Logger
|
||||
|
||||
object NopLogger : Logger
|
||||
@@ -1,18 +1,7 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||
import app.revanced.patcher.cache.Cache
|
||||
|
||||
|
||||
/**
|
||||
* A ReVanced patch.
|
||||
* Can either be a [ResourcePatch] or a [BytecodePatch].
|
||||
*/
|
||||
abstract class Patch<out T : Data> {
|
||||
|
||||
/**
|
||||
* The main function of the [Patch] which the patcher will call.
|
||||
*/
|
||||
abstract fun execute(data: @UnsafeVariance T): PatchResult
|
||||
}
|
||||
abstract class Patch(val patchName: String) {
|
||||
abstract fun execute(cache: Cache): PatchResult
|
||||
}
|
||||
|
||||
@@ -24,12 +24,10 @@ interface PatchResult {
|
||||
}
|
||||
}
|
||||
|
||||
class PatchResultError(
|
||||
errorMessage: String?, cause: Exception?
|
||||
) : Exception(errorMessage, cause), PatchResult {
|
||||
constructor(errorMessage: String) : this(errorMessage, null)
|
||||
constructor(cause: Exception) : this(cause.message, cause)
|
||||
|
||||
class PatchResultError(private val errorMessage: String) : PatchResult {
|
||||
fun errorMessage(): String {
|
||||
return errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
class PatchResultSuccess : PatchResult
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.patcher.patch.annotations
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Annotation to mark a Class as a patch.
|
||||
* @param include If false, the patch should be treated as optional by default.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Patch(val include: Boolean = true)
|
||||
|
||||
/**
|
||||
* Annotation for dependencies of [Patch]es .
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Dependencies(
|
||||
val dependencies: Array<KClass<out Patch<Data>>> = []
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
package app.revanced.patcher.patch.impl
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
/**
|
||||
* Bytecode patch for the Patcher.
|
||||
* @param fingerprints A list of [MethodFingerprint] this patch relies on.
|
||||
*/
|
||||
abstract class BytecodePatch(
|
||||
internal val fingerprints: Iterable<MethodFingerprint>
|
||||
) : Patch<BytecodeData>()
|
||||
@@ -1,9 +0,0 @@
|
||||
package app.revanced.patcher.patch.impl
|
||||
|
||||
import app.revanced.patcher.data.impl.ResourceData
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
/**
|
||||
* Resource patch for the Patcher.
|
||||
*/
|
||||
abstract class ResourcePatch : Patch<ResourceData>()
|
||||
159
src/main/kotlin/app/revanced/patcher/resolver/MethodResolver.kt
Normal file
159
src/main/kotlin/app/revanced/patcher/resolver/MethodResolver.kt
Normal file
@@ -0,0 +1,159 @@
|
||||
package app.revanced.patcher.resolver
|
||||
|
||||
import mu.KotlinLogging
|
||||
import app.revanced.patcher.cache.MethodMap
|
||||
import app.revanced.patcher.cache.PatchData
|
||||
import app.revanced.patcher.cache.PatternScanData
|
||||
import app.revanced.patcher.signature.Signature
|
||||
import app.revanced.patcher.util.ExtraTypes
|
||||
import org.objectweb.asm.Type
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.InsnList
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
private val logger = KotlinLogging.logger("MethodResolver")
|
||||
|
||||
internal class MethodResolver(private val classList: List<ClassNode>, private val signatures: Array<Signature>) {
|
||||
fun resolve(): MethodMap {
|
||||
val methodMap = MethodMap()
|
||||
|
||||
for ((classNode, methods) in classList) {
|
||||
for (method in methods) {
|
||||
for (signature in signatures) {
|
||||
if (methodMap.containsKey(signature.name)) { // method already found for this sig
|
||||
logger.debug { "Sig ${signature.name} already found, skipping." }
|
||||
continue
|
||||
}
|
||||
logger.debug { "Resolving sig ${signature.name}: ${classNode.name} / ${method.name}" }
|
||||
val (r, sr) = cmp(method, signature)
|
||||
if (!r || sr == null) {
|
||||
logger.debug { "Compare result for sig ${signature.name} has failed!" }
|
||||
continue
|
||||
}
|
||||
logger.debug { "Method for sig ${signature.name} found!" }
|
||||
methodMap[signature.name] = PatchData(
|
||||
classNode,
|
||||
method,
|
||||
PatternScanData(
|
||||
// sadly we cannot create contracts for a data class, so we must assert
|
||||
sr.startIndex!!,
|
||||
sr.endIndex!!
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (signature in signatures) {
|
||||
if (methodMap.containsKey(signature.name)) continue
|
||||
logger.error { "Could not find method for sig ${signature.name}!" }
|
||||
}
|
||||
|
||||
return methodMap
|
||||
}
|
||||
|
||||
// These functions do not require the constructor values, so they can be static.
|
||||
companion object {
|
||||
fun resolveMethod(classNode: ClassNode, signature: Signature): PatchData? {
|
||||
for (method in classNode.methods) {
|
||||
val (r, sr) = cmp(method, signature)
|
||||
if (!r || sr == null) continue
|
||||
return PatchData(
|
||||
classNode,
|
||||
method,
|
||||
PatternScanData(0, 0) // opcode list is always ignored.
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun cmp(method: MethodNode, signature: Signature): Pair<Boolean, ScanResult?> {
|
||||
signature.returns?.let { _ ->
|
||||
val methodReturns = Type.getReturnType(method.desc).convertObject()
|
||||
if (signature.returns != methodReturns) {
|
||||
logger.debug {
|
||||
"""
|
||||
Comparing sig ${signature.name}: invalid return type:
|
||||
expected ${signature.returns},
|
||||
got $methodReturns
|
||||
""".trimIndent()
|
||||
}
|
||||
return@cmp false to null
|
||||
}
|
||||
}
|
||||
|
||||
signature.accessors?.let { _ ->
|
||||
if (signature.accessors != method.access) {
|
||||
logger.debug {
|
||||
"""
|
||||
Comparing sig ${signature.name}: invalid accessors:
|
||||
expected ${signature.accessors},
|
||||
got ${method.access}
|
||||
""".trimIndent()
|
||||
}
|
||||
return@cmp false to null
|
||||
}
|
||||
}
|
||||
|
||||
signature.parameters?.let { _ ->
|
||||
val parameters = Type.getArgumentTypes(method.desc).convertObjects()
|
||||
if (!signature.parameters.contentEquals(parameters)) {
|
||||
logger.debug {
|
||||
"""
|
||||
Comparing sig ${signature.name}: invalid parameter types:
|
||||
expected ${signature.parameters.joinToString()}},
|
||||
got ${parameters.joinToString()}
|
||||
""".trimIndent()
|
||||
}
|
||||
return@cmp false to null
|
||||
}
|
||||
}
|
||||
|
||||
signature.opcodes?.let { _ ->
|
||||
val result = method.instructions.scanFor(signature.opcodes)
|
||||
if (!result.found) {
|
||||
logger.debug { "Comparing sig ${signature.name}: invalid opcode pattern" }
|
||||
return@cmp false to null
|
||||
}
|
||||
return@cmp true to result
|
||||
}
|
||||
|
||||
return true to ScanResult(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun ClassNode.component1(): ClassNode {
|
||||
return this
|
||||
}
|
||||
|
||||
private operator fun ClassNode.component2(): List<MethodNode> {
|
||||
return this.methods
|
||||
}
|
||||
|
||||
private fun InsnList.scanFor(pattern: IntArray): ScanResult {
|
||||
for (i in 0 until this.size()) {
|
||||
var occurrence = 0
|
||||
while (i + occurrence < this.size()) {
|
||||
if (this[i + occurrence].opcode != pattern[occurrence]) break
|
||||
if (++occurrence >= pattern.size) {
|
||||
val current = i + occurrence
|
||||
return ScanResult(true, current - pattern.size, current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ScanResult(false)
|
||||
}
|
||||
|
||||
private fun Type.convertObject(): Type {
|
||||
return when (this.sort) {
|
||||
Type.OBJECT -> ExtraTypes.Any
|
||||
Type.ARRAY -> ExtraTypes.ArrayAny
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
private fun Array<Type>.convertObjects(): Array<Type> {
|
||||
return this.map { it.convertObject() }.toTypedArray()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package app.revanced.patcher.resolver
|
||||
|
||||
internal data class ScanResult(
|
||||
val found: Boolean,
|
||||
val startIndex: Int? = 0,
|
||||
val endIndex: Int? = 0
|
||||
)
|
||||
27
src/main/kotlin/app/revanced/patcher/signature/Signature.kt
Normal file
27
src/main/kotlin/app/revanced/patcher/signature/Signature.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
package app.revanced.patcher.signature
|
||||
|
||||
import org.objectweb.asm.Type
|
||||
|
||||
/**
|
||||
* An ASM signature list for the Patcher.
|
||||
*
|
||||
* @param name The name of the method.
|
||||
* Do not use the actual method name, instead try to guess what the method name originally was.
|
||||
* If you are unable to guess a method name, doing something like "patch-name-1" is fine too.
|
||||
* For example: "override-codec-1".
|
||||
* This method name will be mapped to the method matching the signature.
|
||||
* Even though this is technically not needed for the `findParentMethod` method,
|
||||
* it is still recommended giving the method a name, so it can be identified easily.
|
||||
* @param returns The return type/signature of the method.
|
||||
* @param accessors The accessors of the method.
|
||||
* @param parameters The parameter types of the method.
|
||||
* @param opcodes The opcode pattern of the method, used to find the method by pattern scanning.
|
||||
*/
|
||||
@Suppress("ArrayInDataClass")
|
||||
data class Signature(
|
||||
val name: String,
|
||||
val returns: Type?,
|
||||
val accessors: Int?,
|
||||
val parameters: Array<Type>?,
|
||||
val opcodes: IntArray?
|
||||
)
|
||||
12
src/main/kotlin/app/revanced/patcher/util/ExtraTypes.kt
Normal file
12
src/main/kotlin/app/revanced/patcher/util/ExtraTypes.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import org.objectweb.asm.Type
|
||||
|
||||
object ExtraTypes {
|
||||
/**
|
||||
* Any object type.
|
||||
* Should be used instead of types such as: "Ljava/lang/String;"
|
||||
*/
|
||||
val Any: Type = Type.getType(Object::class.java)
|
||||
val ArrayAny: Type = Type.getType(Array<Any>::class.java)
|
||||
}
|
||||
97
src/main/kotlin/app/revanced/patcher/util/Io.kt
Normal file
97
src/main/kotlin/app/revanced/patcher/util/Io.kt
Normal file
@@ -0,0 +1,97 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.jar.JarEntry
|
||||
import java.util.jar.JarInputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
internal class Io(
|
||||
private val input: InputStream,
|
||||
private val output: OutputStream,
|
||||
private val classes: MutableList<ClassNode>
|
||||
) {
|
||||
private val bufferedInputStream = BufferedInputStream(input)
|
||||
|
||||
fun readFromJar() {
|
||||
bufferedInputStream.mark(Integer.MAX_VALUE)
|
||||
// create a BufferedInputStream in order to read the input stream again when calling saveAsJar(..)
|
||||
val jis = JarInputStream(bufferedInputStream)
|
||||
|
||||
// read all entries from the input stream
|
||||
// we use JarEntry because we only read .class files
|
||||
lateinit var jarEntry: JarEntry
|
||||
while (jis.nextJarEntry.also { if (it != null) jarEntry = it } != null) {
|
||||
// if the current entry ends with .class (indicating a java class file), add it to our list of classes to return
|
||||
if (jarEntry.name.endsWith(".class")) {
|
||||
// create a new ClassNode
|
||||
val classNode = ClassNode()
|
||||
// read the bytes with a ClassReader into the ClassNode
|
||||
ClassReader(jis.readBytes()).accept(classNode, ClassReader.EXPAND_FRAMES)
|
||||
// add it to our list
|
||||
classes.add(classNode)
|
||||
}
|
||||
|
||||
// finally, close the entry
|
||||
jis.closeEntry()
|
||||
}
|
||||
|
||||
// at last reset the buffered input stream
|
||||
bufferedInputStream.reset()
|
||||
}
|
||||
|
||||
fun saveAsJar() {
|
||||
val jis = ZipInputStream(bufferedInputStream)
|
||||
val jos = ZipOutputStream(output)
|
||||
val classReaders = mutableMapOf<String, ClassReader>()
|
||||
|
||||
// first write all non .class zip entries from the original input stream to the output stream
|
||||
// we read it first to close the input stream as fast as possible
|
||||
// TODO(oSumAtrIX): There is currently no way to remove non .class files.
|
||||
lateinit var zipEntry: ZipEntry
|
||||
while (jis.nextEntry.also { if (it != null) zipEntry = it } != null) {
|
||||
if (zipEntry.name.endsWith(".class")) {
|
||||
classReaders[zipEntry.name] = ClassReader(jis.readBytes())
|
||||
continue
|
||||
}
|
||||
|
||||
// create a new zipEntry and write the contents of the zipEntry to the output stream and close it
|
||||
jos.putNextEntry(ZipEntry(zipEntry))
|
||||
jos.write(jis.readBytes())
|
||||
jos.closeEntry()
|
||||
}
|
||||
|
||||
// finally, close the input stream
|
||||
jis.close()
|
||||
bufferedInputStream.close()
|
||||
input.close()
|
||||
|
||||
// now write all the patched classes to the output stream
|
||||
for (patchedClass in classes) {
|
||||
// create a new entry of the patched class
|
||||
val name = patchedClass.name + ".class"
|
||||
jos.putNextEntry(JarEntry(name))
|
||||
|
||||
// parse the patched class to a byte array and write it to the output stream
|
||||
val cw = ClassWriter(
|
||||
classReaders[name]!!,
|
||||
ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS
|
||||
)
|
||||
patchedClass.accept(cw)
|
||||
jos.write(cw.toByteArray())
|
||||
|
||||
// close the newly created jar entry
|
||||
jos.closeEntry()
|
||||
}
|
||||
|
||||
// finally, close the rest of the streams
|
||||
jos.close()
|
||||
output.close()
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
internal class ListBackedSet<E>(private val list: MutableList<E>) : MutableSet<E> {
|
||||
override val size get() = list.size
|
||||
override fun add(element: E) = list.add(element)
|
||||
override fun addAll(elements: Collection<E>) = list.addAll(elements)
|
||||
override fun clear() = list.clear()
|
||||
override fun iterator() = list.listIterator()
|
||||
override fun remove(element: E) = list.remove(element)
|
||||
override fun removeAll(elements: Collection<E>) = list.removeAll(elements)
|
||||
override fun retainAll(elements: Collection<E>) = list.retainAll(elements)
|
||||
override fun contains(element: E) = list.contains(element)
|
||||
override fun containsAll(elements: Collection<E>) = list.containsAll(elements)
|
||||
override fun isEmpty() = list.isEmpty()
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
|
||||
class ProxyBackedClassList(internal val internalClasses: MutableList<ClassDef>) : List<ClassDef> {
|
||||
private val internalProxies = mutableListOf<ClassProxy>()
|
||||
internal val proxies: List<ClassProxy> = internalProxies
|
||||
|
||||
fun add(classDef: ClassDef) = internalClasses.add(classDef)
|
||||
fun add(classProxy: ClassProxy) = internalProxies.add(classProxy)
|
||||
|
||||
/**
|
||||
* Apply all resolved classes into [internalClasses] and clean the [proxies] list.
|
||||
*/
|
||||
internal fun applyProxies() {
|
||||
// FIXME: check if this could cause issues when multiple patches use the same proxy
|
||||
internalProxies.removeIf { proxy ->
|
||||
// if the proxy is unused, keep it in the list
|
||||
if (!proxy.proxyUsed) return@removeIf false
|
||||
|
||||
// if it has been used, replace the internal class which it proxied
|
||||
val index = internalClasses.indexOfFirst { it.type == proxy.immutableClass.type }
|
||||
internalClasses[index] = proxy.mutatedClass
|
||||
|
||||
// return true to remove it from the proxies list
|
||||
return@removeIf true
|
||||
}
|
||||
}
|
||||
|
||||
override val size get() = internalClasses.size
|
||||
override fun contains(element: ClassDef) = internalClasses.contains(element)
|
||||
override fun containsAll(elements: Collection<ClassDef>) = internalClasses.containsAll(elements)
|
||||
override fun get(index: Int) = internalClasses[index]
|
||||
override fun indexOf(element: ClassDef) = internalClasses.indexOf(element)
|
||||
override fun isEmpty() = internalClasses.isEmpty()
|
||||
override fun iterator() = internalClasses.iterator()
|
||||
override fun lastIndexOf(element: ClassDef) = internalClasses.lastIndexOf(element)
|
||||
override fun listIterator() = internalClasses.listIterator()
|
||||
override fun listIterator(index: Int) = internalClasses.listIterator(index)
|
||||
override fun subList(fromIndex: Int, toIndex: Int) = internalClasses.subList(fromIndex, toIndex)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package app.revanced.patcher.util.dex
|
||||
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Wrapper for dex files.
|
||||
* @param name The original name of the dex file.
|
||||
* @param dexFileInputStream The dex file as [InputStream].
|
||||
*/
|
||||
data class DexFile(val name: String, val dexFileInputStream: InputStream)
|
||||
@@ -1,55 +0,0 @@
|
||||
package app.revanced.patcher.util.method
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.impl.MethodNotFoundException
|
||||
import app.revanced.patcher.extensions.softCompareTo
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import org.jf.dexlib2.Format
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import org.jf.dexlib2.iface.reference.MethodReference
|
||||
import org.jf.dexlib2.util.Preconditions
|
||||
|
||||
/**
|
||||
* Find a method from another method via instruction offsets.
|
||||
* @param bytecodeData The bytecodeData to use when resolving the next method reference.
|
||||
* @param currentMethod The method to start from.
|
||||
*/
|
||||
class MethodWalker internal constructor(
|
||||
private val bytecodeData: BytecodeData,
|
||||
private var currentMethod: Method
|
||||
) {
|
||||
/**
|
||||
* Get the method which was walked last.
|
||||
* It is possible to cast this method to a [MutableMethod], if the method has been walked mutably.
|
||||
*/
|
||||
fun getMethod(): Method {
|
||||
return currentMethod
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk to a method defined at the offset in the instruction list of the current method.
|
||||
* @param offset The offset of the instruction. This instruction must be of format 35c.
|
||||
* @param walkMutable If this is true, the class of the method will be resolved mutably.
|
||||
* The current method will be mutable.
|
||||
*/
|
||||
fun nextMethod(offset: Int, walkMutable: Boolean = false): MethodWalker {
|
||||
currentMethod.implementation?.instructions?.let { instructions ->
|
||||
val instruction = instructions.elementAt(offset)
|
||||
|
||||
Preconditions.checkFormat(instruction.opcode, Format.Format35c)
|
||||
|
||||
val newMethod = (instruction as Instruction35c).reference as MethodReference
|
||||
val proxy = bytecodeData.findClass(newMethod.definingClass)!!
|
||||
|
||||
val methods = if (walkMutable) proxy.resolve().methods else proxy.immutableClass.methods
|
||||
currentMethod = methods.first { it ->
|
||||
return@first it.softCompareTo(newMethod)
|
||||
}
|
||||
return this
|
||||
}
|
||||
throw MethodNotFoundException("This method can not be walked at offset $offset inside the method ${currentMethod.name}")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package app.revanced.patcher.util.patch.base
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* @param patchBundlePath The path to the patch bundle.
|
||||
*/
|
||||
abstract class PatchBundle(patchBundlePath: String) : File(patchBundlePath) {
|
||||
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
||||
classNames.forEach { className ->
|
||||
val clazz = classLoader.loadClass(className)
|
||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
|
||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Data>>)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package app.revanced.patcher.util.patch.implementation
|
||||
|
||||
import app.revanced.patcher.util.patch.base.PatchBundle
|
||||
import app.revanced.patcher.util.patch.util.StringIterator
|
||||
import org.jf.dexlib2.DexFileFactory
|
||||
|
||||
/**
|
||||
* A patch bundle of the ReVanced [DexPatchBundle] format.
|
||||
* @param patchBundlePath The path to a patch bundle of dex format.
|
||||
* @param dexClassLoader The dex class loader.
|
||||
*/
|
||||
class DexPatchBundle(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
|
||||
fun loadPatches() = loadPatches(dexClassLoader,
|
||||
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
|
||||
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||
})
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package app.revanced.patcher.util.patch.implementation
|
||||
|
||||
import app.revanced.patcher.util.patch.base.PatchBundle
|
||||
import app.revanced.patcher.util.patch.util.StringIterator
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarFile
|
||||
|
||||
/**
|
||||
* A patch bundle of the ReVanced [JarPatchBundle] format.
|
||||
* @param patchBundlePath The path to the patch bundle.
|
||||
*/
|
||||
class JarPatchBundle(patchBundlePath: String) : PatchBundle(patchBundlePath) {
|
||||
fun loadPatches() = loadPatches(
|
||||
URLClassLoader(
|
||||
arrayOf(this.toURI().toURL()),
|
||||
Thread.currentThread().contextClassLoader // TODO: find out why this is required
|
||||
),
|
||||
StringIterator(
|
||||
JarFile(this)
|
||||
.entries()
|
||||
.toList() // TODO: find a cleaner solution than that to filter non class files
|
||||
.filter {
|
||||
it.name.endsWith(".class") && !it.name.contains("$")
|
||||
}
|
||||
.iterator()
|
||||
) {
|
||||
it.realName.replace('/', '.').replace(".class", "")
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package app.revanced.patcher.util.patch.util
|
||||
|
||||
internal class StringIterator<T, I : Iterator<T>>(
|
||||
private val iterator: I,
|
||||
private val _next: (T) -> String
|
||||
) : Iterator<String> {
|
||||
override fun hasNext() = iterator.hasNext()
|
||||
|
||||
override fun next() = _next(iterator.next())
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
|
||||
/**
|
||||
* A proxy class for a [ClassDef].
|
||||
*
|
||||
* A class proxy simply holds a reference to the original class
|
||||
* and allocates a mutable clone for the original class if needed.
|
||||
* @param immutableClass The class to proxy
|
||||
*/
|
||||
class ClassProxy(
|
||||
val immutableClass: ClassDef,
|
||||
) {
|
||||
internal var proxyUsed = false
|
||||
internal lateinit var mutatedClass: MutableClass
|
||||
|
||||
init {
|
||||
// in the instance, that a [MutableClass] is being proxied,
|
||||
// do not create an additional clone and reuse the [MutableClass] instance
|
||||
if (immutableClass is MutableClass) {
|
||||
mutatedClass = immutableClass
|
||||
proxyUsed = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates and returns a mutable clone of the original class.
|
||||
* A patch should always use the original immutable class reference
|
||||
* to avoid unnecessary allocations for the mutable class.
|
||||
* @return A mutable clone of the original class.
|
||||
*/
|
||||
fun resolve(): MutableClass {
|
||||
if (!proxyUsed) {
|
||||
proxyUsed = true
|
||||
mutatedClass = MutableClass(immutableClass)
|
||||
}
|
||||
return mutatedClass
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
|
||||
import org.jf.dexlib2.base.BaseAnnotation
|
||||
import org.jf.dexlib2.iface.Annotation
|
||||
|
||||
class MutableAnnotation(annotation: Annotation) : BaseAnnotation() {
|
||||
private val visibility = annotation.visibility
|
||||
private val type = annotation.type
|
||||
private val _elements by lazy { annotation.elements.map { element -> element.toMutable() }.toMutableSet() }
|
||||
|
||||
override fun getType(): String {
|
||||
return type
|
||||
}
|
||||
|
||||
override fun getElements(): MutableSet<MutableAnnotationElement> {
|
||||
return _elements
|
||||
}
|
||||
|
||||
override fun getVisibility(): Int {
|
||||
return visibility
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun Annotation.toMutable(): MutableAnnotation {
|
||||
return MutableAnnotation(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
|
||||
import org.jf.dexlib2.base.BaseAnnotationElement
|
||||
import org.jf.dexlib2.iface.AnnotationElement
|
||||
import org.jf.dexlib2.iface.value.EncodedValue
|
||||
|
||||
class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnotationElement() {
|
||||
private var name = annotationElement.name
|
||||
private var value = annotationElement.value.toMutable()
|
||||
|
||||
fun setName(name: String) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
fun setValue(value: MutableEncodedValue) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return name
|
||||
}
|
||||
|
||||
override fun getValue(): EncodedValue {
|
||||
return value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun AnnotationElement.toMutable(): MutableAnnotationElement {
|
||||
return MutableAnnotationElement(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import com.google.common.collect.Iterables
|
||||
import org.jf.dexlib2.base.reference.BaseTypeReference
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.util.FieldUtil
|
||||
import org.jf.dexlib2.util.MethodUtil
|
||||
|
||||
class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
|
||||
// Class
|
||||
private var type = classDef.type
|
||||
private var sourceFile = classDef.sourceFile
|
||||
private var accessFlags = classDef.accessFlags
|
||||
private var superclass = classDef.superclass
|
||||
|
||||
private val _interfaces by lazy { classDef.interfaces.toMutableList() }
|
||||
private val _annotations by lazy {
|
||||
classDef.annotations.map { annotation -> annotation.toMutable() }.toMutableSet()
|
||||
}
|
||||
|
||||
// Methods
|
||||
private val _methods by lazy { classDef.methods.map { method -> method.toMutable() }.toMutableSet() }
|
||||
private val _directMethods by lazy { Iterables.filter(_methods, MethodUtil.METHOD_IS_DIRECT).toMutableSet() }
|
||||
private val _virtualMethods by lazy { Iterables.filter(_methods, MethodUtil.METHOD_IS_VIRTUAL).toMutableSet() }
|
||||
|
||||
// Fields
|
||||
private val _fields by lazy { classDef.fields.map { field -> field.toMutable() }.toMutableSet() }
|
||||
private val _staticFields by lazy { Iterables.filter(_fields, FieldUtil.FIELD_IS_STATIC).toMutableSet() }
|
||||
private val _instanceFields by lazy { Iterables.filter(_fields, FieldUtil.FIELD_IS_INSTANCE).toMutableSet() }
|
||||
|
||||
fun setType(type: String) {
|
||||
this.type = type
|
||||
}
|
||||
|
||||
fun setSourceFile(sourceFile: String?) {
|
||||
this.sourceFile = sourceFile
|
||||
}
|
||||
|
||||
fun setAccessFlags(accessFlags: Int) {
|
||||
this.accessFlags = accessFlags
|
||||
}
|
||||
|
||||
fun setSuperClass(superclass: String?) {
|
||||
this.superclass = superclass
|
||||
}
|
||||
|
||||
override fun getType(): String {
|
||||
return type
|
||||
}
|
||||
|
||||
override fun getAccessFlags(): Int {
|
||||
return accessFlags
|
||||
}
|
||||
|
||||
override fun getSourceFile(): String? {
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
override fun getSuperclass(): String? {
|
||||
return superclass
|
||||
}
|
||||
|
||||
override fun getInterfaces(): MutableList<String> {
|
||||
return _interfaces
|
||||
}
|
||||
|
||||
override fun getAnnotations(): MutableSet<MutableAnnotation> {
|
||||
return _annotations
|
||||
}
|
||||
|
||||
override fun getStaticFields(): MutableSet<MutableField> {
|
||||
return _staticFields
|
||||
}
|
||||
|
||||
override fun getInstanceFields(): MutableSet<MutableField> {
|
||||
return _instanceFields
|
||||
}
|
||||
|
||||
override fun getFields(): MutableSet<MutableField> {
|
||||
return _fields
|
||||
}
|
||||
|
||||
override fun getDirectMethods(): MutableSet<MutableMethod> {
|
||||
return _directMethods
|
||||
}
|
||||
|
||||
override fun getVirtualMethods(): MutableSet<MutableMethod> {
|
||||
return _virtualMethods
|
||||
}
|
||||
|
||||
override fun getMethods(): MutableSet<MutableMethod> {
|
||||
return _methods
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun ClassDef.toMutable(): MutableClass {
|
||||
return MutableClass(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
|
||||
import org.jf.dexlib2.HiddenApiRestriction
|
||||
import org.jf.dexlib2.base.reference.BaseFieldReference
|
||||
import org.jf.dexlib2.iface.Field
|
||||
|
||||
class MutableField(field: Field) : Field, BaseFieldReference() {
|
||||
private var definingClass = field.definingClass
|
||||
private var name = field.name
|
||||
private var type = field.type
|
||||
private var accessFlags = field.accessFlags
|
||||
|
||||
private var initialValue = field.initialValue?.toMutable()
|
||||
private val _annotations by lazy { field.annotations.map { annotation -> annotation.toMutable() }.toMutableSet() }
|
||||
private val _hiddenApiRestrictions by lazy { field.hiddenApiRestrictions }
|
||||
|
||||
fun setDefiningClass(definingClass: String) {
|
||||
this.definingClass = definingClass
|
||||
}
|
||||
|
||||
fun setName(name: String) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
fun setType(type: String) {
|
||||
this.type = type
|
||||
}
|
||||
|
||||
fun setAccessFlags(accessFlags: Int) {
|
||||
this.accessFlags = accessFlags
|
||||
}
|
||||
|
||||
fun setInitialValue(initialValue: MutableEncodedValue?) {
|
||||
this.initialValue = initialValue
|
||||
}
|
||||
|
||||
override fun getDefiningClass(): String {
|
||||
return this.definingClass
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return this.name
|
||||
}
|
||||
|
||||
override fun getType(): String {
|
||||
return this.type
|
||||
}
|
||||
|
||||
override fun getAnnotations(): MutableSet<MutableAnnotation> {
|
||||
return this._annotations
|
||||
}
|
||||
|
||||
override fun getAccessFlags(): Int {
|
||||
return this.accessFlags
|
||||
}
|
||||
|
||||
override fun getHiddenApiRestrictions(): MutableSet<HiddenApiRestriction> {
|
||||
return this._hiddenApiRestrictions
|
||||
}
|
||||
|
||||
override fun getInitialValue(): MutableEncodedValue? {
|
||||
return this.initialValue
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun Field.toMutable(): MutableField {
|
||||
return MutableField(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethodParameter.Companion.toMutable
|
||||
import org.jf.dexlib2.HiddenApiRestriction
|
||||
import org.jf.dexlib2.base.reference.BaseMethodReference
|
||||
import org.jf.dexlib2.builder.MutableMethodImplementation
|
||||
import org.jf.dexlib2.iface.Method
|
||||
|
||||
class MutableMethod(method: Method) : Method, BaseMethodReference() {
|
||||
private var definingClass = method.definingClass
|
||||
private var name = method.name
|
||||
private var accessFlags = method.accessFlags
|
||||
private var returnType = method.returnType
|
||||
|
||||
// Create own mutable MethodImplementation (due to not being able to change members like register count)
|
||||
private val _implementation by lazy { method.implementation?.let { MutableMethodImplementation(it) } }
|
||||
private val _annotations by lazy { method.annotations.map { annotation -> annotation.toMutable() }.toMutableSet() }
|
||||
private val _parameters by lazy { method.parameters.map { parameter -> parameter.toMutable() }.toMutableList() }
|
||||
private val _parameterTypes by lazy { method.parameterTypes.toMutableList() }
|
||||
private val _hiddenApiRestrictions by lazy { method.hiddenApiRestrictions }
|
||||
|
||||
override fun getDefiningClass(): String {
|
||||
return definingClass
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return name
|
||||
}
|
||||
|
||||
override fun getParameterTypes(): MutableList<CharSequence> {
|
||||
return _parameterTypes
|
||||
}
|
||||
|
||||
override fun getReturnType(): String {
|
||||
return returnType
|
||||
}
|
||||
|
||||
override fun getAnnotations(): MutableSet<MutableAnnotation> {
|
||||
return _annotations
|
||||
}
|
||||
|
||||
override fun getAccessFlags(): Int {
|
||||
return accessFlags
|
||||
}
|
||||
|
||||
override fun getHiddenApiRestrictions(): MutableSet<HiddenApiRestriction> {
|
||||
return _hiddenApiRestrictions
|
||||
}
|
||||
|
||||
override fun getParameters(): MutableList<MutableMethodParameter> {
|
||||
return _parameters
|
||||
}
|
||||
|
||||
override fun getImplementation(): MutableMethodImplementation? {
|
||||
return _implementation
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun Method.toMutable(): MutableMethod {
|
||||
return MutableMethod(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||
import org.jf.dexlib2.base.BaseMethodParameter
|
||||
import org.jf.dexlib2.iface.MethodParameter
|
||||
|
||||
// TODO: finish overriding all members if necessary
|
||||
class MutableMethodParameter(parameter: MethodParameter) : MethodParameter, BaseMethodParameter() {
|
||||
private var type = parameter.type
|
||||
private var name = parameter.name
|
||||
private var signature = parameter.signature
|
||||
private val _annotations by lazy {
|
||||
parameter.annotations.map { annotation -> annotation.toMutable() }.toMutableSet()
|
||||
}
|
||||
|
||||
override fun getType(): String {
|
||||
return type
|
||||
}
|
||||
|
||||
override fun getName(): String? {
|
||||
return name
|
||||
}
|
||||
|
||||
override fun getSignature(): String? {
|
||||
return signature
|
||||
}
|
||||
|
||||
override fun getAnnotations(): MutableSet<MutableAnnotation> {
|
||||
return _annotations
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun MethodParameter.toMutable(): MutableMethodParameter {
|
||||
return MutableMethodParameter(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
|
||||
import org.jf.dexlib2.base.value.BaseAnnotationEncodedValue
|
||||
import org.jf.dexlib2.iface.AnnotationElement
|
||||
import org.jf.dexlib2.iface.value.AnnotationEncodedValue
|
||||
|
||||
class MutableAnnotationEncodedValue(annotationEncodedValue: AnnotationEncodedValue) : BaseAnnotationEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var type = annotationEncodedValue.type
|
||||
|
||||
private val _elements by lazy {
|
||||
annotationEncodedValue.elements.map { annotationElement -> annotationElement.toMutable() }.toMutableSet()
|
||||
}
|
||||
|
||||
override fun getType(): String {
|
||||
return this.type
|
||||
}
|
||||
|
||||
fun setType(type: String) {
|
||||
this.type = type
|
||||
}
|
||||
|
||||
override fun getElements(): MutableSet<out AnnotationElement> {
|
||||
return _elements
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun AnnotationEncodedValue.toMutable(): MutableAnnotationEncodedValue {
|
||||
return MutableAnnotationEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
|
||||
import org.jf.dexlib2.base.value.BaseArrayEncodedValue
|
||||
import org.jf.dexlib2.iface.value.ArrayEncodedValue
|
||||
import org.jf.dexlib2.iface.value.EncodedValue
|
||||
|
||||
class MutableArrayEncodedValue(arrayEncodedValue: ArrayEncodedValue) : BaseArrayEncodedValue(), MutableEncodedValue {
|
||||
private val _value by lazy {
|
||||
arrayEncodedValue.value.map { encodedValue -> encodedValue.toMutable() }.toMutableList()
|
||||
}
|
||||
|
||||
override fun getValue(): MutableList<out EncodedValue> {
|
||||
return _value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun ArrayEncodedValue.toMutable(): MutableArrayEncodedValue {
|
||||
return MutableArrayEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseBooleanEncodedValue
|
||||
import org.jf.dexlib2.iface.value.BooleanEncodedValue
|
||||
|
||||
class MutableBooleanEncodedValue(booleanEncodedValue: BooleanEncodedValue) : BaseBooleanEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = booleanEncodedValue.value
|
||||
|
||||
override fun getValue(): Boolean {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: Boolean) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun BooleanEncodedValue.toMutable(): MutableBooleanEncodedValue {
|
||||
return MutableBooleanEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseByteEncodedValue
|
||||
import org.jf.dexlib2.iface.value.ByteEncodedValue
|
||||
|
||||
class MutableByteEncodedValue(byteEncodedValue: ByteEncodedValue) : BaseByteEncodedValue(), MutableEncodedValue {
|
||||
private var value = byteEncodedValue.value
|
||||
|
||||
override fun getValue(): Byte {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: Byte) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue {
|
||||
return MutableByteEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseCharEncodedValue
|
||||
import org.jf.dexlib2.iface.value.CharEncodedValue
|
||||
|
||||
class MutableCharEncodedValue(charEncodedValue: CharEncodedValue) : BaseCharEncodedValue(), MutableEncodedValue {
|
||||
private var value = charEncodedValue.value
|
||||
|
||||
override fun getValue(): Char {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: Char) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun CharEncodedValue.toMutable(): MutableCharEncodedValue {
|
||||
return MutableCharEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseDoubleEncodedValue
|
||||
import org.jf.dexlib2.iface.value.DoubleEncodedValue
|
||||
|
||||
class MutableDoubleEncodedValue(doubleEncodedValue: DoubleEncodedValue) : BaseDoubleEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = doubleEncodedValue.value
|
||||
|
||||
override fun getValue(): Double {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: Double) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun DoubleEncodedValue.toMutable(): MutableDoubleEncodedValue {
|
||||
return MutableDoubleEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.ValueType
|
||||
import org.jf.dexlib2.iface.value.*
|
||||
|
||||
interface MutableEncodedValue : EncodedValue {
|
||||
companion object {
|
||||
fun EncodedValue.toMutable(): MutableEncodedValue {
|
||||
return when (this.valueType) {
|
||||
ValueType.TYPE -> MutableTypeEncodedValue(this as TypeEncodedValue)
|
||||
ValueType.FIELD -> MutableFieldEncodedValue(this as FieldEncodedValue)
|
||||
ValueType.METHOD -> MutableMethodEncodedValue(this as MethodEncodedValue)
|
||||
ValueType.ENUM -> MutableEnumEncodedValue(this as EnumEncodedValue)
|
||||
ValueType.ARRAY -> MutableArrayEncodedValue(this as ArrayEncodedValue)
|
||||
ValueType.ANNOTATION -> MutableAnnotationEncodedValue(this as AnnotationEncodedValue)
|
||||
ValueType.BYTE -> MutableByteEncodedValue(this as ByteEncodedValue)
|
||||
ValueType.SHORT -> MutableShortEncodedValue(this as ShortEncodedValue)
|
||||
ValueType.CHAR -> MutableCharEncodedValue(this as CharEncodedValue)
|
||||
ValueType.INT -> MutableIntEncodedValue(this as IntEncodedValue)
|
||||
ValueType.LONG -> MutableLongEncodedValue(this as LongEncodedValue)
|
||||
ValueType.FLOAT -> MutableFloatEncodedValue(this as FloatEncodedValue)
|
||||
ValueType.DOUBLE -> MutableDoubleEncodedValue(this as DoubleEncodedValue)
|
||||
ValueType.METHOD_TYPE -> MutableMethodTypeEncodedValue(this as MethodTypeEncodedValue)
|
||||
ValueType.METHOD_HANDLE -> MutableMethodHandleEncodedValue(this as MethodHandleEncodedValue)
|
||||
ValueType.STRING -> MutableStringEncodedValue(this as StringEncodedValue)
|
||||
ValueType.BOOLEAN -> MutableBooleanEncodedValue(this as BooleanEncodedValue)
|
||||
ValueType.NULL -> MutableNullEncodedValue()
|
||||
else -> this as MutableEncodedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseEnumEncodedValue
|
||||
import org.jf.dexlib2.iface.reference.FieldReference
|
||||
import org.jf.dexlib2.iface.value.EnumEncodedValue
|
||||
|
||||
class MutableEnumEncodedValue(enumEncodedValue: EnumEncodedValue) : BaseEnumEncodedValue(), MutableEncodedValue {
|
||||
private var value = enumEncodedValue.value
|
||||
|
||||
override fun getValue(): FieldReference {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: FieldReference) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun EnumEncodedValue.toMutable(): MutableEnumEncodedValue {
|
||||
return MutableEnumEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.ValueType
|
||||
import org.jf.dexlib2.base.value.BaseFieldEncodedValue
|
||||
import org.jf.dexlib2.iface.reference.FieldReference
|
||||
import org.jf.dexlib2.iface.value.FieldEncodedValue
|
||||
|
||||
class MutableFieldEncodedValue(fieldEncodedValue: FieldEncodedValue) : BaseFieldEncodedValue(), MutableEncodedValue {
|
||||
private var value = fieldEncodedValue.value
|
||||
|
||||
override fun getValueType(): Int {
|
||||
return ValueType.FIELD
|
||||
}
|
||||
|
||||
override fun getValue(): FieldReference {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: FieldReference) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun FieldEncodedValue.toMutable(): MutableFieldEncodedValue {
|
||||
return MutableFieldEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseFloatEncodedValue
|
||||
import org.jf.dexlib2.iface.value.FloatEncodedValue
|
||||
|
||||
class MutableFloatEncodedValue(floatEncodedValue: FloatEncodedValue) : BaseFloatEncodedValue(), MutableEncodedValue {
|
||||
private var value = floatEncodedValue.value
|
||||
|
||||
override fun getValue(): Float {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: Float) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun FloatEncodedValue.toMutable(): MutableFloatEncodedValue {
|
||||
return MutableFloatEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseIntEncodedValue
|
||||
import org.jf.dexlib2.iface.value.IntEncodedValue
|
||||
|
||||
class MutableIntEncodedValue(intEncodedValue: IntEncodedValue) : BaseIntEncodedValue(), MutableEncodedValue {
|
||||
private var value = intEncodedValue.value
|
||||
|
||||
override fun getValue(): Int {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: Int) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun IntEncodedValue.toMutable(): MutableIntEncodedValue {
|
||||
return MutableIntEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseLongEncodedValue
|
||||
import org.jf.dexlib2.iface.value.LongEncodedValue
|
||||
|
||||
class MutableLongEncodedValue(longEncodedValue: LongEncodedValue) : BaseLongEncodedValue(), MutableEncodedValue {
|
||||
private var value = longEncodedValue.value
|
||||
|
||||
override fun getValue(): Long {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: Long) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun LongEncodedValue.toMutable(): MutableLongEncodedValue {
|
||||
return MutableLongEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseMethodEncodedValue
|
||||
import org.jf.dexlib2.iface.reference.MethodReference
|
||||
import org.jf.dexlib2.iface.value.MethodEncodedValue
|
||||
|
||||
class MutableMethodEncodedValue(methodEncodedValue: MethodEncodedValue) : BaseMethodEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = methodEncodedValue.value
|
||||
|
||||
override fun getValue(): MethodReference {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: MethodReference) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun MethodEncodedValue.toMutable(): MutableMethodEncodedValue {
|
||||
return MutableMethodEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseMethodHandleEncodedValue
|
||||
import org.jf.dexlib2.iface.reference.MethodHandleReference
|
||||
import org.jf.dexlib2.iface.value.MethodHandleEncodedValue
|
||||
|
||||
class MutableMethodHandleEncodedValue(methodHandleEncodedValue: MethodHandleEncodedValue) :
|
||||
BaseMethodHandleEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = methodHandleEncodedValue.value
|
||||
|
||||
override fun getValue(): MethodHandleReference {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: MethodHandleReference) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun MethodHandleEncodedValue.toMutable(): MutableMethodHandleEncodedValue {
|
||||
return MutableMethodHandleEncodedValue(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseMethodTypeEncodedValue
|
||||
import org.jf.dexlib2.iface.reference.MethodProtoReference
|
||||
import org.jf.dexlib2.iface.value.MethodTypeEncodedValue
|
||||
|
||||
class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedValue) : BaseMethodTypeEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = methodTypeEncodedValue.value
|
||||
|
||||
override fun getValue(): MethodProtoReference {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: MethodProtoReference) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun MethodTypeEncodedValue.toMutable(): MutableMethodTypeEncodedValue {
|
||||
return MutableMethodTypeEncodedValue(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseNullEncodedValue
|
||||
import org.jf.dexlib2.iface.value.ByteEncodedValue
|
||||
|
||||
class MutableNullEncodedValue : BaseNullEncodedValue(), MutableEncodedValue {
|
||||
companion object {
|
||||
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue {
|
||||
return MutableByteEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseShortEncodedValue
|
||||
import org.jf.dexlib2.iface.value.ShortEncodedValue
|
||||
|
||||
class MutableShortEncodedValue(shortEncodedValue: ShortEncodedValue) : BaseShortEncodedValue(), MutableEncodedValue {
|
||||
private var value = shortEncodedValue.value
|
||||
|
||||
override fun getValue(): Short {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: Short) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun ShortEncodedValue.toMutable(): MutableShortEncodedValue {
|
||||
return MutableShortEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseStringEncodedValue
|
||||
import org.jf.dexlib2.iface.value.ByteEncodedValue
|
||||
import org.jf.dexlib2.iface.value.StringEncodedValue
|
||||
|
||||
class MutableStringEncodedValue(stringEncodedValue: StringEncodedValue) : BaseStringEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = stringEncodedValue.value
|
||||
|
||||
override fun getValue(): String {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: String) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue {
|
||||
return MutableByteEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseTypeEncodedValue
|
||||
import org.jf.dexlib2.iface.value.TypeEncodedValue
|
||||
|
||||
class MutableTypeEncodedValue(typeEncodedValue: TypeEncodedValue) : BaseTypeEncodedValue(), MutableEncodedValue {
|
||||
private var value = typeEncodedValue.value
|
||||
|
||||
override fun getValue(): String {
|
||||
return this.value
|
||||
}
|
||||
|
||||
fun setValue(value: String) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun TypeEncodedValue.toMutable(): MutableTypeEncodedValue {
|
||||
return MutableTypeEncodedValue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package app.revanced.patcher.util.smali
|
||||
|
||||
import org.antlr.runtime.CommonTokenStream
|
||||
import org.antlr.runtime.TokenSource
|
||||
import org.antlr.runtime.tree.CommonTreeNodeStream
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.Opcodes
|
||||
import org.jf.dexlib2.builder.BuilderInstruction
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.writer.builder.DexBuilder
|
||||
import org.jf.smali.LexerErrorInterface
|
||||
import org.jf.smali.smaliFlexLexer
|
||||
import org.jf.smali.smaliParser
|
||||
import org.jf.smali.smaliTreeWalker
|
||||
import java.io.InputStreamReader
|
||||
|
||||
private const val METHOD_TEMPLATE = """
|
||||
.class LInlineCompiler;
|
||||
.super Ljava/lang/Object;
|
||||
.method %s dummyMethod(%s)V
|
||||
.registers %d
|
||||
%s
|
||||
.end method
|
||||
"""
|
||||
|
||||
class InlineSmaliCompiler {
|
||||
companion object {
|
||||
/**
|
||||
* Compiles a string of Smali code to a list of instructions.
|
||||
* p0, p1 etc. will only work correctly if the parameters and registers are passed.
|
||||
* Do not cross the boundaries of the control flow (if-nez insn, etc),
|
||||
* as that will result in exceptions since the labels cannot be calculated.
|
||||
* Do not create dummy labels to fix the issue, since the code addresses will
|
||||
* be messed up and results in broken Dalvik bytecode.
|
||||
* FIXME: Fix the above issue. When this is fixed, add the proper conversions in [InstructionConverter].
|
||||
*/
|
||||
fun compile(
|
||||
instructions: String, parameters: String, registers: Int, forStaticMethod: Boolean
|
||||
): List<BuilderInstruction> {
|
||||
val input =
|
||||
METHOD_TEMPLATE.format(if (forStaticMethod) "static" else "", parameters, registers, instructions)
|
||||
val reader = InputStreamReader(input.byteInputStream())
|
||||
val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15)
|
||||
val tokens = CommonTokenStream(lexer as TokenSource)
|
||||
val parser = smaliParser(tokens)
|
||||
val result = parser.smali_file()
|
||||
if (parser.numberOfSyntaxErrors > 0 || lexer.numberOfSyntaxErrors > 0) {
|
||||
throw IllegalStateException(
|
||||
"Encountered ${parser.numberOfSyntaxErrors} parser syntax errors and ${lexer.numberOfSyntaxErrors} lexer syntax errors!"
|
||||
)
|
||||
}
|
||||
val treeStream = CommonTreeNodeStream(result.tree)
|
||||
treeStream.tokenStream = tokens
|
||||
val dexGen = smaliTreeWalker(treeStream)
|
||||
dexGen.setDexBuilder(DexBuilder(Opcodes.getDefault()))
|
||||
val classDef = dexGen.smali_file()
|
||||
return classDef.methods.first().implementation!!.instructions.map { it.toBuilderInstruction() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile lines of Smali code to a list of instructions.
|
||||
* @param templateMethod The method to compile the instructions against.
|
||||
* @returns A list of instructions.
|
||||
*/
|
||||
fun String.toInstructions(templateMethod: Method? = null) = InlineSmaliCompiler.compile(this,
|
||||
templateMethod?.parameters?.joinToString("") { it } ?: "",
|
||||
templateMethod?.implementation?.registerCount ?: 1,
|
||||
templateMethod?.let { AccessFlags.STATIC.isSet(it.accessFlags) } ?: true
|
||||
)
|
||||
|
||||
/**
|
||||
* Compile a line of Smali code to an instruction.
|
||||
* @param templateMethod The method to compile the instructions against.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun String.toInstruction(templateMethod: Method? = null) = this.toInstructions(templateMethod).first()
|
||||
@@ -1,261 +0,0 @@
|
||||
package app.revanced.patcher.util.smali
|
||||
|
||||
import org.jf.dexlib2.Format
|
||||
import org.jf.dexlib2.builder.instruction.*
|
||||
import org.jf.dexlib2.iface.instruction.Instruction
|
||||
import org.jf.dexlib2.iface.instruction.formats.*
|
||||
import org.jf.util.ExceptionWithContext
|
||||
|
||||
fun Instruction.toBuilderInstruction() =
|
||||
when (this.opcode.format) {
|
||||
Format.Format10x -> InstructionConverter.newBuilderInstruction10x(this as Instruction10x)
|
||||
Format.Format11n -> InstructionConverter.newBuilderInstruction11n(this as Instruction11n)
|
||||
Format.Format11x -> InstructionConverter.newBuilderInstruction11x(this as Instruction11x)
|
||||
Format.Format12x -> InstructionConverter.newBuilderInstruction12x(this as Instruction12x)
|
||||
Format.Format20bc -> InstructionConverter.newBuilderInstruction20bc(this as Instruction20bc)
|
||||
Format.Format21c -> InstructionConverter.newBuilderInstruction21c(this as Instruction21c)
|
||||
Format.Format21ih -> InstructionConverter.newBuilderInstruction21ih(this as Instruction21ih)
|
||||
Format.Format21lh -> InstructionConverter.newBuilderInstruction21lh(this as Instruction21lh)
|
||||
Format.Format21s -> InstructionConverter.newBuilderInstruction21s(this as Instruction21s)
|
||||
Format.Format22b -> InstructionConverter.newBuilderInstruction22b(this as Instruction22b)
|
||||
Format.Format22c -> InstructionConverter.newBuilderInstruction22c(this as Instruction22c)
|
||||
Format.Format22cs -> InstructionConverter.newBuilderInstruction22cs(this as Instruction22cs)
|
||||
Format.Format22s -> InstructionConverter.newBuilderInstruction22s(this as Instruction22s)
|
||||
Format.Format22x -> InstructionConverter.newBuilderInstruction22x(this as Instruction22x)
|
||||
Format.Format23x -> InstructionConverter.newBuilderInstruction23x(this as Instruction23x)
|
||||
Format.Format31c -> InstructionConverter.newBuilderInstruction31c(this as Instruction31c)
|
||||
Format.Format31i -> InstructionConverter.newBuilderInstruction31i(this as Instruction31i)
|
||||
Format.Format32x -> InstructionConverter.newBuilderInstruction32x(this as Instruction32x)
|
||||
Format.Format35c -> InstructionConverter.newBuilderInstruction35c(this as Instruction35c)
|
||||
Format.Format35mi -> InstructionConverter.newBuilderInstruction35mi(this as Instruction35mi)
|
||||
Format.Format35ms -> InstructionConverter.newBuilderInstruction35ms(this as Instruction35ms)
|
||||
Format.Format3rc -> InstructionConverter.newBuilderInstruction3rc(this as Instruction3rc)
|
||||
Format.Format3rmi -> InstructionConverter.newBuilderInstruction3rmi(this as Instruction3rmi)
|
||||
Format.Format3rms -> InstructionConverter.newBuilderInstruction3rms(this as Instruction3rms)
|
||||
Format.Format51l -> InstructionConverter.newBuilderInstruction51l(this as Instruction51l)
|
||||
else -> throw ExceptionWithContext("Instruction format %s not supported", this.opcode.format)
|
||||
}
|
||||
|
||||
internal class InstructionConverter {
|
||||
companion object {
|
||||
internal fun newBuilderInstruction10x(instruction: Instruction10x): BuilderInstruction10x {
|
||||
return BuilderInstruction10x(
|
||||
instruction.opcode
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction11n(instruction: Instruction11n): BuilderInstruction11n {
|
||||
return BuilderInstruction11n(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.narrowLiteral
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction11x(instruction: Instruction11x): BuilderInstruction11x {
|
||||
return BuilderInstruction11x(
|
||||
instruction.opcode,
|
||||
instruction.registerA
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction12x(instruction: Instruction12x): BuilderInstruction12x {
|
||||
return BuilderInstruction12x(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.registerB
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction20bc(instruction: Instruction20bc): BuilderInstruction20bc {
|
||||
return BuilderInstruction20bc(
|
||||
instruction.opcode,
|
||||
instruction.verificationError,
|
||||
instruction.reference
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction21c(instruction: Instruction21c): BuilderInstruction21c {
|
||||
return BuilderInstruction21c(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.reference
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction21ih(instruction: Instruction21ih): BuilderInstruction21ih {
|
||||
return BuilderInstruction21ih(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.narrowLiteral
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction21lh(instruction: Instruction21lh): BuilderInstruction21lh {
|
||||
return BuilderInstruction21lh(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.wideLiteral
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction21s(instruction: Instruction21s): BuilderInstruction21s {
|
||||
return BuilderInstruction21s(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.narrowLiteral
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction22b(instruction: Instruction22b): BuilderInstruction22b {
|
||||
return BuilderInstruction22b(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.registerB,
|
||||
instruction.narrowLiteral
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction22c(instruction: Instruction22c): BuilderInstruction22c {
|
||||
return BuilderInstruction22c(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.registerB,
|
||||
instruction.reference
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction22cs(instruction: Instruction22cs): BuilderInstruction22cs {
|
||||
return BuilderInstruction22cs(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.registerB,
|
||||
instruction.fieldOffset
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction22s(instruction: Instruction22s): BuilderInstruction22s {
|
||||
return BuilderInstruction22s(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.registerB,
|
||||
instruction.narrowLiteral
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction22x(instruction: Instruction22x): BuilderInstruction22x {
|
||||
return BuilderInstruction22x(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.registerB
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction23x(instruction: Instruction23x): BuilderInstruction23x {
|
||||
return BuilderInstruction23x(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.registerB,
|
||||
instruction.registerC
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction31c(instruction: Instruction31c): BuilderInstruction31c {
|
||||
return BuilderInstruction31c(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.reference
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction31i(instruction: Instruction31i): BuilderInstruction31i {
|
||||
return BuilderInstruction31i(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.narrowLiteral
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction32x(instruction: Instruction32x): BuilderInstruction32x {
|
||||
return BuilderInstruction32x(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.registerB
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction35c(instruction: Instruction35c): BuilderInstruction35c {
|
||||
return BuilderInstruction35c(
|
||||
instruction.opcode,
|
||||
instruction.registerCount,
|
||||
instruction.registerC,
|
||||
instruction.registerD,
|
||||
instruction.registerE,
|
||||
instruction.registerF,
|
||||
instruction.registerG,
|
||||
instruction.reference
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction35mi(instruction: Instruction35mi): BuilderInstruction35mi {
|
||||
return BuilderInstruction35mi(
|
||||
instruction.opcode,
|
||||
instruction.registerCount,
|
||||
instruction.registerC,
|
||||
instruction.registerD,
|
||||
instruction.registerE,
|
||||
instruction.registerF,
|
||||
instruction.registerG,
|
||||
instruction.inlineIndex
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction35ms(instruction: Instruction35ms): BuilderInstruction35ms {
|
||||
return BuilderInstruction35ms(
|
||||
instruction.opcode,
|
||||
instruction.registerCount,
|
||||
instruction.registerC,
|
||||
instruction.registerD,
|
||||
instruction.registerE,
|
||||
instruction.registerF,
|
||||
instruction.registerG,
|
||||
instruction.vtableIndex
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction3rc(instruction: Instruction3rc): BuilderInstruction3rc {
|
||||
return BuilderInstruction3rc(
|
||||
instruction.opcode,
|
||||
instruction.startRegister,
|
||||
instruction.registerCount,
|
||||
instruction.reference
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction3rmi(instruction: Instruction3rmi): BuilderInstruction3rmi {
|
||||
return BuilderInstruction3rmi(
|
||||
instruction.opcode,
|
||||
instruction.startRegister,
|
||||
instruction.registerCount,
|
||||
instruction.inlineIndex
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction3rms(instruction: Instruction3rms): BuilderInstruction3rms {
|
||||
return BuilderInstruction3rms(
|
||||
instruction.opcode,
|
||||
instruction.startRegister,
|
||||
instruction.registerCount,
|
||||
instruction.vtableIndex
|
||||
)
|
||||
}
|
||||
|
||||
internal fun newBuilderInstruction51l(instruction: Instruction51l): BuilderInstruction51l {
|
||||
return BuilderInstruction51l(
|
||||
instruction.opcode,
|
||||
instruction.registerA,
|
||||
instruction.wideLiteral
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/main/kotlin/app/revanced/patcher/writer/ASMWriter.kt
Normal file
21
src/main/kotlin/app/revanced/patcher/writer/ASMWriter.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
package app.revanced.patcher.writer
|
||||
|
||||
import org.objectweb.asm.tree.AbstractInsnNode
|
||||
import org.objectweb.asm.tree.InsnList
|
||||
|
||||
object ASMWriter {
|
||||
fun InsnList.setAt(index: Int, node: AbstractInsnNode) {
|
||||
this[this.get(index)] = node
|
||||
}
|
||||
|
||||
fun InsnList.insertAt(index: Int = 0, vararg nodes: AbstractInsnNode) {
|
||||
this.insert(this.get(index), nodes.toInsnList())
|
||||
}
|
||||
|
||||
// TODO(Sculas): Should this be public?
|
||||
private fun Array<out AbstractInsnNode>.toInsnList(): InsnList {
|
||||
val list = InsnList()
|
||||
this.forEach { list.add(it) }
|
||||
return list
|
||||
}
|
||||
}
|
||||
161
src/test/kotlin/app/revanced/patcher/PatcherTest.kt
Normal file
161
src/test/kotlin/app/revanced/patcher/PatcherTest.kt
Normal file
@@ -0,0 +1,161 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.cache.Cache
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
import app.revanced.patcher.patch.PatchResultSuccess
|
||||
import app.revanced.patcher.signature.Signature
|
||||
import app.revanced.patcher.util.ExtraTypes
|
||||
import app.revanced.patcher.util.TestUtil
|
||||
import app.revanced.patcher.writer.ASMWriter.insertAt
|
||||
import app.revanced.patcher.writer.ASMWriter.setAt
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
import org.objectweb.asm.Type
|
||||
import org.objectweb.asm.tree.FieldInsnNode
|
||||
import org.objectweb.asm.tree.LdcInsnNode
|
||||
import org.objectweb.asm.tree.MethodInsnNode
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
import kotlin.test.Test
|
||||
|
||||
internal class PatcherTest {
|
||||
companion object {
|
||||
val testSignatures: Array<Signature> = arrayOf(
|
||||
// Java:
|
||||
// public static void main(String[] args) {
|
||||
// System.out.println("Hello, world!");
|
||||
// }
|
||||
// Bytecode:
|
||||
// public static main(java.lang.String[] arg0) { // Method signature: ([Ljava/lang/String;)V
|
||||
// getstatic java/lang/System.out:java.io.PrintStream
|
||||
// ldc "Hello, world!" (java.lang.String)
|
||||
// invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
|
||||
// return
|
||||
// }
|
||||
Signature(
|
||||
"mainMethod",
|
||||
Type.VOID_TYPE,
|
||||
ACC_PUBLIC or ACC_STATIC,
|
||||
arrayOf(ExtraTypes.ArrayAny),
|
||||
intArrayOf(
|
||||
LDC,
|
||||
INVOKEVIRTUAL
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPatcher() {
|
||||
val patcher = Patcher(
|
||||
PatcherTest::class.java.getResourceAsStream("/test1.jar")!!,
|
||||
ByteArrayOutputStream(),
|
||||
testSignatures
|
||||
)
|
||||
|
||||
patcher.addPatches(
|
||||
object : Patch("TestPatch") {
|
||||
override fun execute(cache: Cache): PatchResult {
|
||||
// Get the method from the resolver cache
|
||||
val mainMethod = patcher.cache.methods["mainMethod"]
|
||||
// Get the instruction list
|
||||
val instructions = mainMethod.method.instructions!!
|
||||
|
||||
// Let's modify it, so it prints "Hello, ReVanced! Editing bytecode."
|
||||
// Get the start index of our opcode pattern.
|
||||
// This will be the index of the LDC instruction.
|
||||
val startIndex = mainMethod.scanData.startIndex
|
||||
TestUtil.assertNodeEqual(LdcInsnNode("Hello, world!"), instructions[startIndex]!!)
|
||||
// Create a new LDC node and replace the LDC instruction.
|
||||
val stringNode = LdcInsnNode("Hello, ReVanced! Editing bytecode.")
|
||||
instructions.setAt(startIndex, stringNode)
|
||||
|
||||
// Now lets print our string twice!
|
||||
// Insert our instructions after the second instruction by our pattern.
|
||||
// This will place our instructions after the original INVOKEVIRTUAL call.
|
||||
// You could also copy the instructions from the list and then modify the LDC instruction again,
|
||||
// but this is to show a more advanced example of writing bytecode using the patcher and ASM.
|
||||
instructions.insertAt(
|
||||
startIndex + 1,
|
||||
FieldInsnNode(
|
||||
GETSTATIC,
|
||||
Type.getInternalName(System::class.java), // "java/lang/System"
|
||||
"out",
|
||||
"L" + Type.getInternalName(PrintStream::class.java) // "Ljava/io/PrintStream"
|
||||
),
|
||||
LdcInsnNode("Hello, ReVanced! Adding bytecode."),
|
||||
MethodInsnNode(
|
||||
INVOKEVIRTUAL,
|
||||
Type.getInternalName(PrintStream::class.java), // "java/io/PrintStream"
|
||||
"println",
|
||||
Type.getMethodDescriptor(
|
||||
Type.VOID_TYPE,
|
||||
Type.getType(String::class.java)
|
||||
) // "(Ljava/lang/String;)V"
|
||||
)
|
||||
)
|
||||
|
||||
// Our code now looks like this:
|
||||
// public static main(java.lang.String[] arg0) { // Method signature: ([Ljava/lang/String;)V
|
||||
// getstatic java/lang/System.out:java.io.PrintStream
|
||||
// ldc "Hello, ReVanced! Editing bytecode." (java.lang.String) // We overwrote this instruction.
|
||||
// invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
|
||||
// getstatic java/lang/System.out:java.io.PrintStream // This instruction and the 2 instructions below are written manually.
|
||||
// ldc "Hello, ReVanced! Adding bytecode." (java.lang.String)
|
||||
// invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
|
||||
// return
|
||||
// }
|
||||
|
||||
// Finally, tell the patcher that this patch was a success.
|
||||
// You can also return PatchResultError with a message.
|
||||
// If an exception is thrown inside this function,
|
||||
// a PatchResultError will be returned with the error message.
|
||||
return PatchResultSuccess()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Apply all patches loaded in the patcher
|
||||
val patchResult = patcher.applyPatches()
|
||||
// You can check if an error occurred
|
||||
for ((patchName, result) in patchResult) {
|
||||
if (result.isFailure) {
|
||||
throw Exception("Patch $patchName failed", result.exceptionOrNull()!!)
|
||||
}
|
||||
}
|
||||
|
||||
patcher.save()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test patcher with no changes`() {
|
||||
val testData = PatcherTest::class.java.getResourceAsStream("/test1.jar")!!
|
||||
// val available = testData.available()
|
||||
val out = ByteArrayOutputStream()
|
||||
Patcher(testData, out, testSignatures).save()
|
||||
// FIXME(Sculas): There seems to be a 1-byte difference, not sure what it is.
|
||||
// assertEquals(available, out.size())
|
||||
out.close()
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun `should not raise an exception if any signature member except the name is missing`() {
|
||||
val sigName = "testMethod"
|
||||
|
||||
assertDoesNotThrow("Should raise an exception because opcodes is empty") {
|
||||
Patcher(
|
||||
PatcherTest::class.java.getResourceAsStream("/test1.jar")!!,
|
||||
ByteArrayOutputStream(),
|
||||
arrayOf(
|
||||
Signature(
|
||||
sigName,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/test/kotlin/app/revanced/patcher/ReaderTest.kt
Normal file
12
src/test/kotlin/app/revanced/patcher/ReaderTest.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import kotlin.test.Test
|
||||
|
||||
internal class ReaderTest {
|
||||
@Test
|
||||
fun `read jar containing multiple classes`() {
|
||||
val testData = PatcherTest::class.java.getResourceAsStream("/test2.jar")!!
|
||||
Patcher(testData, ByteArrayOutputStream(), PatcherTest.testSignatures).save() // reusing test sigs from PatcherTest
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package app.revanced.patcher.usage
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class PatcherTest {
|
||||
@Test
|
||||
fun testPatcher() {
|
||||
return // FIXME: create a proper resource to pass this test
|
||||
/**
|
||||
val patcher = Patcher(
|
||||
File(PatcherTest::class.java.getResource("/example.apk")!!.toURI()),
|
||||
"exampleCacheDirectory",
|
||||
patchResources = true
|
||||
)
|
||||
|
||||
patcher.addPatches(listOf(ExampleBytecodePatch(), ExampleResourcePatch()))
|
||||
|
||||
for (signature in patcher.resolveSignatures()) {
|
||||
if (!signature.resolved) {
|
||||
throw Exception("Signature ${signature.metadata.name} was not resolved!")
|
||||
}
|
||||
val patternScanMethod = signature.metadata.patternScanMethod
|
||||
if (patternScanMethod is PatternScanMethod.Fuzzy) {
|
||||
val warnings = patternScanMethod.warnings
|
||||
if (warnings != null) {
|
||||
println("Signature ${signature.metadata.name} had ${warnings.size} warnings!")
|
||||
for (warning in warnings) {
|
||||
println(warning.toString())
|
||||
}
|
||||
} else {
|
||||
println("Signature ${signature.metadata.name} used the fuzzy resolver, but the warnings list is null!")
|
||||
}
|
||||
}
|
||||
}
|
||||
for ((metadata, result) in patcher.applyPatches()) {
|
||||
if (result.isFailure) {
|
||||
throw Exception("Patch ${metadata.shortName} failed", result.exceptionOrNull()!!)
|
||||
} else {
|
||||
println("Patch ${metadata.shortName} applied successfully!")
|
||||
}
|
||||
}
|
||||
val out = patcher.save()
|
||||
assertTrue(out.isNotEmpty(), "Expected the output of Patcher#save() to not be empty.")
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package app.revanced.patcher.usage.bytecode.annotation
|
||||
|
||||
import app.revanced.patcher.annotation.Compatibility
|
||||
import app.revanced.patcher.annotation.Package
|
||||
|
||||
@Compatibility(
|
||||
[Package(
|
||||
"com.example.examplePackage", arrayOf("0.0.1", "0.0.2")
|
||||
)]
|
||||
)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class ExampleBytecodeCompatibility
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package app.revanced.patcher.usage.bytecode.fingerprints
|
||||
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.usage.bytecode.annotation.ExampleBytecodeCompatibility
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.Opcode
|
||||
|
||||
@Name("example-fingerprint")
|
||||
@MatchingMethod(
|
||||
"LexampleClass;",
|
||||
"exampleMehod"
|
||||
)
|
||||
@FuzzyPatternScanMethod(2)
|
||||
@ExampleBytecodeCompatibility
|
||||
@Version("0.0.1")
|
||||
object ExampleFingerprint : MethodFingerprint(
|
||||
"V",
|
||||
AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
listOf("[L"),
|
||||
listOf(
|
||||
Opcode.SGET_OBJECT,
|
||||
null, // Testing unknown opcodes.
|
||||
Opcode.INVOKE_STATIC, // This is intentionally wrong to test the Fuzzy resolver.
|
||||
Opcode.RETURN_VOID
|
||||
),
|
||||
null
|
||||
)
|
||||
@@ -1,164 +0,0 @@
|
||||
package app.revanced.patcher.usage.bytecode.patch
|
||||
|
||||
import app.revanced.patcher.annotation.Description
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
import app.revanced.patcher.patch.PatchResultSuccess
|
||||
import app.revanced.patcher.patch.annotations.Patch
|
||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||
import app.revanced.patcher.usage.bytecode.fingerprints.ExampleFingerprint
|
||||
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.util.smali.toInstruction
|
||||
import com.google.common.collect.ImmutableList
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.Format
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.builder.MutableMethodImplementation
|
||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction11x
|
||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction21c
|
||||
import org.jf.dexlib2.immutable.ImmutableField
|
||||
import org.jf.dexlib2.immutable.ImmutableMethod
|
||||
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
||||
import org.jf.dexlib2.immutable.reference.ImmutableFieldReference
|
||||
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
|
||||
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
|
||||
import org.jf.dexlib2.util.Preconditions
|
||||
|
||||
@Patch
|
||||
@Name("example-bytecode-patch")
|
||||
@Description("Example demonstration of a bytecode patch.")
|
||||
@ExampleResourceCompatibility
|
||||
@Version("0.0.1")
|
||||
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||
// This function will be executed by the patcher.
|
||||
// You can treat it as a constructor
|
||||
override fun execute(data: BytecodeData): PatchResult {
|
||||
// Get the resolved method by its fingerprint from the resolver cache
|
||||
val result = ExampleFingerprint.result!!
|
||||
|
||||
// Get the implementation for the resolved method
|
||||
val method = result.mutableMethod
|
||||
val implementation = method.implementation!!
|
||||
|
||||
// Let's modify it, so it prints "Hello, ReVanced! Editing bytecode."
|
||||
// Get the start index of our opcode pattern.
|
||||
// This will be the index of the instruction with the opcode CONST_STRING.
|
||||
val startIndex = result.patternScanResult!!.startIndex
|
||||
|
||||
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
|
||||
|
||||
// Get the class in which the method matching our fingerprint is defined in.
|
||||
val mainClass = data.findClass {
|
||||
it.type == result.classDef.type
|
||||
}!!.resolve()
|
||||
|
||||
// Add a new method returning a string
|
||||
mainClass.methods.add(
|
||||
ImmutableMethod(
|
||||
result.classDef.type,
|
||||
"returnHello",
|
||||
null,
|
||||
"Ljava/lang/String;",
|
||||
AccessFlags.PRIVATE or AccessFlags.STATIC,
|
||||
null,
|
||||
null,
|
||||
ImmutableMethodImplementation(
|
||||
1,
|
||||
ImmutableList.of(
|
||||
BuilderInstruction21c(
|
||||
Opcode.CONST_STRING,
|
||||
0,
|
||||
ImmutableStringReference("Hello, ReVanced! Adding bytecode.")
|
||||
),
|
||||
BuilderInstruction11x(Opcode.RETURN_OBJECT, 0)
|
||||
),
|
||||
null,
|
||||
null
|
||||
)
|
||||
).toMutable()
|
||||
)
|
||||
|
||||
// Add a field in the main class
|
||||
// We will use this field in our method below to call println on
|
||||
// The field holds the Ljava/io/PrintStream->out; field
|
||||
mainClass.fields.add(
|
||||
ImmutableField(
|
||||
mainClass.type,
|
||||
"dummyField",
|
||||
"Ljava/io/PrintStream;",
|
||||
AccessFlags.PRIVATE or AccessFlags.STATIC,
|
||||
ImmutableFieldEncodedValue(
|
||||
ImmutableFieldReference(
|
||||
"Ljava/lang/System;",
|
||||
"out",
|
||||
"Ljava/io/PrintStream;"
|
||||
)
|
||||
),
|
||||
null,
|
||||
null
|
||||
).toMutable()
|
||||
)
|
||||
|
||||
// store the fields initial value into the first virtual register
|
||||
implementation.replaceInstruction(
|
||||
0,
|
||||
"sget-object v0, LTestClass;->dummyField:Ljava/io/PrintStream;".toInstruction()
|
||||
)
|
||||
|
||||
// Now let's create a new call to our method and print the return value!
|
||||
// You can also use the smali compiler to create instructions.
|
||||
// For this sake of example I reuse the TestClass field dummyField inside the virtual register 0.
|
||||
//
|
||||
// Control flow instructions are not supported as of now.
|
||||
val instructions = """
|
||||
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
|
||||
move-result-object v1
|
||||
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||
"""
|
||||
method.addInstructions(startIndex + 2, instructions)
|
||||
|
||||
// Finally, tell the patcher that this patch was a success.
|
||||
// You can also return PatchResultError with a message.
|
||||
// If an exception is thrown inside this function,
|
||||
// a PatchResultError will be returned with the error message.
|
||||
return PatchResultSuccess()
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the string for an instruction at the given index with a new one.
|
||||
* @param index The index of the instruction to replace the string for
|
||||
* @param string The replacing string
|
||||
*/
|
||||
private fun MutableMethodImplementation.replaceStringAt(index: Int, string: String) {
|
||||
val instruction = this.instructions[index]
|
||||
|
||||
// Utility method of dexlib2
|
||||
Preconditions.checkFormat(instruction.opcode, Format.Format21c)
|
||||
|
||||
// Cast this to an instruction of the format 21c
|
||||
// The instruction format can be found in the docs at
|
||||
// https://source.android.com/devices/tech/dalvik/dalvik-bytecode
|
||||
val strInstruction = instruction as Instruction21c
|
||||
|
||||
// In our case we want an instruction with the opcode CONST_STRING
|
||||
// The format is 21c, so we create a new BuilderInstruction21c
|
||||
// This instruction will hold the string reference constant in the virtual register of the original instruction
|
||||
// For that a reference to the string is needed. It can be created with an ImmutableStringReference.
|
||||
// At last, use the method replaceInstruction to replace it at the given index startIndex.
|
||||
this.replaceInstruction(
|
||||
index,
|
||||
BuilderInstruction21c(
|
||||
Opcode.CONST_STRING,
|
||||
strInstruction.registerA,
|
||||
ImmutableStringReference(string)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package app.revanced.patcher.usage.resource.annotation
|
||||
|
||||
import app.revanced.patcher.annotation.Compatibility
|
||||
import app.revanced.patcher.annotation.Package
|
||||
|
||||
@Compatibility(
|
||||
[Package(
|
||||
"com.example.examplePackage", arrayOf("0.0.1", "0.0.2")
|
||||
)]
|
||||
)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class ExampleResourceCompatibility
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package app.revanced.patcher.usage.resource.patch
|
||||
|
||||
import app.revanced.patcher.annotation.Description
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.data.impl.ResourceData
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
import app.revanced.patcher.patch.PatchResultSuccess
|
||||
import app.revanced.patcher.patch.annotations.Patch
|
||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
||||
import org.w3c.dom.Element
|
||||
|
||||
@Patch
|
||||
@Name("example-resource-patch")
|
||||
@Description("Example demonstration of a resource patch.")
|
||||
@ExampleResourceCompatibility
|
||||
@Version("0.0.1")
|
||||
class ExampleResourcePatch : ResourcePatch() {
|
||||
override fun execute(data: ResourceData): PatchResult {
|
||||
data.xmlEditor["AndroidManifest.xml"].use { editor ->
|
||||
val element = editor // regular DomFileEditor
|
||||
.file
|
||||
.getElementsByTagName("application")
|
||||
.item(0) as Element
|
||||
element
|
||||
.setAttribute(
|
||||
"exampleAttribute",
|
||||
"exampleValue"
|
||||
)
|
||||
}
|
||||
|
||||
return PatchResultSuccess()
|
||||
}
|
||||
}
|
||||
44
src/test/kotlin/app/revanced/patcher/util/TestUtil.kt
Normal file
44
src/test/kotlin/app/revanced/patcher/util/TestUtil.kt
Normal file
@@ -0,0 +1,44 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import org.objectweb.asm.tree.AbstractInsnNode
|
||||
import org.objectweb.asm.tree.FieldInsnNode
|
||||
import org.objectweb.asm.tree.LdcInsnNode
|
||||
import kotlin.test.fail
|
||||
|
||||
object TestUtil {
|
||||
fun <T: AbstractInsnNode> assertNodeEqual(expected: T, actual: T) {
|
||||
val a = expected.nodeString()
|
||||
val b = actual.nodeString()
|
||||
if (a != b) {
|
||||
fail("expected: $a,\nactual: $b\n")
|
||||
}
|
||||
}
|
||||
|
||||
private fun AbstractInsnNode.nodeString(): String {
|
||||
val sb = NodeStringBuilder()
|
||||
when (this) {
|
||||
// TODO(Sculas): Add more types
|
||||
is LdcInsnNode -> sb
|
||||
.addType("cst", cst)
|
||||
is FieldInsnNode -> sb
|
||||
.addType("owner", owner)
|
||||
.addType("name", name)
|
||||
.addType("desc", desc)
|
||||
}
|
||||
return "(${this::class.simpleName}): (type = $type, opcode = $opcode, $sb)"
|
||||
}
|
||||
}
|
||||
|
||||
private class NodeStringBuilder {
|
||||
private val sb = StringBuilder()
|
||||
|
||||
fun addType(name: String, value: Any): NodeStringBuilder {
|
||||
sb.append("$name = \"$value\", ")
|
||||
return this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val s = sb.toString()
|
||||
return s.substring(0 until s.length - 2) // remove the last ", "
|
||||
}
|
||||
}
|
||||
BIN
src/test/resources/test1.jar
Normal file
BIN
src/test/resources/test1.jar
Normal file
Binary file not shown.
BIN
src/test/resources/test2.jar
Normal file
BIN
src/test/resources/test2.jar
Normal file
Binary file not shown.
Reference in New Issue
Block a user