1
mirror of https://github.com/revanced/revanced-patcher synced 2025-09-17 07:30:49 +02:00

Compare commits

..

34 Commits

Author SHA1 Message Date
semantic-release-bot
062ae14936 chore: Release v21.1.0-dev.2 [skip ci]
# [21.1.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.1...v21.1.0-dev.2) (2025-06-20)

### Bug Fixes

* Add back missing log by naming logger correctly ([#332](https://github.com/ReVanced/revanced-patcher/issues/332)) ([e4e66b0](e4e66b0d8b))
* Support UTF-8 chars when compiling instructions in Smali in non UTF-8 environments ([#331](https://github.com/ReVanced/revanced-patcher/issues/331)) ([bb8771b](bb8771bb8b))

### Features

* Use option name as key for simplicity and consistency ([754b02e](754b02e4ca))

### Performance Improvements

* Use a buffered writer to reduce IO overhead ([#347](https://github.com/ReVanced/revanced-patcher/issues/347)) ([99f4318](99f431897e))
2025-06-20 13:28:31 +00:00
Pg
99f431897e perf: Use a buffered writer to reduce IO overhead (#347) 2025-06-20 15:26:10 +02:00
oSumAtrIX
d80abbcd17 docs: Correct API usage of fingerprints 2025-03-10 13:52:09 +01:00
oSumAtrIX
509ecc81e1 docs: Correct API usage of fingerprints 2025-03-10 13:47:55 +01:00
kitadai31
e4e66b0d8b fix: Add back missing log by naming logger correctly (#332) 2025-01-20 00:40:26 +01:00
Vologhat
bb8771bb8b fix: Support UTF-8 chars when compiling instructions in Smali in non UTF-8 environments (#331) 2025-01-07 01:30:21 +01:00
oSumAtrIX
754b02e4ca feat: Use option name as key for simplicity and consistency 2024-12-24 16:47:48 +01:00
oSumAtrIX
fe5fb736cb build: Bump dependencies 2024-12-17 04:20:28 +01:00
semantic-release-bot
fc505a8726 chore: Release v21.1.0-dev.1 [skip ci]
# [21.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0...v21.1.0-dev.1) (2024-12-07)

### Features

* Add identity hash code to unnamed patches ([88a3252](88a3252574))
2024-12-07 05:20:15 +00:00
oSumAtrIX
88a3252574 feat: Add identity hash code to unnamed patches 2024-12-07 06:19:08 +01:00
semantic-release-bot
ead701bdaf chore: Release v21.0.0 [skip ci]
# [21.0.0](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0) (2024-11-05)

### Bug Fixes

* Match fingerprint before delegating the match property ([5d996de](5d996def4d))
* Merge extension only when patch executes ([#315](https://github.com/ReVanced/revanced-patcher/issues/315)) ([aa472eb](aa472eb985))

### Features

* Improve Fingerprint API ([#316](https://github.com/ReVanced/revanced-patcher/issues/316)) ([0abf1c6](0abf1c6c02))
* Improve various APIs  ([#317](https://github.com/ReVanced/revanced-patcher/issues/317)) ([b824978](b8249789df))
* Move fingerprint match members to fingerprint for ease of access by using context receivers ([0746c22](0746c22743))

### Performance Improvements

* Use smallest lookup map for strings ([1358d3f](1358d3fa10))

### BREAKING CHANGES

* Various APIs have been changed.
* Many APIs have been changed.
2024-11-05 18:18:41 +00:00
oSumAtrIX
0581dcf931 chore: Merge branch dev to main 2024-11-05 19:16:28 +01:00
semantic-release-bot
62191e3c4a chore: Release v21.0.0-dev.4 [skip ci]
# [21.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.3...v21.0.0-dev.4) (2024-11-05)

### Performance Improvements

* Use smallest lookup map for strings ([1358d3f](1358d3fa10))
2024-11-05 13:41:06 +00:00
oSumAtrIX
1358d3fa10 perf: Use smallest lookup map for strings 2024-11-05 14:39:18 +01:00
semantic-release-bot
6712f0ea72 chore: Release v21.0.0-dev.3 [skip ci]
# [21.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.2...v21.0.0-dev.3) (2024-11-05)

### Features

* Move fingerprint match members to fingerprint for ease of access by using context receivers ([0746c22](0746c22743))
2024-11-05 13:25:22 +00:00
oSumAtrIX
0746c22743 feat: Move fingerprint match members to fingerprint for ease of access by using context receivers 2024-11-05 14:23:19 +01:00
semantic-release-bot
7f55868e6f chore: Release v21.0.0-dev.2 [skip ci]
# [21.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.1...v21.0.0-dev.2) (2024-11-01)

### Bug Fixes

* Match fingerprint before delegating the match property ([5d996de](5d996def4d))
2024-11-01 01:49:47 +00:00
oSumAtrIX
5d996def4d fix: Match fingerprint before delegating the match property 2024-11-01 02:47:57 +01:00
semantic-release-bot
49f4570164 chore: Release v21.0.0-dev.1 [skip ci]
# [21.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0-dev.1) (2024-10-27)

### Bug Fixes

* Merge extension only when patch executes ([#315](https://github.com/ReVanced/revanced-patcher/issues/315)) ([aa472eb](aa472eb985))

### Features

* Improve Fingerprint API ([#316](https://github.com/ReVanced/revanced-patcher/issues/316)) ([0abf1c6](0abf1c6c02))
* Improve various APIs  ([#317](https://github.com/ReVanced/revanced-patcher/issues/317)) ([b824978](b8249789df))

### BREAKING CHANGES

* Various APIs have been changed.
* Many APIs have been changed.
2024-10-27 15:08:13 +00:00
oSumAtrIX
b8249789df feat: Improve various APIs (#317)
Some APIs have been slightly changed, and API docs have been added.

BREAKING CHANGE: Various APIs have been changed.
2024-10-27 16:06:25 +01:00
oSumAtrIX
0abf1c6c02 feat: Improve Fingerprint API (#316)
Fingerprints can now be matched easily without adding them to a patch first.

BREAKING CHANGE: Many APIs have been changed.
2024-10-27 16:04:30 +01:00
oSumAtrIX
aa472eb985 fix: Merge extension only when patch executes (#315) 2024-10-27 16:00:30 +01:00
semantic-release-bot
ab624f04f6 chore: Release v20.0.2 [skip ci]
## [20.0.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1...v20.0.2) (2024-10-17)

### Bug Fixes

* Make it work on Android 12 and lower by using existing APIs ([#312](https://github.com/ReVanced/revanced-patcher/issues/312)) ([a44802e](a44802ef4e))
2024-10-17 18:03:27 +00:00
oSumAtrIX
21b5c079fb chore: Merge branch dev to main (#314) 2024-10-17 20:01:49 +02:00
semantic-release-bot
5024204046 chore: Release v20.0.2-dev.1 [skip ci]
## [20.0.2-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1...v20.0.2-dev.1) (2024-10-15)

### Bug Fixes

* Make it work on Android 12 and lower by using existing APIs ([#312](https://github.com/ReVanced/revanced-patcher/issues/312)) ([a44802e](a44802ef4e))
2024-10-15 11:41:01 +00:00
LisoUseInAIKyrios
a44802ef4e fix: Make it work on Android 12 and lower by using existing APIs (#312)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2024-10-15 13:39:33 +02:00
semantic-release-bot
4c1c34ad01 chore: Release v20.0.1 [skip ci]
## [20.0.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0...v20.0.1) (2024-10-13)

### Bug Fixes

* Check for class type exactly instead of with contains ([#310](https://github.com/ReVanced/revanced-patcher/issues/310)) ([69f2f20](69f2f20fd9))
* Make it work on Android by not using APIs from JVM unavailable to Android. ([2be6e97](2be6e97817))
* Use non-nullable type for options ([ea6fc70](ea6fc70caa))

### Performance Improvements

* Free memory earlier and remove negligible lookup maps ([d53aacd](d53aacdad4))
2024-10-13 01:54:23 +00:00
oSumAtrIX
b2aecb726d chore: Merge branch dev to main (#304) 2024-10-13 03:52:31 +02:00
semantic-release-bot
851f9c7885 chore: Release v20.0.1-dev.5 [skip ci]
## [20.0.1-dev.5](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.4...v20.0.1-dev.5) (2024-10-11)

### Bug Fixes

* Use non-nullable type for options ([ea6fc70](ea6fc70caa))
2024-10-11 03:30:03 +00:00
oSumAtrIX
ea6fc70caa fix: Use non-nullable type for options 2024-10-11 05:28:15 +02:00
semantic-release-bot
a2875d1d64 chore: Release v20.0.1-dev.4 [skip ci]
## [20.0.1-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.3...v20.0.1-dev.4) (2024-10-07)

### Bug Fixes

* Make it work on Android by not using APIs from JVM unavailable to Android. ([2be6e97](2be6e97817))
2024-10-07 16:27:20 +00:00
oSumAtrIX
2be6e97817 fix: Make it work on Android by not using APIs from JVM unavailable to Android. 2024-10-07 18:25:43 +02:00
semantic-release-bot
348d0070e7 chore: Release v20.0.1-dev.3 [skip ci]
## [20.0.1-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.2...v20.0.1-dev.3) (2024-10-03)

### Performance Improvements

* Free memory earlier and remove negligible lookup maps ([d53aacd](d53aacdad4))
2024-10-03 14:08:34 +00:00
oSumAtrIX
d53aacdad4 perf: Free memory earlier and remove negligible lookup maps
Negligible lookup maps used for matching fingerprints have been removed to reduce the likelihood of OOM when the maps are instantiated, commonly observed with 400M RAM. Additionally, lookup maps previously kept for the duration of the patcher instance are now cleared before the classes are compiled. This reduces the likelihood of OOM when compiling classes.
On a related note, a linear increase in memory usage is observed with every compiled class until all classes are compiled implying compiled classes not being freed by GC because they are still referenced. After compiling a class, the class is technically free-able though. The classes are assumed to be referenced in the `multidexlib2` library that takes the list of all classes to compile multiple DEX with and seems to hold the reference to all these classes in memory until all DEX are compiled. A clever fix would involve splitting the list of classes into chunks and getting rid of the list of all classes so that after every DEX compilation, the corresponding split of classes can be freed.
2024-10-03 16:06:42 +02:00
25 changed files with 989 additions and 640 deletions

View File

@@ -1,3 +1,143 @@
# [21.1.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.1...v21.1.0-dev.2) (2025-06-20)
### Bug Fixes
* Add back missing log by naming logger correctly ([#332](https://github.com/ReVanced/revanced-patcher/issues/332)) ([e4e66b0](https://github.com/ReVanced/revanced-patcher/commit/e4e66b0d8bb0986b79fb150b9c15da35b8e11561))
* Support UTF-8 chars when compiling instructions in Smali in non UTF-8 environments ([#331](https://github.com/ReVanced/revanced-patcher/issues/331)) ([bb8771b](https://github.com/ReVanced/revanced-patcher/commit/bb8771bb8b8ab1724d957e56f4de88c02684d87b))
### Features
* Use option name as key for simplicity and consistency ([754b02e](https://github.com/ReVanced/revanced-patcher/commit/754b02e4ca66ec10764d5205c6643f2d86d0c6a2))
### Performance Improvements
* Use a buffered writer to reduce IO overhead ([#347](https://github.com/ReVanced/revanced-patcher/issues/347)) ([99f4318](https://github.com/ReVanced/revanced-patcher/commit/99f431897eb9e607987fd5d09b879d7eda442f3e))
# [21.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0...v21.1.0-dev.1) (2024-12-07)
### Features
* Add identity hash code to unnamed patches ([88a3252](https://github.com/ReVanced/revanced-patcher/commit/88a325257494939a79fb30dd51d60c5c52546755))
# [21.0.0](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0) (2024-11-05)
### Bug Fixes
* Match fingerprint before delegating the match property ([5d996de](https://github.com/ReVanced/revanced-patcher/commit/5d996def4d3de4e2bfc34562e5a6c7d89a8cddf0))
* Merge extension only when patch executes ([#315](https://github.com/ReVanced/revanced-patcher/issues/315)) ([aa472eb](https://github.com/ReVanced/revanced-patcher/commit/aa472eb9857145b53b49f843406a9764fbb7e5ce))
### Features
* Improve Fingerprint API ([#316](https://github.com/ReVanced/revanced-patcher/issues/316)) ([0abf1c6](https://github.com/ReVanced/revanced-patcher/commit/0abf1c6c0279708fdef5cb66b141d07d17682693))
* Improve various APIs ([#317](https://github.com/ReVanced/revanced-patcher/issues/317)) ([b824978](https://github.com/ReVanced/revanced-patcher/commit/b8249789df8b90129f7b7ad0e523a8d0ceaab848))
* Move fingerprint match members to fingerprint for ease of access by using context receivers ([0746c22](https://github.com/ReVanced/revanced-patcher/commit/0746c22743a9561bae2284d234b151f2f8511ca5))
### Performance Improvements
* Use smallest lookup map for strings ([1358d3f](https://github.com/ReVanced/revanced-patcher/commit/1358d3fa10cb8ba011b6b89cfe3684ecf9849d2f))
### BREAKING CHANGES
* Various APIs have been changed.
* Many APIs have been changed.
# [21.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.3...v21.0.0-dev.4) (2024-11-05)
### Performance Improvements
* Use smallest lookup map for strings ([1358d3f](https://github.com/ReVanced/revanced-patcher/commit/1358d3fa10cb8ba011b6b89cfe3684ecf9849d2f))
# [21.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.2...v21.0.0-dev.3) (2024-11-05)
### Features
* Move fingerprint match members to fingerprint for ease of access by using context receivers ([0746c22](https://github.com/ReVanced/revanced-patcher/commit/0746c22743a9561bae2284d234b151f2f8511ca5))
# [21.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.1...v21.0.0-dev.2) (2024-11-01)
### Bug Fixes
* Match fingerprint before delegating the match property ([5d996de](https://github.com/ReVanced/revanced-patcher/commit/5d996def4d3de4e2bfc34562e5a6c7d89a8cddf0))
# [21.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0-dev.1) (2024-10-27)
### Bug Fixes
* Merge extension only when patch executes ([#315](https://github.com/ReVanced/revanced-patcher/issues/315)) ([aa472eb](https://github.com/ReVanced/revanced-patcher/commit/aa472eb9857145b53b49f843406a9764fbb7e5ce))
### Features
* Improve Fingerprint API ([#316](https://github.com/ReVanced/revanced-patcher/issues/316)) ([0abf1c6](https://github.com/ReVanced/revanced-patcher/commit/0abf1c6c0279708fdef5cb66b141d07d17682693))
* Improve various APIs ([#317](https://github.com/ReVanced/revanced-patcher/issues/317)) ([b824978](https://github.com/ReVanced/revanced-patcher/commit/b8249789df8b90129f7b7ad0e523a8d0ceaab848))
### BREAKING CHANGES
* Various APIs have been changed.
* Many APIs have been changed.
## [20.0.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1...v20.0.2) (2024-10-17)
### Bug Fixes
* Make it work on Android 12 and lower by using existing APIs ([#312](https://github.com/ReVanced/revanced-patcher/issues/312)) ([a44802e](https://github.com/ReVanced/revanced-patcher/commit/a44802ef4ebf59ae47213854ba761c81dadc51f3))
## [20.0.2-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1...v20.0.2-dev.1) (2024-10-15)
### Bug Fixes
* Make it work on Android 12 and lower by using existing APIs ([#312](https://github.com/ReVanced/revanced-patcher/issues/312)) ([a44802e](https://github.com/ReVanced/revanced-patcher/commit/a44802ef4ebf59ae47213854ba761c81dadc51f3))
## [20.0.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0...v20.0.1) (2024-10-13)
### Bug Fixes
* Check for class type exactly instead of with contains ([#310](https://github.com/ReVanced/revanced-patcher/issues/310)) ([69f2f20](https://github.com/ReVanced/revanced-patcher/commit/69f2f20fd99162f91cd9c531dfe47d00d3152ead))
* Make it work on Android by not using APIs from JVM unavailable to Android. ([2be6e97](https://github.com/ReVanced/revanced-patcher/commit/2be6e97817437f40e17893dfff3bea2cd4c3ff9e))
* Use non-nullable type for options ([ea6fc70](https://github.com/ReVanced/revanced-patcher/commit/ea6fc70caab055251ad4d0d3f1b5cf53865abb85))
### Performance Improvements
* Free memory earlier and remove negligible lookup maps ([d53aacd](https://github.com/ReVanced/revanced-patcher/commit/d53aacdad4ed3750ddae526fb307577ea36e6171))
## [20.0.1-dev.5](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.4...v20.0.1-dev.5) (2024-10-11)
### Bug Fixes
* Use non-nullable type for options ([ea6fc70](https://github.com/ReVanced/revanced-patcher/commit/ea6fc70caab055251ad4d0d3f1b5cf53865abb85))
## [20.0.1-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.3...v20.0.1-dev.4) (2024-10-07)
### Bug Fixes
* Make it work on Android by not using APIs from JVM unavailable to Android. ([2be6e97](https://github.com/ReVanced/revanced-patcher/commit/2be6e97817437f40e17893dfff3bea2cd4c3ff9e))
## [20.0.1-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.2...v20.0.1-dev.3) (2024-10-03)
### Performance Improvements
* Free memory earlier and remove negligible lookup maps ([d53aacd](https://github.com/ReVanced/revanced-patcher/commit/d53aacdad4ed3750ddae526fb307577ea36e6171))
## [20.0.1-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.1...v20.0.1-dev.2) (2024-10-01) ## [20.0.1-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.1...v20.0.1-dev.2) (2024-10-01)
## [20.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0...v20.0.1-dev.1) (2024-09-18) ## [20.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0...v20.0.1-dev.1) (2024-09-18)

View File

@@ -1,7 +1,22 @@
public final class app/revanced/patcher/Fingerprint { public final class app/revanced/patcher/Fingerprint {
public final fun getMatch ()Lapp/revanced/patcher/Match; public final fun getClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z public final fun getClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Z public final fun getMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
public final fun getMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
public final fun getOriginalClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
public final fun getOriginalClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
public final fun getOriginalMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
public final fun getOriginalMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
public final fun getPatternMatch (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
public final fun getPatternMatchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
public final fun getStringMatches (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
public final fun getStringMatchesOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
} }
public final class app/revanced/patcher/FingerprintBuilder { public final class app/revanced/patcher/FingerprintBuilder {
@@ -18,32 +33,27 @@ public final class app/revanced/patcher/FingerprintBuilder {
public final class app/revanced/patcher/FingerprintKt { public final class app/revanced/patcher/FingerprintKt {
public static final fun fingerprint (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/Fingerprint; public static final fun fingerprint (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/Fingerprint;
public static final fun fingerprint (Lapp/revanced/patcher/patch/BytecodePatchBuilder;ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint;
public static synthetic fun fingerprint$default (ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/Fingerprint; public static synthetic fun fingerprint$default (ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/Fingerprint;
public static synthetic fun fingerprint$default (Lapp/revanced/patcher/patch/BytecodePatchBuilder;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint;
} }
public abstract interface annotation class app/revanced/patcher/InternalApi : java/lang/annotation/Annotation { public abstract interface annotation class app/revanced/patcher/InternalApi : java/lang/annotation/Annotation {
} }
public final class app/revanced/patcher/Match { public final class app/revanced/patcher/Match {
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lapp/revanced/patcher/Match$PatternMatch;Ljava/util/List;Lapp/revanced/patcher/patch/BytecodePatchContext;)V public final fun getClassDef ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
public final fun getClassDef ()Lcom/android/tools/smali/dexlib2/iface/ClassDef; public final fun getMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
public final fun getMethod ()Lcom/android/tools/smali/dexlib2/iface/Method; public final fun getOriginalClassDef ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
public final fun getMutableClass ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass; public final fun getOriginalMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
public final fun getMutableMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
public final fun getPatternMatch ()Lapp/revanced/patcher/Match$PatternMatch; public final fun getPatternMatch ()Lapp/revanced/patcher/Match$PatternMatch;
public final fun getStringMatches ()Ljava/util/List; public final fun getStringMatches ()Ljava/util/List;
} }
public final class app/revanced/patcher/Match$PatternMatch { public final class app/revanced/patcher/Match$PatternMatch {
public fun <init> (II)V
public final fun getEndIndex ()I public final fun getEndIndex ()I
public final fun getStartIndex ()I public final fun getStartIndex ()I
} }
public final class app/revanced/patcher/Match$StringMatch { public final class app/revanced/patcher/Match$StringMatch {
public fun <init> (Ljava/lang/String;I)V
public final fun getIndex ()I public final fun getIndex ()I
public final fun getString ()Ljava/lang/String; public final fun getString ()Ljava/lang/String;
} }
@@ -63,8 +73,10 @@ public final class app/revanced/patcher/Patcher : java/io/Closeable {
} }
public final class app/revanced/patcher/PatcherConfig { public final class app/revanced/patcher/PatcherConfig {
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Z)V public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
} }
public final class app/revanced/patcher/PatcherContext : java/io/Closeable { public final class app/revanced/patcher/PatcherContext : java/io/Closeable {
@@ -135,40 +147,36 @@ public final class app/revanced/patcher/extensions/InstructionExtensions {
} }
public final class app/revanced/patcher/patch/BytecodePatch : app/revanced/patcher/patch/Patch { public final class app/revanced/patcher/patch/BytecodePatch : app/revanced/patcher/patch/Patch {
public final fun getExtension ()Ljava/io/InputStream; public final fun getExtensionInputStream ()Ljava/util/function/Supplier;
public final fun getFingerprints ()Ljava/util/Set;
public fun toString ()Ljava/lang/String; public fun toString ()Ljava/lang/String;
} }
public final class app/revanced/patcher/patch/BytecodePatchBuilder : app/revanced/patcher/patch/PatchBuilder { public final class app/revanced/patcher/patch/BytecodePatchBuilder : app/revanced/patcher/patch/PatchBuilder {
public synthetic fun build$revanced_patcher ()Lapp/revanced/patcher/patch/Patch; public synthetic fun build$revanced_patcher ()Lapp/revanced/patcher/patch/Patch;
public final fun extendWith (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatchBuilder; public final fun extendWith (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatchBuilder;
public final fun getExtension ()Ljava/io/InputStream; public final fun getExtensionInputStream ()Ljava/util/function/Supplier;
public final fun invoke (Lapp/revanced/patcher/Fingerprint;)Lapp/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint; public final fun setExtensionInputStream (Ljava/util/function/Supplier;)V
public final fun setExtension (Ljava/io/InputStream;)V
}
public final class app/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint {
public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/Match;
} }
public final class app/revanced/patcher/patch/BytecodePatchContext : app/revanced/patcher/patch/PatchContext, java/io/Closeable { public final class app/revanced/patcher/patch/BytecodePatchContext : app/revanced/patcher/patch/PatchContext, java/io/Closeable {
public final fun classBy (Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/proxy/ClassProxy; public final fun classBy (Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/proxy/ClassProxy;
public final fun classByType (Ljava/lang/String;)Lapp/revanced/patcher/util/proxy/ClassProxy;
public fun close ()V public fun close ()V
public synthetic fun get ()Ljava/lang/Object; public synthetic fun get ()Ljava/lang/Object;
public fun get ()Ljava/util/Set; public fun get ()Ljava/util/Set;
public final fun getClasses ()Lapp/revanced/patcher/util/ProxyClassList; public final fun getClasses ()Lapp/revanced/patcher/util/ProxyClassList;
public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/MethodNavigator; public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)Lapp/revanced/patcher/util/MethodNavigator;
public final fun proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy; public final fun proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
} }
public final class app/revanced/patcher/patch/Option { public final class app/revanced/patcher/patch/Option {
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getDefault ()Ljava/lang/Object; public final fun getDefault ()Ljava/lang/Object;
public final fun getDescription ()Ljava/lang/String; public final fun getDescription ()Ljava/lang/String;
public final fun getKey ()Ljava/lang/String; public final fun getKey ()Ljava/lang/String;
public final fun getName ()Ljava/lang/String;
public final fun getRequired ()Z public final fun getRequired ()Z
public final fun getTitle ()Ljava/lang/String; public final fun getTitle ()Ljava/lang/String;
public final fun getType ()Lkotlin/reflect/KType; public final fun getType ()Lkotlin/reflect/KType;
@@ -286,7 +294,7 @@ public final class app/revanced/patcher/patch/Options : java/util/Map, kotlin/jv
} }
public abstract class app/revanced/patcher/patch/Patch { public abstract class app/revanced/patcher/patch/Patch {
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Set;Ljava/util/Set;Ljava/util/Set;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Set;Ljava/util/Set;Ljava/util/Set;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun execute (Lapp/revanced/patcher/patch/PatchContext;)V public final fun execute (Lapp/revanced/patcher/patch/PatchContext;)V
public final fun finalize (Lapp/revanced/patcher/patch/PatchContext;)V public final fun finalize (Lapp/revanced/patcher/patch/PatchContext;)V
public final fun getCompatiblePackages ()Ljava/util/Set; public final fun getCompatiblePackages ()Ljava/util/Set;
@@ -303,13 +311,13 @@ public abstract class app/revanced/patcher/patch/PatchBuilder {
public final fun compatibleWith ([Ljava/lang/String;)V public final fun compatibleWith ([Ljava/lang/String;)V
public final fun compatibleWith ([Lkotlin/Pair;)V public final fun compatibleWith ([Lkotlin/Pair;)V
public final fun dependsOn ([Lapp/revanced/patcher/patch/Patch;)V public final fun dependsOn ([Lapp/revanced/patcher/patch/Patch;)V
public final fun execute (Lkotlin/jvm/functions/Function2;)V public final fun execute (Lkotlin/jvm/functions/Function1;)V
public final fun finalize (Lkotlin/jvm/functions/Function2;)V public final fun finalize (Lkotlin/jvm/functions/Function1;)V
protected final fun getCompatiblePackages ()Ljava/util/Set; protected final fun getCompatiblePackages ()Ljava/util/Set;
protected final fun getDependencies ()Ljava/util/Set; protected final fun getDependencies ()Ljava/util/Set;
protected final fun getDescription ()Ljava/lang/String; protected final fun getDescription ()Ljava/lang/String;
protected final fun getExecutionBlock ()Lkotlin/jvm/functions/Function2; protected final fun getExecutionBlock ()Lkotlin/jvm/functions/Function1;
protected final fun getFinalizeBlock ()Lkotlin/jvm/functions/Function2; protected final fun getFinalizeBlock ()Lkotlin/jvm/functions/Function1;
protected final fun getName ()Ljava/lang/String; protected final fun getName ()Ljava/lang/String;
protected final fun getOptions ()Ljava/util/Set; protected final fun getOptions ()Ljava/util/Set;
protected final fun getUse ()Z protected final fun getUse ()Z
@@ -317,8 +325,8 @@ public abstract class app/revanced/patcher/patch/PatchBuilder {
public final fun invoke (Ljava/lang/String;[Ljava/lang/String;)Lkotlin/Pair; public final fun invoke (Ljava/lang/String;[Ljava/lang/String;)Lkotlin/Pair;
protected final fun setCompatiblePackages (Ljava/util/Set;)V protected final fun setCompatiblePackages (Ljava/util/Set;)V
protected final fun setDependencies (Ljava/util/Set;)V protected final fun setDependencies (Ljava/util/Set;)V
protected final fun setExecutionBlock (Lkotlin/jvm/functions/Function2;)V protected final fun setExecutionBlock (Lkotlin/jvm/functions/Function1;)V
protected final fun setFinalizeBlock (Lkotlin/jvm/functions/Function2;)V protected final fun setFinalizeBlock (Lkotlin/jvm/functions/Function1;)V
} }
public abstract interface class app/revanced/patcher/patch/PatchContext : java/util/function/Supplier { public abstract interface class app/revanced/patcher/patch/PatchContext : java/util/function/Supplier {
@@ -395,18 +403,13 @@ public final class app/revanced/patcher/patch/ResourcePatchBuilder : app/revance
} }
public final class app/revanced/patcher/patch/ResourcePatchContext : app/revanced/patcher/patch/PatchContext { public final class app/revanced/patcher/patch/ResourcePatchContext : app/revanced/patcher/patch/PatchContext {
public final fun delete (Ljava/lang/String;)Z
public final fun document (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
public final fun document (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
public fun get ()Lapp/revanced/patcher/PatcherResult$PatchedResources; public fun get ()Lapp/revanced/patcher/PatcherResult$PatchedResources;
public synthetic fun get ()Ljava/lang/Object; public synthetic fun get ()Ljava/lang/Object;
public final fun get (Ljava/lang/String;Z)Ljava/io/File; public final fun get (Ljava/lang/String;Z)Ljava/io/File;
public static synthetic fun get$default (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File; public static synthetic fun get$default (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
public final fun getDocument ()Lapp/revanced/patcher/patch/ResourcePatchContext$DocumentOperatable;
public final fun stageDelete (Lkotlin/jvm/functions/Function1;)Z
}
public final class app/revanced/patcher/patch/ResourcePatchContext$DocumentOperatable {
public fun <init> (Lapp/revanced/patcher/patch/ResourcePatchContext;)V
public final fun get (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
public final fun get (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
} }
public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w3c/dom/Document { public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w3c/dom/Document {
@@ -482,11 +485,12 @@ public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w
} }
public final class app/revanced/patcher/util/MethodNavigator { public final class app/revanced/patcher/util/MethodNavigator {
public final fun at (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator; public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
public final fun at ([I)Lapp/revanced/patcher/util/MethodNavigator; public final fun original ()Lcom/android/tools/smali/dexlib2/iface/Method;
public static synthetic fun at$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator; public final fun stop ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
public final fun immutable ()Lcom/android/tools/smali/dexlib2/iface/Method; public final fun to (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
public final fun mutable ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod; public final fun to ([I)Lapp/revanced/patcher/util/MethodNavigator;
public static synthetic fun to$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
} }
public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList { public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList {

View File

@@ -56,6 +56,8 @@ dependencies {
kotlin { kotlin {
compilerOptions { compilerOptions {
jvmTarget.set(JvmTarget.JVM_11) jvmTarget.set(JvmTarget.JVM_11)
freeCompilerArgs = listOf("-Xcontext-receivers")
} }
} }

View File

@@ -89,9 +89,9 @@ val patcherResult = Patcher(PatcherConfig(apkFile = File("some.apk"))).use { pat
runBlocking { runBlocking {
patcher().collect { patchResult -> patcher().collect { patchResult ->
if (patchResult.exception != null) if (patchResult.exception != null)
logger.info("\"${patchResult.patch}\" failed:\n${patchResult.exception}") logger.info { "\"${patchResult.patch}\" failed:\n${patchResult.exception}" }
else else
logger.info("\"${patchResult.patch}\" succeeded") logger.info { "\"${patchResult.patch}\" succeeded" }
} }
} }

View File

@@ -72,6 +72,10 @@ To start developing patches with ReVanced Patcher, you must prepare a developmen
Throughout the documentation, [ReVanced Patches](https://github.com/revanced/revanced-patches) will be used as an example project. Throughout the documentation, [ReVanced Patches](https://github.com/revanced/revanced-patches) will be used as an example project.
> [!NOTE]
> To start a fresh project,
> you can use the [ReVanced Patches template](https://github.com/revanced/revanced-patches-template).
1. Clone the repository 1. Clone the repository
```bash ```bash

File diff suppressed because it is too large Load Diff

View File

@@ -76,23 +76,23 @@ val disableAdsPatch = bytecodePatch(
) { ) {
compatibleWith("com.some.app"("1.0.0")) compatibleWith("com.some.app"("1.0.0"))
// Resource patch disables ads by patching resource files. // Patches can depend on other patches, executing them first.
dependsOn(disableAdsResourcePatch) dependsOn(disableAdsResourcePatch)
// Precompiled DEX file to be merged into the patched app. // Merge precompiled DEX files into the patched app, before the patch is executed.
extendWith("disable-ads.rve") extendWith("disable-ads.rve")
// Fingerprint to find the method to patch.
val showAdsMatch by showAdsFingerprint {
// More about fingerprints on the next page of the documentation.
}
// Business logic of the patch to disable ads in the app. // Business logic of the patch to disable ads in the app.
execute { execute {
// Fingerprint to find the method to patch.
val showAdsFingerprint = fingerprint {
// More about fingerprints on the next page of the documentation.
}
// In the method that shows ads, // In the method that shows ads,
// call DisableAdsPatch.shouldDisableAds() from the extension (precompiled DEX file) // call DisableAdsPatch.shouldDisableAds() from the extension (precompiled DEX file)
// to enable or disable ads. // to enable or disable ads.
showAdsMatch.mutableMethod.addInstructions( showAdsFingerprint.method.addInstructions(
0, 0,
""" """
invoke-static {}, LDisableAdsPatch;->shouldDisableAds()Z invoke-static {}, LDisableAdsPatch;->shouldDisableAds()Z
@@ -122,10 +122,10 @@ To define an option, use the available `option` functions:
```kt ```kt
val patch = bytecodePatch(name = "Patch") { val patch = bytecodePatch(name = "Patch") {
// Add an inbuilt option and delegate it to a property. // Add an inbuilt option and delegate it to a property.
val value by stringOption(key = "option") val value by stringOption(name = "Inbuilt option")
// Add an option with a custom type and delegate it to a property. // Add an option with a custom type and delegate it to a property.
val string by option<String>(key = "string") val string by option<String>(name = "String option")
execute { execute {
println(value) println(value)
@@ -139,20 +139,20 @@ Options of a patch can be set after loading the patches with `PatchLoader` by ob
```kt ```kt
loadPatchesJar(patches).apply { loadPatchesJar(patches).apply {
// Type is checked at runtime. // Type is checked at runtime.
first { it.name == "Patch" }.options["option"] = "Value" first { it.name == "Patch" }.options["Option"] = "Value"
} }
``` ```
The type of an option can be obtained from the `type` property of the option: The type of an option can be obtained from the `type` property of the option:
```kt ```kt
option.type // The KType of the option. option.type // The KType of the option. Captures the full type information of the option.
``` ```
Options can be declared outside of a patch and added to a patch manually: Options can be declared outside a patch and added to a patch manually:
```kt ```kt
val option = stringOption(key = "option") val option = stringOption(name = "Option")
bytecodePatch(name = "Patch") { bytecodePatch(name = "Patch") {
val value by option() val value by option()
@@ -184,10 +184,8 @@ and use it in a patch:
val patch = bytecodePatch(name = "Complex patch") { val patch = bytecodePatch(name = "Complex patch") {
extendWith("complex-patch.rve") extendWith("complex-patch.rve")
val match by methodFingerprint()
execute { execute {
match.mutableMethod.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V") fingerprint.method.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V")
} }
} }
``` ```

View File

@@ -96,21 +96,21 @@ Example of patches:
@Surpress("unused") @Surpress("unused")
val bytecodePatch = bytecodePatch { val bytecodePatch = bytecodePatch {
execute { execute {
// TODO // More about this on the next page of the documentation.
} }
} }
@Surpress("unused") @Surpress("unused")
val rawResourcePatch = rawResourcePatch { val rawResourcePatch = rawResourcePatch {
execute { execute {
// TODO // More about this on the next page of the documentation.
} }
} }
@Surpress("unused") @Surpress("unused")
val resourcePatch = resourcePatch { val resourcePatch = resourcePatch {
execute { execute {
// TODO // More about this on the next page of the documentation.
} }
} }
``` ```

View File

@@ -4,18 +4,107 @@ A handful of APIs are available to make patch development easier and more effici
## 📙 Overview ## 📙 Overview
1. 👹 Mutate classes with `context.proxy(ClassDef)` 1. 👹 Create mutable replacements of classes with `proxy(ClassDef)`
2. 🔍 Find and proxy existing classes with `classBy(Predicate)` and `classByType(String)` 2. 🔍 Find and create mutable replaces with `classBy(Predicate)`
3. 🏃‍ Easily access referenced methods recursively by index with `MethodNavigator` 3. 🏃‍ Navigate method calls recursively by index with `navigate(Method)`
4. 🔨 Make use of extension functions from `BytecodeUtils` and `ResourceUtils` with certain applications 4. 💾 Read and write resource files with `get(String, Boolean)` and `delete(String)`
(Available in ReVanced Patches) 5. 📃 Read and write DOM files using `document(String)` and `document(InputStream)`
5. 💾 Read and write (decoded) resources with `ResourcePatchContext.get(Path, Boolean)`
6. 📃 Read and write DOM files using `ResourcePatchContext.document`
### 🧰 APIs ### 🧰 APIs
> [!WARNING] #### 👹 `proxy(ClassDef)`
> This section is still under construction and may be incomplete.
By default, the classes are immutable, meaning they cannot be modified.
To make a class mutable, use the `proxy(ClassDef)` function.
This function creates a lazy mutable copy of the class definition.
Accessing the property will replace the original class definition with the mutable copy,
thus allowing you to make changes to the class. Subsequent accesses will return the same mutable copy.
```kt
execute {
val mutableClass = proxy(classDef)
mutableClass.methods.add(Method())
}
```
#### 🔍 `classBy(Predicate)`
The `classBy(Predicate)` function is an alternative to finding and creating mutable classes by a predicate.
It automatically proxies the class definition, making it mutable.
```kt
execute {
// Alternative to proxy(classes.find { it.name == "Lcom/example/MyClass;" })?.classDef
val classDef = classBy { it.name == "Lcom/example/MyClass;" }?.classDef
}
```
#### 🏃‍ `navigate(Method).at(index)`
The `navigate(Method)` function allows you to navigate method calls recursively by index.
```kt
execute {
// Sequentially navigate to the instructions at index 1 within 'someMethod'.
val method = navigate(someMethod).to(1).original() // original() returns the original immutable method.
// Further navigate to the second occurrence where the instruction's opcode is 'INVOKEVIRTUAL'.
// stop() returns the mutable copy of the method.
val method = navigate(someMethod).to(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
// Alternatively, to stop(), you can delegate the method to a variable.
val method by navigate(someMethod).to(1)
// You can chain multiple calls to at() to navigate deeper into the method.
val method by navigate(someMethod).to(1).to(2, 3, 4).to(5)
}
```
#### 💾 `get(String, Boolean)` and `delete(String)`
The `get(String, Boolean)` function returns a `File` object that can be used to read and write resource files.
```kt
execute {
val file = get("res/values/strings.xml")
val content = file.readText()
file.writeText(content)
}
```
The `delete` function can mark files for deletion when the APK is rebuilt.
```kt
execute {
delete("res/values/strings.xml")
}
```
#### 📃 `document(String)` and `document(InputStream)`
The `document` function is used to read and write DOM files.
```kt
execute {
document("res/values/strings.xml").use { document ->
val element = doc.createElement("string").apply {
textContent = "Hello, World!"
}
document.documentElement.appendChild(element)
}
}
```
You can also read documents from an `InputStream`:
```kt
execute {
val inputStream = classLoader.getResourceAsStream("some.xml")
document(inputStream).use { document ->
// ...
}
}
```
## 🎉 Afterword ## 🎉 Afterword
@@ -23,5 +112,6 @@ ReVanced Patcher is a powerful library to patch Android applications, offering a
that outlive app updates. Patches make up ReVanced; without you, the community of patch developers, that outlive app updates. Patches make up ReVanced; without you, the community of patch developers,
ReVanced would not be what it is today. We hope that this documentation has been helpful to you ReVanced would not be what it is today. We hope that this documentation has been helpful to you
and are excited to see what you will create with ReVanced Patcher. If you have any questions or need help, and are excited to see what you will create with ReVanced Patcher. If you have any questions or need help,
talk to us on one of our platforms linked on [revanced.app](https://revanced.app) or open an issue in case of a bug or feature request, talk to us on one of our platforms linked on [revanced.app](https://revanced.app) or open an issue in case of a bug or
feature request,
ReVanced ReVanced

View File

@@ -1,3 +1,3 @@
org.gradle.parallel = true org.gradle.parallel = true
org.gradle.caching = true org.gradle.caching = true
version = 20.0.1-dev.2 version = 21.1.0-dev.2

View File

@@ -1,14 +1,14 @@
[versions] [versions]
android = "4.1.1.4" android = "4.1.1.4"
apktool-lib = "2.9.3" apktool-lib = "2.10.1.1"
binary-compatibility-validator = "0.15.1" binary-compatibility-validator = "0.15.1"
kotlin = "2.0.0" kotlin = "2.0.20"
kotlinx-coroutines-core = "1.8.1" kotlinx-coroutines-core = "1.8.1"
mockk = "1.13.10" mockk = "1.13.10"
multidexlib2 = "3.0.3.r3" multidexlib2 = "3.0.3.r3"
# Tracking https://github.com/google/smali/issues/64. # Tracking https://github.com/google/smali/issues/64.
#noinspection GradleDependency #noinspection GradleDependency
smali = "3.0.5" smali = "3.0.8"
xpp3 = "1.1.4c" xpp3 = "1.1.4c"
[libraries] [libraries]

File diff suppressed because it is too large Load Diff

View File

@@ -91,20 +91,14 @@ class Patcher(private val config: PatcherConfig) : Closeable {
}.also { executedPatches[this] = it } }.also { executedPatches[this] = it }
} }
// Prevent from decoding the app manifest twice if it is not needed. // Prevent decoding the app manifest twice if it is not needed.
if (config.resourceMode != ResourcePatchContext.ResourceMode.NONE) { if (config.resourceMode != ResourcePatchContext.ResourceMode.NONE) {
context.resourceContext.decodeResources(config.resourceMode) context.resourceContext.decodeResources(config.resourceMode)
} }
logger.info("Merging extensions") logger.info("Initializing lookup maps")
context.executablePatches.forEachRecursively { patch -> // Accessing the lazy lookup maps to initialize them.
if (patch is BytecodePatch && patch.extension != null) {
context.bytecodeContext.merge(patch.extension)
}
}
// Initialize lookup maps.
context.bytecodeContext.lookupMaps context.bytecodeContext.lookupMaps
logger.info("Executing patches") logger.info("Executing patches")

View File

@@ -12,16 +12,32 @@ import java.util.logging.Logger
* @param temporaryFilesPath A path to a folder to store temporary files in. * @param temporaryFilesPath A path to a folder to store temporary files in.
* @param aaptBinaryPath A path to a custom aapt binary. * @param aaptBinaryPath A path to a custom aapt binary.
* @param frameworkFileDirectory A path to the directory to cache the framework file in. * @param frameworkFileDirectory A path to the directory to cache the framework file in.
* @param multithreadingDexFileWriter Whether to use multiple threads for writing dex files.
* This has impact on memory usage and performance.
*/ */
class PatcherConfig( class PatcherConfig(
internal val apkFile: File, internal val apkFile: File,
private val temporaryFilesPath: File = File("revanced-temporary-files"), private val temporaryFilesPath: File = File("revanced-temporary-files"),
aaptBinaryPath: File? = null,
frameworkFileDirectory: String? = null,
) {
/**
* The configuration for the patcher.
*
* @param apkFile The apk file to patch.
* @param temporaryFilesPath A path to a folder to store temporary files in.
* @param aaptBinaryPath A path to a custom aapt binary.
* @param frameworkFileDirectory A path to the directory to cache the framework file in.
*/
@Deprecated(
"Use the constructor with a File for aaptBinaryPath instead.",
ReplaceWith("PatcherConfig(apkFile, temporaryFilesPath, aaptBinaryPath?.let { File(it) }, frameworkFileDirectory)"),
)
constructor(
apkFile: File,
temporaryFilesPath: File = File("revanced-temporary-files"),
aaptBinaryPath: String? = null, aaptBinaryPath: String? = null,
frameworkFileDirectory: String? = null, frameworkFileDirectory: String? = null,
internal val multithreadingDexFileWriter: Boolean = false, ) : this(apkFile, temporaryFilesPath, aaptBinaryPath?.let { File(it) }, frameworkFileDirectory)
) {
private val logger = Logger.getLogger(PatcherConfig::class.java.name) private val logger = Logger.getLogger(PatcherConfig::class.java.name)
/** /**
@@ -36,8 +52,7 @@ class PatcherConfig(
*/ */
internal val resourceConfig = internal val resourceConfig =
Config.getDefaultConfig().apply { Config.getDefaultConfig().apply {
useAapt2 = true aaptBinary = aaptBinaryPath
aaptPath = aaptBinaryPath ?: ""
frameworkDirectory = frameworkFileDirectory frameworkDirectory = frameworkFileDirectory
} }

View File

@@ -29,12 +29,12 @@ class PatcherResult internal constructor(
* @param resourcesApk The compiled resources.apk file. * @param resourcesApk The compiled resources.apk file.
* @param otherResources The directory containing other resources files. * @param otherResources The directory containing other resources files.
* @param doNotCompress List of files that should not be compressed. * @param doNotCompress List of files that should not be compressed.
* @param deleteResources List of predicates about resources that should be deleted. * @param deleteResources List of resources that should be deleted.
*/ */
class PatchedResources internal constructor( class PatchedResources internal constructor(
val resourcesApk: File?, val resourcesApk: File?,
val otherResources: File?, val otherResources: File?,
val doNotCompress: Set<String>, val doNotCompress: Set<String>,
val deleteResources: Set<(String) -> Boolean>, val deleteResources: Set<String>,
) )
} }

View File

@@ -14,6 +14,7 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.DexFile import com.android.tools.smali.dexlib2.iface.DexFile
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.iface.reference.StringReference
import lanchon.multidexlib2.BasicDexFileNamer import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.DexIO import lanchon.multidexlib2.DexIO
@@ -21,7 +22,6 @@ import lanchon.multidexlib2.MultiDexIO
import lanchon.multidexlib2.RawDexIO import lanchon.multidexlib2.RawDexIO
import java.io.Closeable import java.io.Closeable
import java.io.FileFilter import java.io.FileFilter
import java.io.InputStream
import java.util.* import java.util.*
import java.util.logging.Logger import java.util.logging.Logger
@@ -34,7 +34,7 @@ import java.util.logging.Logger
class BytecodePatchContext internal constructor(private val config: PatcherConfig) : class BytecodePatchContext internal constructor(private val config: PatcherConfig) :
PatchContext<Set<PatcherResult.PatchedDexFile>>, PatchContext<Set<PatcherResult.PatchedDexFile>>,
Closeable { Closeable {
private val logger = Logger.getLogger(BytecodePatchContext::class.java.name) private val logger = Logger.getLogger(this::class.java.name)
/** /**
* [Opcodes] of the supplied [PatcherConfig.apkFile]. * [Opcodes] of the supplied [PatcherConfig.apkFile].
@@ -60,31 +60,24 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
internal val lookupMaps by lazy { LookupMaps(classes) } internal val lookupMaps by lazy { LookupMaps(classes) }
/** /**
* A map for lookup by [merge]. * Merge the extension of [bytecodePatch] into the [BytecodePatchContext].
*/ * If no extension is present, the function will return early.
internal val classesByType = mutableMapOf<String, ClassDef>().apply {
classes.forEach { classDef -> put(classDef.type, classDef) }
}
/**
* Merge an extension to [classes].
* *
* @param extensionInputStream The input stream of the extension to merge. * @param bytecodePatch The [BytecodePatch] to merge the extension of.
*/ */
internal fun merge(extensionInputStream: InputStream) { internal fun mergeExtension(bytecodePatch: BytecodePatch) {
val extension = extensionInputStream.readAllBytes() bytecodePatch.extensionInputStream?.get()?.use { extensionStream ->
RawDexIO.readRawDexFile(extensionStream, 0, null).classes.forEach { classDef ->
RawDexIO.readRawDexFile(extension, 0, null).classes.forEach { classDef -> val existingClass = lookupMaps.classesByType[classDef.type] ?: run {
val existingClass = classesByType[classDef.type] ?: run { logger.fine { "Adding class \"$classDef\"" }
logger.fine("Adding class \"$classDef\"")
classes += classDef classes += classDef
classesByType[classDef.type] = classDef lookupMaps.classesByType[classDef.type] = classDef
return@forEach return@forEach
} }
logger.fine("Class \"$classDef\" exists already. Adding missing methods and fields.") logger.fine { "Class \"$classDef\" exists already. Adding missing methods and fields." }
existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass -> existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
// If the class was merged, replace the original class with the merged class. // If the class was merged, replace the original class with the merged class.
@@ -96,17 +89,9 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
classes += mergedClass classes += mergedClass
} }
} }
} ?: logger.fine("No extension to merge")
} }
/**
* Find a class by its type using a contains check.
*
* @param type The type of the class.
* @return A proxy for the first class that matches the type.
*/
@Deprecated("Use classBy { type in it.type } instead.", ReplaceWith("classBy { type in it.type }"))
fun classByType(type: String) = classBy { type in it.type }
/** /**
* Find a class with a predicate. * Find a class with a predicate.
* *
@@ -123,9 +108,9 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
* *
* @return A proxy for the class. * @return A proxy for the class.
*/ */
fun proxy(classDef: ClassDef) = this@BytecodePatchContext.classes.proxyPool.find { fun proxy(classDef: ClassDef) = classes.proxyPool.find {
it.immutableClass.type == classDef.type it.immutableClass.type == classDef.type
} ?: ClassProxy(classDef).also { this@BytecodePatchContext.classes.proxyPool.add(it) } } ?: ClassProxy(classDef).also { classes.proxyPool.add(it) }
/** /**
* Navigate a method. * Navigate a method.
@@ -134,7 +119,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
* *
* @return A [MethodNavigator] for the method. * @return A [MethodNavigator] for the method.
*/ */
fun navigate(method: Method) = MethodNavigator(this@BytecodePatchContext, method) fun navigate(method: MethodReference) = MethodNavigator(method)
/** /**
* Compile bytecode from the [BytecodePatchContext]. * Compile bytecode from the [BytecodePatchContext].
@@ -145,6 +130,9 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
override fun get(): Set<PatcherResult.PatchedDexFile> { override fun get(): Set<PatcherResult.PatchedDexFile> {
logger.info("Compiling patched dex files") logger.info("Compiling patched dex files")
// Free up memory before compiling the dex files.
lookupMaps.close()
val patchedDexFileResults = val patchedDexFileResults =
config.patchedFiles.resolve("dex").also { config.patchedFiles.resolve("dex").also {
it.deleteRecursively() // Make sure the directory is empty. it.deleteRecursively() // Make sure the directory is empty.
@@ -152,7 +140,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
}.apply { }.apply {
MultiDexIO.writeDexFile( MultiDexIO.writeDexFile(
true, true,
if (config.multithreadingDexFileWriter) -1 else 1, -1,
this, this,
BasicDexFileNamer(), BasicDexFileNamer(),
object : DexFile { object : DexFile {
@@ -162,7 +150,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
override fun getOpcodes() = this@BytecodePatchContext.opcodes override fun getOpcodes() = this@BytecodePatchContext.opcodes
}, },
DexIO.DEFAULT_MAX_DEX_POOL_SIZE, DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
) { _, entryName, _ -> logger.info("Compiled $entryName") } ) { _, entryName, _ -> logger.info { "Compiled $entryName" } }
}.listFiles(FileFilter { it.isFile })!!.map { }.listFiles(FileFilter { it.isFile })!!.map {
PatcherResult.PatchedDexFile(it.name, it.inputStream()) PatcherResult.PatchedDexFile(it.name, it.inputStream())
}.toSet() }.toSet()
@@ -178,47 +166,21 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
* @param classes The list of classes to create the lookup maps from. * @param classes The list of classes to create the lookup maps from.
*/ */
internal class LookupMaps internal constructor(classes: List<ClassDef>) : Closeable { internal class LookupMaps internal constructor(classes: List<ClassDef>) : Closeable {
/**
* Classes associated by their type.
*/
internal val classesByType = classes.associateBy { it.type }.toMutableMap()
/**
* All methods and the class they are a member of.
*/
internal val allMethods = MethodClassPairs()
/**
* Methods associated by its access flags, return type and parameter.
*/
internal val methodsBySignature = MethodClassPairsLookupMap()
/** /**
* Methods associated by strings referenced in it. * Methods associated by strings referenced in it.
*/ */
internal val methodsByStrings = MethodClassPairsLookupMap() internal val methodsByStrings = MethodClassPairsLookupMap()
// Lookup map for fast checking if a class exists by its type.
val classesByType = mutableMapOf<String, ClassDef>().apply {
classes.forEach { classDef -> put(classDef.type, classDef) }
}
init { init {
classes.forEach { classDef -> classes.forEach { classDef ->
classDef.methods.forEach { method -> classDef.methods.forEach { method ->
val methodClassPair: MethodClassPair = method to classDef val methodClassPair: MethodClassPair = method to classDef
// For fingerprints with no access or return type specified.
allMethods += methodClassPair
val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
// Add <access><returnType> as the key.
methodsBySignature[accessFlagsReturnKey] = methodClassPair
// Add <access><returnType>[parameters] as the key.
methodsBySignature[
buildString {
append(accessFlagsReturnKey)
appendParameters(method.parameterTypes)
},
] = methodClassPair
// Add strings contained in the method as the key. // Add strings contained in the method as the key.
method.instructionsOrNull?.forEach instructions@{ instruction -> method.instructionsOrNull?.forEach instructions@{ instruction ->
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO) { if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO) {
@@ -236,38 +198,14 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
} }
} }
internal companion object {
/**
* Appends a string based on the parameter reference types of this method.
*/
internal fun StringBuilder.appendParameters(parameters: Iterable<CharSequence>) {
// Maximum parameters to use in the signature key.
// Some apps have methods with an incredible number of parameters (over 100 parameters have been seen).
// To keep the signature map from becoming needlessly bloated,
// group together in the same map entry all methods with the same access/return and 5 or more parameters.
// The value of 5 was chosen based on local performance testing and is not set in stone.
val maxSignatureParameters = 5
// Must append a unique value before the parameters to distinguish this key includes the parameters.
// If this is not appended, then methods with no parameters
// will collide with different keys that specify access/return but omit the parameters.
append("p:")
parameters.forEachIndexed { index, parameter ->
if (index >= maxSignatureParameters) return
append(parameter.first())
}
}
}
override fun close() { override fun close() {
allMethods.clear()
methodsBySignature.clear()
methodsByStrings.clear() methodsByStrings.clear()
classesByType.clear()
} }
} }
override fun close() { override fun close() {
lookupMaps.close() lookupMaps.close()
classesByType.clear()
classes.clear() classes.clear()
} }
} }

View File

@@ -20,16 +20,51 @@ import kotlin.reflect.typeOf
* @constructor Create a new [Option]. * @constructor Create a new [Option].
*/ */
@Suppress("MemberVisibilityCanBePrivate", "unused") @Suppress("MemberVisibilityCanBePrivate", "unused")
class Option<T> @PublishedApi internal constructor( class Option<T>
@PublishedApi
@Deprecated("Use the constructor with the name instead of a key instead.")
internal constructor(
@Deprecated("Use the name property instead.")
val key: String, val key: String,
val default: T? = null, val default: T? = null,
val values: Map<String, T?>? = null, val values: Map<String, T?>? = null,
@Deprecated("Use the name property instead.")
val title: String? = null, val title: String? = null,
val description: String? = null, val description: String? = null,
val required: Boolean = false, val required: Boolean = false,
val type: KType, val type: KType,
val validator: Option<T>.(T?) -> Boolean = { true }, val validator: Option<T>.(T?) -> Boolean = { true },
) { ) {
/**
* The name.
*/
val name = key
/**
* An option.
*
* @param T The value type of the option.
* @param name The name.
* @param default The default value.
* @param values Eligible option values mapped to a human-readable name.
* @param description A description.
* @param required Whether the option is required.
* @param type The type of the option value (to handle type erasure).
* @param validator The function to validate the option value.
*
* @constructor Create a new [Option].
*/
@PublishedApi
internal constructor(
name: String,
default: T? = null,
values: Map<String, T?>? = null,
description: String? = null,
required: Boolean = false,
type: KType,
validator: Option<T>.(T?) -> Boolean = { true },
) : this(name, default, values, name, description, required, type, validator)
/** /**
* The value of the [Option]. * The value of the [Option].
*/ */
@@ -109,7 +144,7 @@ class Option<T> @PublishedApi internal constructor(
class Options internal constructor( class Options internal constructor(
private val options: Map<String, Option<*>>, private val options: Map<String, Option<*>>,
) : Map<String, Option<*>> by options { ) : Map<String, Option<*>> by options {
internal constructor(options: Set<Option<*>>) : this(options.associateBy { it.key }) internal constructor(options: Set<Option<*>>) : this(options.associateBy { it.name })
/** /**
* Set an option's value. * Set an option's value.
@@ -231,7 +266,7 @@ fun intOption(
title: String? = null, title: String? = null,
description: String? = null, description: String? = null,
required: Boolean = false, required: Boolean = false,
validator: Option<Int?>.(Int?) -> Boolean = { true }, validator: Option<Int>.(Int?) -> Boolean = { true },
) = option( ) = option(
key, key,
default, default,
@@ -264,7 +299,7 @@ fun PatchBuilder<*>.intOption(
title: String? = null, title: String? = null,
description: String? = null, description: String? = null,
required: Boolean = false, required: Boolean = false,
validator: Option<Int?>.(Int?) -> Boolean = { true }, validator: Option<Int>.(Int?) -> Boolean = { true },
) = option( ) = option(
key, key,
default, default,
@@ -297,7 +332,7 @@ fun booleanOption(
title: String? = null, title: String? = null,
description: String? = null, description: String? = null,
required: Boolean = false, required: Boolean = false,
validator: Option<Boolean?>.(Boolean?) -> Boolean = { true }, validator: Option<Boolean>.(Boolean?) -> Boolean = { true },
) = option( ) = option(
key, key,
default, default,
@@ -330,7 +365,7 @@ fun PatchBuilder<*>.booleanOption(
title: String? = null, title: String? = null,
description: String? = null, description: String? = null,
required: Boolean = false, required: Boolean = false,
validator: Option<Boolean?>.(Boolean?) -> Boolean = { true }, validator: Option<Boolean>.(Boolean?) -> Boolean = { true },
) = option( ) = option(
key, key,
default, default,
@@ -363,7 +398,7 @@ fun floatOption(
title: String? = null, title: String? = null,
description: String? = null, description: String? = null,
required: Boolean = false, required: Boolean = false,
validator: Option<Float?>.(Float?) -> Boolean = { true }, validator: Option<Float>.(Float?) -> Boolean = { true },
) = option( ) = option(
key, key,
default, default,
@@ -396,7 +431,7 @@ fun PatchBuilder<*>.floatOption(
title: String? = null, title: String? = null,
description: String? = null, description: String? = null,
required: Boolean = false, required: Boolean = false,
validator: Option<Float?>.(Float?) -> Boolean = { true }, validator: Option<Float>.(Float?) -> Boolean = { true },
) = option( ) = option(
key, key,
default, default,
@@ -429,7 +464,7 @@ fun longOption(
title: String? = null, title: String? = null,
description: String? = null, description: String? = null,
required: Boolean = false, required: Boolean = false,
validator: Option<Long?>.(Long?) -> Boolean = { true }, validator: Option<Long>.(Long?) -> Boolean = { true },
) = option( ) = option(
key, key,
default, default,
@@ -462,7 +497,7 @@ fun PatchBuilder<*>.longOption(
title: String? = null, title: String? = null,
description: String? = null, description: String? = null,
required: Boolean = false, required: Boolean = false,
validator: Option<Long?>.(Long?) -> Boolean = { true }, validator: Option<Long>.(Long?) -> Boolean = { true },
) = option( ) = option(
key, key,
default, default,
@@ -856,14 +891,14 @@ sealed class OptionException(errorMessage: String) : Exception(errorMessage, nul
* *
* @param value The value that failed validation. * @param value The value that failed validation.
*/ */
class ValueValidationException(value: Any?, option: Option<*>) : OptionException("The option value \"$value\" failed validation for ${option.key}") class ValueValidationException(value: Any?, option: Option<*>) : OptionException("The option value \"$value\" failed validation for ${option.name}")
/** /**
* An exception thrown when a value is required but null was passed. * An exception thrown when a value is required but null was passed.
* *
* @param option The [Option] that requires a value. * @param option The [Option] that requires a value.
*/ */
class ValueRequiredException(option: Option<*>) : OptionException("The option ${option.key} requires a value, but the value was null") class ValueRequiredException(option: Option<*>) : OptionException("The option ${option.name} requires a value, but the value was null")
/** /**
* An exception thrown when a [Option] is not found. * An exception thrown when a [Option] is not found.

File diff suppressed because it is too large Load Diff

View File

@@ -10,9 +10,9 @@ import brut.androlib.ApkDecoder
import brut.androlib.apk.UsesFramework import brut.androlib.apk.UsesFramework
import brut.androlib.res.Framework import brut.androlib.res.Framework
import brut.androlib.res.ResourcesDecoder import brut.androlib.res.ResourcesDecoder
import brut.androlib.res.decoder.AndroidManifestPullStreamDecoder
import brut.androlib.res.decoder.AndroidManifestResourceParser import brut.androlib.res.decoder.AndroidManifestResourceParser
import brut.androlib.res.decoder.XmlPullStreamDecoder import brut.androlib.res.xml.ResXmlUtils
import brut.androlib.res.xml.ResXmlPatcher
import brut.directory.ExtFile import brut.directory.ExtFile
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@@ -32,22 +32,26 @@ class ResourcePatchContext internal constructor(
private val logger = Logger.getLogger(ResourcePatchContext::class.java.name) private val logger = Logger.getLogger(ResourcePatchContext::class.java.name)
/** /**
* Read and write documents in the [PatcherConfig.apkFiles]. * Read a document from an [InputStream].
*/ */
val document = DocumentOperatable() fun document(inputStream: InputStream) = Document(inputStream)
/** /**
* Predicate to delete resources from [PatcherConfig.apkFiles]. * Read and write documents in the [PatcherConfig.apkFiles].
*/ */
private val deleteResources = mutableSetOf<(String) -> Boolean>() fun document(path: String) = Document(get(path))
/**
* Set of resources from [PatcherConfig.apkFiles] to delete.
*/
private val deleteResources = mutableSetOf<String>()
/** /**
* Decode resources of [PatcherConfig.apkFile]. * Decode resources of [PatcherConfig.apkFile].
* *
* @param mode The [ResourceMode] to use. * @param mode The [ResourceMode] to use.
*/ */
internal fun decodeResources(mode: ResourceMode) = internal fun decodeResources(mode: ResourceMode) = with(packageMetadata.apkInfo) {
with(packageMetadata.apkInfo) {
config.initializeTemporaryFilesDirectories() config.initializeTemporaryFilesDirectories()
// Needed to decode resources. // Needed to decode resources.
@@ -60,8 +64,7 @@ class ResourcePatchContext internal constructor(
resourcesDecoder.decodeManifest(config.apkFiles) resourcesDecoder.decodeManifest(config.apkFiles)
// Needed to record uncompressed files. // Needed to record uncompressed files.
val apkDecoder = ApkDecoder(config.resourceConfig, this) ApkDecoder(this, config.resourceConfig).recordUncompressedFiles(resourcesDecoder.resFileMapping)
apkDecoder.recordUncompressedFiles(resourcesDecoder.resFileMapping)
usesFramework = usesFramework =
UsesFramework().apply { UsesFramework().apply {
@@ -72,10 +75,10 @@ class ResourcePatchContext internal constructor(
// Decode manually instead of using resourceDecoder.decodeManifest // Decode manually instead of using resourceDecoder.decodeManifest
// because it does not support decoding to an OutputStream. // because it does not support decoding to an OutputStream.
XmlPullStreamDecoder( AndroidManifestPullStreamDecoder(
AndroidManifestResourceParser(resourcesDecoder.resTable), AndroidManifestResourceParser(resourcesDecoder.resTable),
resourcesDecoder.resXmlSerializer, resourcesDecoder.newXmlSerializer(),
).decodeManifest( ).decode(
apkFile.directory.getFileInput("AndroidManifest.xml"), apkFile.directory.getFileInput("AndroidManifest.xml"),
// Older Android versions do not support OutputStream.nullOutputStream() // Older Android versions do not support OutputStream.nullOutputStream()
object : OutputStream() { object : OutputStream() {
@@ -85,7 +88,7 @@ class ResourcePatchContext internal constructor(
) )
// Get the package name and version from the manifest using the XmlPullStreamDecoder. // Get the package name and version from the manifest using the XmlPullStreamDecoder.
// XmlPullStreamDecoder.decodeManifest() sets metadata.apkInfo. // AndroidManifestPullStreamDecoder.decode() sets metadata.apkInfo.
packageMetadata.let { metadata -> packageMetadata.let { metadata ->
metadata.packageName = resourcesDecoder.resTable.packageRenamed metadata.packageName = resourcesDecoder.resTable.packageRenamed
versionInfo.let { versionInfo.let {
@@ -95,7 +98,7 @@ class ResourcePatchContext internal constructor(
/* /*
The ResTable if flagged as sparse if the main package is not loaded, which is the case here, The ResTable if flagged as sparse if the main package is not loaded, which is the case here,
because ResourcesDecoder.decodeResources loads the main package because ResourcesDecoder.decodeResources loads the main package
and not XmlPullStreamDecoder.decodeManifest. and not AndroidManifestPullStreamDecoder.decode.
See ARSCDecoder.readTableType for more info. See ARSCDecoder.readTableType for more info.
Set this to false again to prevent the ResTable from being flagged as sparse falsely. Set this to false again to prevent the ResTable from being flagged as sparse falsely.
@@ -125,10 +128,10 @@ class ResourcePatchContext internal constructor(
AaptInvoker( AaptInvoker(
config.resourceConfig, config.resourceConfig,
packageMetadata.apkInfo, packageMetadata.apkInfo,
).invokeAapt( ).invoke(
resources.resolve("resources.apk"), resources.resolve("resources.apk"),
config.apkFiles.resolve("AndroidManifest.xml").also { config.apkFiles.resolve("AndroidManifest.xml").also {
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(it) ResXmlUtils.fixingPublicAttrsInProviderAttributes(it)
}, },
config.apkFiles.resolve("res"), config.apkFiles.resolve("res"),
null, null,
@@ -201,11 +204,11 @@ class ResourcePatchContext internal constructor(
} }
/** /**
* Stage a file to be deleted from [PatcherConfig.apkFile]. * Mark a file for deletion when the APK is rebuilt.
* *
* @param shouldDelete The predicate to stage the file for deletion given its name. * @param name The name of the file to delete.
*/ */
fun stageDelete(shouldDelete: (String) -> Boolean) = deleteResources.add(shouldDelete) fun delete(name: String) = deleteResources.add(name)
/** /**
* How to handle resources decoding and compiling. * How to handle resources decoding and compiling.
@@ -227,10 +230,4 @@ class ResourcePatchContext internal constructor(
*/ */
NONE, NONE,
} }
inner class DocumentOperatable {
operator fun get(inputStream: InputStream) = Document(inputStream)
operator fun get(path: String) = Document(this@ResourcePatchContext[path])
}
} }

View File

@@ -60,7 +60,7 @@ internal object ClassMerger {
if (missingMethods.isEmpty()) return this if (missingMethods.isEmpty()) return this
logger.fine("Found ${missingMethods.size} missing methods") logger.fine { "Found ${missingMethods.size} missing methods" }
return asMutableClass().apply { return asMutableClass().apply {
methods.addAll(missingMethods.map { it.toMutable() }) methods.addAll(missingMethods.map { it.toMutable() })
@@ -80,7 +80,7 @@ internal object ClassMerger {
if (missingFields.isEmpty()) return this if (missingFields.isEmpty()) return this
logger.fine("Found ${missingFields.size} missing fields") logger.fine { "Found ${missingFields.size} missing fields" }
return asMutableClass().apply { return asMutableClass().apply {
fields.addAll(missingFields.map { it.toMutable() }) fields.addAll(missingFields.map { it.toMutable() })
@@ -100,7 +100,7 @@ internal object ClassMerger {
context.traverseClassHierarchy(this) { context.traverseClassHierarchy(this) {
if (accessFlags.isPublic()) return@traverseClassHierarchy if (accessFlags.isPublic()) return@traverseClassHierarchy
logger.fine("Publicizing ${this.type}") logger.fine { "Publicizing ${this.type}" }
accessFlags = accessFlags.toPublic() accessFlags = accessFlags.toPublic()
} }
@@ -124,7 +124,7 @@ internal object ClassMerger {
if (brokenFields.isEmpty()) return this if (brokenFields.isEmpty()) return this
logger.fine("Found ${brokenFields.size} broken fields") logger.fine { "Found ${brokenFields.size} broken fields" }
/** /**
* Make a field public. * Make a field public.
@@ -153,7 +153,7 @@ internal object ClassMerger {
if (brokenMethods.isEmpty()) return this if (brokenMethods.isEmpty()) return this
logger.fine("Found ${brokenMethods.size} methods") logger.fine { "Found ${brokenMethods.size} methods" }
/** /**
* Make a method public. * Make a method public.

View File

@@ -34,7 +34,7 @@ class Document internal constructor(
readerCount.remove(it) readerCount.remove(it)
} }
it.outputStream().use { stream -> it.outputStream().buffered().use { stream ->
TransformerFactory.newInstance() TransformerFactory.newInstance()
.newTransformer() .newTransformer()
.transform(DOMSource(this), StreamResult(stream)) .transform(DOMSource(this), StreamResult(stream))

View File

@@ -12,11 +12,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil import com.android.tools.smali.dexlib2.util.MethodUtil
import kotlin.reflect.KProperty
/** /**
* A navigator for methods. * A navigator for methods.
* *
* @param context The [BytecodePatchContext] to use.
* @param startMethod The [Method] to start navigating from. * @param startMethod The [Method] to start navigating from.
* *
* @constructor Creates a new [MethodNavigator]. * @constructor Creates a new [MethodNavigator].
@@ -24,11 +24,15 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
* @throws NavigateException If the method does not have an implementation. * @throws NavigateException If the method does not have an implementation.
* @throws NavigateException If the instruction at the specified index is not a method reference. * @throws NavigateException If the instruction at the specified index is not a method reference.
*/ */
class MethodNavigator internal constructor(private val context: BytecodePatchContext, private var startMethod: MethodReference) { context(BytecodePatchContext)
class MethodNavigator internal constructor(
private var startMethod: MethodReference,
) {
private var lastNavigatedMethodReference = startMethod private var lastNavigatedMethodReference = startMethod
private val lastNavigatedMethodInstructions get() = with(immutable()) { private val lastNavigatedMethodInstructions
instructionsOrNull ?: throw NavigateException("Method $definingClass.$name does not have an implementation.") get() = with(original()) {
instructionsOrNull ?: throw NavigateException("Method $this does not have an implementation.")
} }
/** /**
@@ -38,7 +42,7 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
* *
* @return This [MethodNavigator]. * @return This [MethodNavigator].
*/ */
fun at(vararg index: Int): MethodNavigator { fun to(vararg index: Int): MethodNavigator {
index.forEach { index.forEach {
lastNavigatedMethodReference = lastNavigatedMethodInstructions.getMethodReferenceAt(it) lastNavigatedMethodReference = lastNavigatedMethodInstructions.getMethodReferenceAt(it)
} }
@@ -52,7 +56,7 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
* @param index The index of the method to navigate to. * @param index The index of the method to navigate to.
* @param predicate The predicate to match. * @param predicate The predicate to match.
*/ */
fun at(index: Int = 0, predicate: (Instruction) -> Boolean): MethodNavigator { fun to(index: Int = 0, predicate: (Instruction) -> Boolean): MethodNavigator {
lastNavigatedMethodReference = lastNavigatedMethodInstructions.asSequence() lastNavigatedMethodReference = lastNavigatedMethodInstructions.asSequence()
.filter(predicate).asIterable().getMethodReferenceAt(index) .filter(predicate).asIterable().getMethodReferenceAt(index)
@@ -76,15 +80,22 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
* *
* @return The last navigated method mutably. * @return The last navigated method mutably.
*/ */
fun mutable() = context.classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature fun stop() = classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
as MutableMethod as MutableMethod
/**
* Get the last navigated method mutably.
*
* @return The last navigated method mutably.
*/
operator fun getValue(nothing: Nothing?, property: KProperty<*>) = stop()
/** /**
* Get the last navigated method immutably. * Get the last navigated method immutably.
* *
* @return The last navigated method immutably. * @return The last navigated method immutably.
*/ */
fun immutable() = context.classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature fun original(): Method = classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
/** /**
* Predicate to match the class defining the current method reference. * Predicate to match the class defining the current method reference.
@@ -96,7 +107,8 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
/** /**
* Find the first [lastNavigatedMethodReference] in the class. * Find the first [lastNavigatedMethodReference] in the class.
*/ */
private val ClassDef.firstMethodBySignature get() = methods.first { private val ClassDef.firstMethodBySignature
get() = methods.first {
MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference) MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference)
} }

View File

@@ -50,7 +50,7 @@ class InlineSmaliCompiler {
registers, registers,
instructions, instructions,
) )
val reader = InputStreamReader(input.byteInputStream()) val reader = InputStreamReader(input.byteInputStream(), Charsets.UTF_8)
val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15) val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15)
val tokens = CommonTokenStream(lexer as TokenSource) val tokens = CommonTokenStream(lexer as TokenSource)
val parser = smaliParser(tokens) val parser = smaliParser(tokens)

View File

@@ -5,16 +5,15 @@ import app.revanced.patcher.patch.BytecodePatchContext.LookupMaps
import app.revanced.patcher.util.ProxyClassList import app.revanced.patcher.util.ProxyClassList
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import io.mockk.every import io.mockk.*
import io.mockk.mockk
import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertAll
import java.util.logging.Logger import java.util.logging.Logger
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
internal object PatcherTest { internal object PatcherTest {
@@ -149,18 +148,14 @@ internal object PatcherTest {
@Test @Test
fun `throws if unmatched fingerprint match is delegated`() { fun `throws if unmatched fingerprint match is delegated`() {
val patch = bytecodePatch { val patch = bytecodePatch {
// Fingerprint can never match.
val match by fingerprint { }
// Manually add the fingerprint.
app.revanced.patcher.fingerprint { }()
execute { execute {
// Throws, because the fingerprint can't be matched. // Fingerprint can never match.
match.patternMatch val fingerprint = fingerprint { }
}
}
assertEquals(2, patch.fingerprints.size) // Throws, because the fingerprint can't be matched.
fingerprint.patternMatch
}
}
assertTrue( assertTrue(
patch().exception != null, patch().exception != null,
@@ -170,43 +165,6 @@ internal object PatcherTest {
@Test @Test
fun `matches fingerprint`() { fun `matches fingerprint`() {
mockClassWithMethod()
val patches = setOf(bytecodePatch { fingerprint { this returns "V" } })
assertNull(
patches.first().fingerprints.first().match,
"Expected fingerprint to be matched before execution.",
)
patches()
assertDoesNotThrow("Expected fingerprint to be matched.") {
assertEquals(
"V",
patches.first().fingerprints.first().match!!.method.returnType,
"Expected fingerprint to be matched.",
)
}
}
private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
every { patcher.context.executablePatches } returns toMutableSet()
every { patcher.context.bytecodeContext.lookupMaps } returns LookupMaps(patcher.context.bytecodeContext.classes)
return runBlocking { patcher().toList() }
}
private operator fun Patch<*>.invoke() = setOf(this)().first()
private fun Any.setPrivateField(field: String, value: Any) {
this::class.java.getDeclaredField(field).apply {
this.isAccessible = true
set(this@setPrivateField, value)
}
}
private fun mockClassWithMethod() {
every { patcher.context.bytecodeContext.classes } returns ProxyClassList( every { patcher.context.bytecodeContext.classes } returns ProxyClassList(
mutableListOf( mutableListOf(
ImmutableClassDef( ImmutableClassDef(
@@ -232,6 +190,47 @@ internal object PatcherTest {
), ),
), ),
) )
val fingerprint = fingerprint { returns("V") }
val fingerprint2 = fingerprint { returns("V") }
val fingerprint3 = fingerprint { returns("V") }
val patches = setOf(
bytecodePatch {
execute {
fingerprint.match(classes.first().methods.first())
fingerprint2.match(classes.first())
fingerprint3.originalClassDef
}
},
)
patches()
with(patcher.context.bytecodeContext) {
assertAll(
"Expected fingerprints to match.",
{ assertNotNull(fingerprint.originalClassDefOrNull) },
{ assertNotNull(fingerprint2.originalClassDefOrNull) },
{ assertNotNull(fingerprint3.originalClassDefOrNull) },
)
}
}
private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
every { patcher.context.executablePatches } returns toMutableSet()
every { patcher.context.bytecodeContext.lookupMaps } returns LookupMaps(patcher.context.bytecodeContext.classes) every { patcher.context.bytecodeContext.lookupMaps } returns LookupMaps(patcher.context.bytecodeContext.classes)
every { with(patcher.context.bytecodeContext) { mergeExtension(any<BytecodePatch>()) } } just runs
return runBlocking { patcher().toList() }
}
private operator fun Patch<*>.invoke() = setOf(this)().first()
private fun Any.setPrivateField(field: String, value: Any) {
this::class.java.getDeclaredField(field).apply {
this.isAccessible = true
set(this@setPrivateField, value)
}
} }
} }

View File

@@ -1,6 +1,5 @@
package app.revanced.patcher.patch package app.revanced.patcher.patch
import app.revanced.patcher.fingerprint
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@@ -24,23 +23,6 @@ internal object PatchTest {
assertEquals("compatible.package", patch.compatiblePackages!!.first().first) assertEquals("compatible.package", patch.compatiblePackages!!.first().first)
} }
@Test
fun `can create patch with fingerprints`() {
val externalFingerprint = fingerprint {}
val patch = bytecodePatch(name = "Test") {
val externalFingerprintMatch by externalFingerprint()
val internalFingerprintMatch by fingerprint {}
execute {
externalFingerprintMatch.method
internalFingerprintMatch.method
}
}
assertEquals(2, patch.fingerprints.size)
}
@Test @Test
fun `can create patch with dependencies`() { fun `can create patch with dependencies`() {
val patch = bytecodePatch(name = "Test") { val patch = bytecodePatch(name = "Test") {