You've already forked revanced-patcher
mirror of
https://github.com/revanced/revanced-patcher
synced 2025-09-10 05:30:49 +02:00
Compare commits
31 Commits
v21.0.0-de
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b0df36f772 | ||
![]() |
8255ca14a0 | ||
![]() |
f70e85778a | ||
![]() |
d21d2b10d1 | ||
![]() |
dd95d8cfea | ||
![]() |
e8358aba3d | ||
![]() |
42c5a75ef3 | ||
![]() |
81e6aed44a | ||
![]() |
b0d34719b1 | ||
![]() |
b160a2adc0 | ||
![]() |
33fadcbd0c | ||
![]() |
68db95b99b | ||
![]() |
4f2ef3c47c | ||
![]() |
062ae14936 | ||
![]() |
99f431897e | ||
![]() |
d80abbcd17 | ||
![]() |
509ecc81e1 | ||
![]() |
e4e66b0d8b | ||
![]() |
bb8771bb8b | ||
![]() |
754b02e4ca | ||
![]() |
fe5fb736cb | ||
![]() |
fc505a8726 | ||
![]() |
88a3252574 | ||
![]() |
ead701bdaf | ||
![]() |
0581dcf931 | ||
![]() |
62191e3c4a | ||
![]() |
1358d3fa10 | ||
![]() |
6712f0ea72 | ||
![]() |
0746c22743 | ||
![]() |
7f55868e6f | ||
![]() |
5d996def4d |
2
.github/workflows/build_pull_request.yml
vendored
2
.github/workflows/build_pull_request.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Cache Gradle
|
- name: Cache Gradle
|
||||||
uses: burrunan/gradle-cache-action@v1
|
uses: burrunan/gradle-cache-action@v3
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Cache Gradle
|
- name: Cache Gradle
|
||||||
uses: burrunan/gradle-cache-action@v1
|
uses: burrunan/gradle-cache-action@v3
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
|
86
CHANGELOG.md
86
CHANGELOG.md
@@ -1,3 +1,89 @@
|
|||||||
|
# [21.1.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.3...v21.1.0-dev.4) (2025-07-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Correctly save XML files in UTF-8 by using a bufferedWriter ([#356](https://github.com/ReVanced/revanced-patcher/issues/356)) ([33fadcb](https://github.com/ReVanced/revanced-patcher/commit/33fadcbd0c7076b848bdca4d62a9c684d5781232))
|
||||||
|
|
||||||
|
# [21.1.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.2...v21.1.0-dev.3) (2025-06-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Encode XML files as UTF-8 to fix compilation of resources ([#339](https://github.com/ReVanced/revanced-patcher/issues/339)) ([4f2ef3c](https://github.com/ReVanced/revanced-patcher/commit/4f2ef3c47cea76a26c464cfb45d4bb57fe7198b5))
|
||||||
|
|
||||||
|
# [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)
|
# [21.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0-dev.1) (2024-10-27)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,4 +1,22 @@
|
|||||||
public final class app/revanced/patcher/Fingerprint {
|
public final class app/revanced/patcher/Fingerprint {
|
||||||
|
public final fun getClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
||||||
|
public final fun getClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
||||||
|
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 {
|
||||||
@@ -31,13 +49,11 @@ public final class app/revanced/patcher/Match {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -57,6 +73,8 @@ 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/io/File;Ljava/lang/String;)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 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 synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
}
|
}
|
||||||
@@ -146,10 +164,6 @@ public final class app/revanced/patcher/patch/BytecodePatchContext : app/revance
|
|||||||
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 getMatch (Lapp/revanced/patcher/Fingerprint;)Lapp/revanced/patcher/Match;
|
|
||||||
public final fun getValue (Lapp/revanced/patcher/Fingerprint;Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/Match;
|
|
||||||
public final fun match (Lapp/revanced/patcher/Fingerprint;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
|
|
||||||
public final fun match (Lapp/revanced/patcher/Fingerprint;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
|
|
||||||
public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)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;
|
||||||
}
|
}
|
||||||
@@ -157,9 +171,12 @@ public final class app/revanced/patcher/patch/BytecodePatchContext : app/revance
|
|||||||
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;
|
||||||
@@ -468,12 +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 at ([I)Lapp/revanced/patcher/util/MethodNavigator;
|
|
||||||
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 getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
||||||
public final fun original ()Lcom/android/tools/smali/dexlib2/iface/Method;
|
public final fun original ()Lcom/android/tools/smali/dexlib2/iface/Method;
|
||||||
public final fun stop ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
public final fun stop ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
||||||
|
public final fun to (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
|
||||||
|
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 {
|
||||||
|
@@ -56,6 +56,8 @@ dependencies {
|
|||||||
kotlin {
|
kotlin {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
jvmTarget.set(JvmTarget.JVM_11)
|
jvmTarget.set(JvmTarget.JVM_11)
|
||||||
|
|
||||||
|
freeCompilerArgs = listOf("-Xcontext-receivers")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -117,15 +117,19 @@ With this information, the original code can be reconstructed:
|
|||||||
```java
|
```java
|
||||||
package com.some.app.ads;
|
package com.some.app.ads;
|
||||||
|
|
||||||
<accessFlags> class AdsLoader {
|
<accessFlags>
|
||||||
public final boolean <methodName>(boolean <parameter>) {
|
|
||||||
|
class AdsLoader {
|
||||||
|
public final boolean <methodName>(boolean <parameter>)
|
||||||
|
|
||||||
|
{
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
var userStatus = "pro";
|
var userStatus = "pro";
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
return <returnValue>;
|
return <returnValue >;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -134,13 +138,14 @@ Using that fingerprint, this method can be matched uniquely from all other metho
|
|||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> A fingerprint should contain information about a method likely to remain the same across updates.
|
> A fingerprint should contain information about a method likely to remain the same across updates.
|
||||||
> A method's name is not included in the fingerprint because it will likely change with each update in an obfuscated app.
|
> A method's name is not included in the fingerprint because it will likely change with each update in an obfuscated
|
||||||
> In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the same.
|
> app.
|
||||||
|
> In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the
|
||||||
|
> same.
|
||||||
|
|
||||||
## 🔨 How to use fingerprints
|
## 🔨 How to use fingerprints
|
||||||
|
|
||||||
A fingerprint is matched to a method,
|
After declaring a fingerprint, it can be used in a patch to find the method it matches to:
|
||||||
once the `match` property of the fingerprint is accessed in a patch's `execute` scope:
|
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
val fingerprint = fingerprint {
|
val fingerprint = fingerprint {
|
||||||
@@ -149,52 +154,34 @@ val fingerprint = fingerprint {
|
|||||||
|
|
||||||
val patch = bytecodePatch {
|
val patch = bytecodePatch {
|
||||||
execute {
|
execute {
|
||||||
val match = fingerprint.match!!
|
fingerprint.method
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The fingerprint won't be matched again, if it has already been matched once.
|
The fingerprint won't be matched again, if it has already been matched once, for performance reasons.
|
||||||
This makes it useful, to share fingerprints between multiple patches, and let the first patch match the fingerprint:
|
This makes it useful, to share fingerprints between multiple patches,
|
||||||
|
and let the first executing patch match the fingerprint:
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
// Either of these two patches will match the fingerprint first and the other patch can reuse the match:
|
// Either of these two patches will match the fingerprint first and the other patch can reuse the match:
|
||||||
val mainActivityPatch1 = bytecodePatch {
|
val mainActivityPatch1 = bytecodePatch {
|
||||||
execute {
|
execute {
|
||||||
val match = mainActivityOnCreateFingerprint.match!!
|
mainActivityOnCreateFingerprint.method
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val mainActivityPatch2 = bytecodePatch {
|
val mainActivityPatch2 = bytecodePatch {
|
||||||
execute {
|
execute {
|
||||||
val match = mainActivityOnCreateFingerprint.match!!
|
mainActivityOnCreateFingerprint.method
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
A fingerprint match can also be delegated to a variable for convenience without the need to check for `null`:
|
|
||||||
```kt
|
|
||||||
val fingerprint = fingerprint {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
val patch = bytecodePatch {
|
|
||||||
execute {
|
|
||||||
// Alternative to fingerprint.match ?: throw PatchException("No match found")
|
|
||||||
val match by fingerprint.match
|
|
||||||
|
|
||||||
try {
|
|
||||||
match.method
|
|
||||||
} catch (e: PatchException) {
|
|
||||||
// Handle the exception for example.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> If the fingerprint can not be matched to any method, the match of a fingerprint is `null`. If such a match is delegated
|
> If the fingerprint can not be matched to any method,
|
||||||
> to a variable, accessing it will raise an exception.
|
> accessing certain properties of the fingerprint will raise an exception.
|
||||||
|
> Instead, the `orNull` properties can be used to return `null` if no match is found.
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If a fingerprint has an opcode pattern, you can use the `fuzzyPatternScanThreshhold` parameter of the `opcode`
|
> If a fingerprint has an opcode pattern, you can use the `fuzzyPatternScanThreshhold` parameter of the `opcode`
|
||||||
@@ -211,47 +198,43 @@ val patch = bytecodePatch {
|
|||||||
> )
|
> )
|
||||||
>}
|
>}
|
||||||
> ```
|
> ```
|
||||||
>
|
|
||||||
The match of a fingerprint contains references to the original method and class definition of the method:
|
|
||||||
|
|
||||||
```kt
|
The following properties can be accessed in a fingerprint:
|
||||||
class Match(
|
|
||||||
val originalMethod: Method,
|
|
||||||
val originalClassDef: ClassDef,
|
|
||||||
val patternMatch: Match.PatternMatch?,
|
|
||||||
val stringMatches: List<Match.StringMatch>?,
|
|
||||||
// ...
|
|
||||||
) {
|
|
||||||
val classDef by lazy { /* ... */ }
|
|
||||||
val method by lazy { /* ... */ }
|
|
||||||
|
|
||||||
// ...
|
- `originalClassDef`: The original class definition the fingerprint matches to.
|
||||||
}
|
- `originalClassDefOrNull`: The original class definition the fingerprint matches to.
|
||||||
```
|
- `originalMethod`: The original method the fingerprint matches to.
|
||||||
|
- `originalMethodOrNull`: The original method the fingerprint matches to.
|
||||||
|
- `classDef`: The class the fingerprint matches to.
|
||||||
|
- `classDefOrNull`: The class the fingerprint matches to.
|
||||||
|
- `method`: The method the fingerprint matches to. If no match is found, an exception is raised.
|
||||||
|
- `methodOrNull`: The method the fingerprint matches to.
|
||||||
|
|
||||||
The `classDef` and `method` properties can be used to make changes to the class or method.
|
The difference between the `original` and non-`original` properties is that the `original` properties return the
|
||||||
They are lazy properties, so they are only computed
|
original class or method definition, while the non-`original` properties return a mutable copy of the class or method.
|
||||||
and will effectively replace the original method or class definition when accessed.
|
The mutable copies can be modified. They are lazy properties, so they are only computed
|
||||||
|
and only then will effectively replace the `original` method or class definition when accessed.
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If only read-only access to the class or method is needed,
|
> If only read-only access to the class or method is needed,
|
||||||
> the `originalClassDef` and `originalMethod` properties can be used,
|
> the `originalClassDef` and `originalMethod` properties should be used,
|
||||||
> to avoid making a mutable copy of the class or method.
|
> to avoid making a mutable copy of the class or method.
|
||||||
|
|
||||||
## 🏹 Manually matching fingerprints
|
## 🏹 Manually matching fingerprints
|
||||||
|
|
||||||
By default, a fingerprint is matched automatically against all classes when the `match` property is accessed.
|
By default, a fingerprint is matched automatically against all classes
|
||||||
|
when one of the fingerprint's properties is accessed.
|
||||||
|
|
||||||
Instead, the fingerprint can be matched manually using various overloads of a fingerprint's `match` function:
|
Instead, the fingerprint can be matched manually using various overloads of a fingerprint's `match` function:
|
||||||
|
|
||||||
- In a **list of classes**, if the fingerprint can match in a known subset of classes
|
- In a **list of classes**, if the fingerprint can match in a known subset of classes
|
||||||
|
|
||||||
If you have a known list of classes you know the fingerprint can match in,
|
If you have a known list of classes you know the fingerprint can match in,
|
||||||
you can match the fingerprint on the list of classes:
|
you can match the fingerprint on the list of classes:
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
execute {
|
execute {
|
||||||
val match = showAdsFingerprint.match(classes) ?: throw PatchException("No match found")
|
val match = showAdsFingerprint(classes)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -263,23 +246,24 @@ you can match the fingerprint on the list of classes:
|
|||||||
execute {
|
execute {
|
||||||
val adsLoaderClass = classes.single { it.name == "Lcom/some/app/ads/Loader;" }
|
val adsLoaderClass = classes.single { it.name == "Lcom/some/app/ads/Loader;" }
|
||||||
|
|
||||||
val match = showAdsFingerprint.match(context, adsLoaderClass) ?: throw PatchException("No match found")
|
val match = showAdsFingerprint.match(adsLoaderClass)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Another common usecase is to use a fingerprint to reduce the search space of a method to a single class.
|
Another common usecase is to use a fingerprint to reduce the search space of a method to a single class.
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
execute {
|
execute {
|
||||||
// Match showAdsFingerprint in the class of the ads loader found by adsLoaderClassFingerprint.
|
// Match showAdsFingerprint in the class of the ads loader found by adsLoaderClassFingerprint.
|
||||||
val match by showAdsFingerprint.match(adsLoaderClassFingerprint.match!!.classDef)
|
val match = showAdsFingerprint.match(adsLoaderClassFingerprint.classDef)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Match a **single method**, to extract certain information about it
|
- Match a **single method**, to extract certain information about it
|
||||||
|
|
||||||
The match of a fingerprint contains useful information about the method,
|
The match of a fingerprint contains useful information about the method,
|
||||||
such as the start and end index of an opcode pattern or the indices of the instructions with certain string references.
|
such as the start and end index of an opcode pattern or the indices of the instructions with certain string
|
||||||
|
references.
|
||||||
A fingerprint can be leveraged to extract such information from a method instead of manually figuring it out:
|
A fingerprint can be leveraged to extract such information from a method instead of manually figuring it out:
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
@@ -288,14 +272,19 @@ you can match the fingerprint on the list of classes:
|
|||||||
strings("free", "trial")
|
strings("free", "trial")
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPlanFingerprint.match(adsFingerprintMatch.method)?.let { match ->
|
currentPlanFingerprint.match(adsFingerprint.method).let { match ->
|
||||||
match.stringMatches.forEach { match ->
|
match.stringMatches.forEach { match ->
|
||||||
println("The index of the string '${match.string}' is ${match.index}")
|
println("The index of the string '${match.string}' is ${match.index}")
|
||||||
}
|
}
|
||||||
} ?: throw PatchException("No match found")
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> If the fingerprint can not be matched to any method, calling `match` will raise an
|
||||||
|
> exception.
|
||||||
|
> Instead, the `orNull` overloads can be used to return `null` if no match is found.
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> To see real-world examples of fingerprints,
|
> To see real-world examples of fingerprints,
|
||||||
> check out the repository for [ReVanced Patches](https://github.com/revanced/revanced-patches).
|
> check out the repository for [ReVanced Patches](https://github.com/revanced/revanced-patches).
|
||||||
|
@@ -73,26 +73,26 @@ package app.revanced.patches.ads
|
|||||||
val disableAdsPatch = bytecodePatch(
|
val disableAdsPatch = bytecodePatch(
|
||||||
name = "Disable ads",
|
name = "Disable ads",
|
||||||
description = "Disable ads in the app.",
|
description = "Disable ads in the app.",
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.some.app"("1.0.0"))
|
compatibleWith("com.some.app"("1.0.0"))
|
||||||
|
|
||||||
// Patches can depend on other patches, executing them first.
|
// Patches can depend on other patches, executing them first.
|
||||||
dependsOn(disableAdsResourcePatch)
|
dependsOn(disableAdsResourcePatch)
|
||||||
|
|
||||||
// Merge precompiled DEX files into the patched app, before the patch is executed.
|
// Merge precompiled DEX files into the patched app, before the patch is executed.
|
||||||
extendWith("disable-ads.rve")
|
extendWith("disable-ads.rve")
|
||||||
|
|
||||||
// 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.
|
// Fingerprint to find the method to patch.
|
||||||
val showAdsMatch by showAdsFingerprint {
|
val showAdsFingerprint = fingerprint {
|
||||||
// More about fingerprints on the next page of the documentation.
|
// 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.method.addInstructions(
|
showAdsFingerprint.method.addInstructions(
|
||||||
0,
|
0,
|
||||||
"""
|
"""
|
||||||
invoke-static {}, LDisableAdsPatch;->shouldDisableAds()Z
|
invoke-static {}, LDisableAdsPatch;->shouldDisableAds()Z
|
||||||
@@ -122,11 +122,11 @@ 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)
|
||||||
println(string)
|
println(string)
|
||||||
@@ -139,7 +139,7 @@ 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"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ option.type // The KType of the option. Captures the full type information of th
|
|||||||
Options can be declared outside 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()
|
||||||
@@ -183,18 +183,18 @@ and use it in a patch:
|
|||||||
```kt
|
```kt
|
||||||
val patch = bytecodePatch(name = "Complex patch") {
|
val patch = bytecodePatch(name = "Complex patch") {
|
||||||
extendWith("complex-patch.rve")
|
extendWith("complex-patch.rve")
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
fingerprint.match!!.mutableMethod.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V")
|
fingerprint.method.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
ReVanced Patcher merges the classes from the extension into `context.classes` before executing the patch.
|
ReVanced Patcher merges the classes from the extension into `context.classes` before executing the patch.
|
||||||
When the patch is executed, it can reference the classes and methods from the extension.
|
When the patch is executed, it can reference the classes and methods from the extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
> The [ReVanced Patches template](https://github.com/ReVanced/revanced-patches-template) repository
|
> The [ReVanced Patches template](https://github.com/ReVanced/revanced-patches-template) repository
|
||||||
> is a template project to create patches and extensions.
|
> is a template project to create patches and extensions.
|
||||||
|
|
||||||
@@ -211,9 +211,9 @@ A simple real-world example would be a patch that opens a resource file of the a
|
|||||||
Other patches that depend on this patch can write to the file, and the finalization block can close the file.
|
Other patches that depend on this patch can write to the file, and the finalization block can close the file.
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
val patch = bytecodePatch(name = "Patch") {
|
val patch = bytecodePatch(name = "Patch") {
|
||||||
dependsOn(
|
dependsOn(
|
||||||
bytecodePatch(name = "Dependency") {
|
bytecodePatch(name = "Dependency") {
|
||||||
execute {
|
execute {
|
||||||
print("1")
|
print("1")
|
||||||
}
|
}
|
||||||
@@ -249,10 +249,10 @@ The same order is followed for multiple patches depending on the patch.
|
|||||||
- A patch can declare compatibility with specific packages and versions,
|
- A patch can declare compatibility with specific packages and versions,
|
||||||
but patches can still be executed on any package or version.
|
but patches can still be executed on any package or version.
|
||||||
It is recommended that compatibility is specified to present known compatible packages and versions.
|
It is recommended that compatibility is specified to present known compatible packages and versions.
|
||||||
- If `compatibleWith` is not used, the patch is treated as compatible with any package
|
- If `compatibleWith` is not used, the patch is treated as compatible with any package
|
||||||
- If a package is specified with no versions, the patch is compatible with any version of the package
|
- If a package is specified with no versions, the patch is compatible with any version of the package
|
||||||
- If an empty array of versions is specified, the patch is not compatible with any version of the package.
|
- If an empty array of versions is specified, the patch is not compatible with any version of the package.
|
||||||
This is useful for declaring incompatibility with a specific package.
|
This is useful for declaring incompatibility with a specific package.
|
||||||
- A patch can raise a `PatchException` at any time of execution to indicate that the patch failed to execute.
|
- A patch can raise a `PatchException` at any time of execution to indicate that the patch failed to execute.
|
||||||
|
|
||||||
## ⏭️ What's next
|
## ⏭️ What's next
|
||||||
|
@@ -46,17 +46,17 @@ The `navigate(Method)` function allows you to navigate method calls recursively
|
|||||||
```kt
|
```kt
|
||||||
execute {
|
execute {
|
||||||
// Sequentially navigate to the instructions at index 1 within 'someMethod'.
|
// Sequentially navigate to the instructions at index 1 within 'someMethod'.
|
||||||
val method = navigate(someMethod).at(1).original() // original() returns the original immutable method.
|
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'.
|
// Further navigate to the second occurrence where the instruction's opcode is 'INVOKEVIRTUAL'.
|
||||||
// stop() returns the mutable copy of the method.
|
// stop() returns the mutable copy of the method.
|
||||||
val method = navigate(someMethod).at(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
|
val method = navigate(someMethod).to(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
|
||||||
|
|
||||||
// Alternatively, to stop(), you can delegate the method to a variable.
|
// Alternatively, to stop(), you can delegate the method to a variable.
|
||||||
val method by navigate(someMethod).at(1)
|
val method by navigate(someMethod).to(1)
|
||||||
|
|
||||||
// You can chain multiple calls to at() to navigate deeper into the method.
|
// You can chain multiple calls to at() to navigate deeper into the method.
|
||||||
val method by navigate(someMethod).at(1).at(2, 3, 4).at(5)
|
val method by navigate(someMethod).to(1).to(2, 3, 4).to(5)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ execute {
|
|||||||
The `document` function is used to read and write DOM files.
|
The `document` function is used to read and write DOM files.
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
execute {
|
execute {
|
||||||
document("res/values/strings.xml").use { document ->
|
document("res/values/strings.xml").use { document ->
|
||||||
val element = doc.createElement("string").apply {
|
val element = doc.createElement("string").apply {
|
||||||
textContent = "Hello, World!"
|
textContent = "Hello, World!"
|
||||||
@@ -112,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
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
org.gradle.caching = true
|
org.gradle.caching = true
|
||||||
version = 21.0.0-dev.1
|
version = 21.1.0-dev.4
|
||||||
|
@@ -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.18.1"
|
||||||
kotlin = "2.0.0"
|
kotlin = "2.2.10"
|
||||||
kotlinx-coroutines-core = "1.8.1"
|
kotlinx-coroutines-core = "1.10.2"
|
||||||
mockk = "1.13.10"
|
mockk = "1.14.5"
|
||||||
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.9"
|
||||||
xpp3 = "1.1.4c"
|
xpp3 = "1.1.4c"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
1357
package-lock.json
generated
1357
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,6 @@
|
|||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1",
|
||||||
"gradle-semantic-release-plugin": "^1.10.1",
|
"gradle-semantic-release-plugin": "^1.10.1",
|
||||||
"semantic-release": "^24.1.2"
|
"semantic-release": "^24.2.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -16,9 +16,28 @@ import java.util.logging.Logger
|
|||||||
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: String? = null,
|
aaptBinaryPath: File? = null,
|
||||||
frameworkFileDirectory: String? = 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,
|
||||||
|
frameworkFileDirectory: String? = null,
|
||||||
|
) : 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)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package app.revanced.patcher.patch
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
import app.revanced.patcher.*
|
import app.revanced.patcher.InternalApi
|
||||||
|
import app.revanced.patcher.PatcherConfig
|
||||||
|
import app.revanced.patcher.PatcherResult
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
|
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
|
||||||
import app.revanced.patcher.util.ClassMerger.merge
|
import app.revanced.patcher.util.ClassMerger.merge
|
||||||
import app.revanced.patcher.util.MethodNavigator
|
import app.revanced.patcher.util.MethodNavigator
|
||||||
@@ -22,7 +24,6 @@ import java.io.Closeable
|
|||||||
import java.io.FileFilter
|
import java.io.FileFilter
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A context for patches containing the current state of the bytecode.
|
* A context for patches containing the current state of the bytecode.
|
||||||
@@ -33,7 +34,7 @@ import kotlin.reflect.KProperty
|
|||||||
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].
|
||||||
@@ -53,36 +54,6 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
|||||||
).also { opcodes = it.opcodes }.classes.toMutableList(),
|
).also { opcodes = it.opcodes }.classes.toMutableList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* The match for this [Fingerprint]. Null if unmatched.
|
|
||||||
*/
|
|
||||||
val Fingerprint.match get() = match(this@BytecodePatchContext)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Match using a [ClassDef].
|
|
||||||
*
|
|
||||||
* @param classDef The class to match against.
|
|
||||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
|
||||||
*/
|
|
||||||
fun Fingerprint.match(classDef: ClassDef) = match(this@BytecodePatchContext, classDef)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Match using a [Method].
|
|
||||||
* The class is retrieved from the method.
|
|
||||||
*
|
|
||||||
* @param method The method to match against.
|
|
||||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
|
||||||
*/
|
|
||||||
fun Fingerprint.match(method: Method) = match(this@BytecodePatchContext, method)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the match for this [Fingerprint].
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException If the [Fingerprint] has not been matched.
|
|
||||||
*/
|
|
||||||
operator fun Fingerprint.getValue(nothing: Nothing?, property: KProperty<*>): Match = _match
|
|
||||||
?: throw PatchException("No fingerprint match to delegate to \"${property.name}\".")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The lookup maps for methods and the class they are a member of from the [classes].
|
* The lookup maps for methods and the class they are a member of from the [classes].
|
||||||
*/
|
*/
|
||||||
@@ -137,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.
|
||||||
@@ -148,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: MethodReference) = MethodNavigator(this@BytecodePatchContext, method)
|
fun navigate(method: MethodReference) = MethodNavigator(method)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile bytecode from the [BytecodePatchContext].
|
* Compile bytecode from the [BytecodePatchContext].
|
||||||
@@ -227,28 +198,6 @@ 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() {
|
||||||
methodsByStrings.clear()
|
methodsByStrings.clear()
|
||||||
classesByType.clear()
|
classesByType.clear()
|
||||||
|
@@ -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.
|
||||||
@@ -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.
|
||||||
|
@@ -87,7 +87,8 @@ sealed class Patch<C : PatchContext<*>>(
|
|||||||
finalizeBlock?.invoke(context)
|
finalizeBlock?.invoke(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString() = name ?: "Patch"
|
override fun toString() = name ?:
|
||||||
|
"Patch@${System.identityHashCode(this)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Patch<*>.anyRecursively(
|
internal fun Patch<*>.anyRecursively(
|
||||||
@@ -161,7 +162,7 @@ class BytecodePatch internal constructor(
|
|||||||
|
|
||||||
override fun finalize(context: PatcherContext) = finalize(context.bytecodeContext)
|
override fun finalize(context: PatcherContext) = finalize(context.bytecodeContext)
|
||||||
|
|
||||||
override fun toString() = name ?: "BytecodePatch"
|
override fun toString() = name ?: "Bytecode${super.toString()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -204,7 +205,7 @@ class RawResourcePatch internal constructor(
|
|||||||
|
|
||||||
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
|
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
|
||||||
|
|
||||||
override fun toString() = name ?: "RawResourcePatch"
|
override fun toString() = name ?: "RawResource${super.toString()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,7 +248,7 @@ class ResourcePatch internal constructor(
|
|||||||
|
|
||||||
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
|
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
|
||||||
|
|
||||||
override fun toString() = name ?: "ResourcePatch"
|
override fun toString() = name ?: "Resource${super.toString()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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
|
||||||
@@ -51,64 +51,62 @@ class ResourcePatchContext internal constructor(
|
|||||||
*
|
*
|
||||||
* @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.
|
||||||
val resourcesDecoder = ResourcesDecoder(config.resourceConfig, this)
|
val resourcesDecoder = ResourcesDecoder(config.resourceConfig, this)
|
||||||
|
|
||||||
if (mode == ResourceMode.FULL) {
|
if (mode == ResourceMode.FULL) {
|
||||||
logger.info("Decoding resources")
|
logger.info("Decoding resources")
|
||||||
|
|
||||||
resourcesDecoder.decodeResources(config.apkFiles)
|
resourcesDecoder.decodeResources(config.apkFiles)
|
||||||
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 {
|
||||||
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
|
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.info("Decoding app manifest")
|
|
||||||
|
|
||||||
// Decode manually instead of using resourceDecoder.decodeManifest
|
|
||||||
// because it does not support decoding to an OutputStream.
|
|
||||||
XmlPullStreamDecoder(
|
|
||||||
AndroidManifestResourceParser(resourcesDecoder.resTable),
|
|
||||||
resourcesDecoder.resXmlSerializer,
|
|
||||||
).decodeManifest(
|
|
||||||
apkFile.directory.getFileInput("AndroidManifest.xml"),
|
|
||||||
// Older Android versions do not support OutputStream.nullOutputStream()
|
|
||||||
object : OutputStream() {
|
|
||||||
override fun write(b: Int) { // Do nothing.
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get the package name and version from the manifest using the XmlPullStreamDecoder.
|
|
||||||
// XmlPullStreamDecoder.decodeManifest() sets metadata.apkInfo.
|
|
||||||
packageMetadata.let { metadata ->
|
|
||||||
metadata.packageName = resourcesDecoder.resTable.packageRenamed
|
|
||||||
versionInfo.let {
|
|
||||||
metadata.packageVersion = it.versionName ?: it.versionCode
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
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
|
|
||||||
and not XmlPullStreamDecoder.decodeManifest.
|
|
||||||
See ARSCDecoder.readTableType for more info.
|
|
||||||
|
|
||||||
Set this to false again to prevent the ResTable from being flagged as sparse falsely.
|
|
||||||
*/
|
|
||||||
metadata.apkInfo.sparseResources = false
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("Decoding app manifest")
|
||||||
|
|
||||||
|
// Decode manually instead of using resourceDecoder.decodeManifest
|
||||||
|
// because it does not support decoding to an OutputStream.
|
||||||
|
AndroidManifestPullStreamDecoder(
|
||||||
|
AndroidManifestResourceParser(resourcesDecoder.resTable),
|
||||||
|
resourcesDecoder.newXmlSerializer(),
|
||||||
|
).decode(
|
||||||
|
apkFile.directory.getFileInput("AndroidManifest.xml"),
|
||||||
|
// Older Android versions do not support OutputStream.nullOutputStream()
|
||||||
|
object : OutputStream() {
|
||||||
|
override fun write(b: Int) { // Do nothing.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get the package name and version from the manifest using the XmlPullStreamDecoder.
|
||||||
|
// AndroidManifestPullStreamDecoder.decode() sets metadata.apkInfo.
|
||||||
|
packageMetadata.let { metadata ->
|
||||||
|
metadata.packageName = resourcesDecoder.resTable.packageRenamed
|
||||||
|
versionInfo.let {
|
||||||
|
metadata.packageVersion = it.versionName ?: it.versionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
and not AndroidManifestPullStreamDecoder.decode.
|
||||||
|
See ARSCDecoder.readTableType for more info.
|
||||||
|
|
||||||
|
Set this to false again to prevent the ResTable from being flagged as sparse falsely.
|
||||||
|
*/
|
||||||
|
metadata.apkInfo.sparseResources = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile resources in [PatcherConfig.apkFiles].
|
* Compile resources in [PatcherConfig.apkFiles].
|
||||||
@@ -130,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,
|
||||||
|
@@ -5,6 +5,7 @@ import java.io.Closeable
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
import javax.xml.transform.OutputKeys
|
||||||
import javax.xml.transform.TransformerFactory
|
import javax.xml.transform.TransformerFactory
|
||||||
import javax.xml.transform.dom.DOMSource
|
import javax.xml.transform.dom.DOMSource
|
||||||
import javax.xml.transform.stream.StreamResult
|
import javax.xml.transform.stream.StreamResult
|
||||||
@@ -34,15 +35,22 @@ class Document internal constructor(
|
|||||||
readerCount.remove(it)
|
readerCount.remove(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
it.outputStream().use { stream ->
|
val transformer = TransformerFactory.newInstance().newTransformer()
|
||||||
TransformerFactory.newInstance()
|
// Set to UTF-16 to prevent surrogate pairs from being escaped to invalid numeric character references, but save as UTF-8.
|
||||||
.newTransformer()
|
if (isAndroid) {
|
||||||
.transform(DOMSource(this), StreamResult(stream))
|
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-16")
|
||||||
|
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
|
||||||
|
it.bufferedWriter(charset = Charsets.UTF_8).use { writer ->
|
||||||
|
transformer.transform(DOMSource(this), StreamResult(writer))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transformer.transform(DOMSource(this), StreamResult(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private val readerCount = mutableMapOf<File, Int>()
|
private val readerCount = mutableMapOf<File, Int>()
|
||||||
|
private val isAndroid = System.getProperty("java.runtime.name") == "Android Runtime"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,6 @@ 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].
|
||||||
@@ -25,12 +24,16 @@ import kotlin.reflect.KProperty
|
|||||||
* @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(original()) {
|
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.")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate to the method at the specified index.
|
* Navigate to the method at the specified index.
|
||||||
@@ -39,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)
|
||||||
}
|
}
|
||||||
@@ -53,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)
|
||||||
|
|
||||||
@@ -77,7 +80,7 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
|
|||||||
*
|
*
|
||||||
* @return The last navigated method mutably.
|
* @return The last navigated method mutably.
|
||||||
*/
|
*/
|
||||||
fun stop() = context.classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
|
fun stop() = classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
|
||||||
as MutableMethod
|
as MutableMethod
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,7 +95,7 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
|
|||||||
*
|
*
|
||||||
* @return The last navigated method immutably.
|
* @return The last navigated method immutably.
|
||||||
*/
|
*/
|
||||||
fun original() = 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.
|
||||||
@@ -104,9 +107,10 @@ 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
|
||||||
MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference)
|
get() = methods.first {
|
||||||
}
|
MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An exception thrown when navigating fails.
|
* An exception thrown when navigating fails.
|
||||||
|
@@ -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)
|
||||||
|
@@ -3,21 +3,18 @@ package app.revanced.patcher
|
|||||||
import app.revanced.patcher.patch.*
|
import app.revanced.patcher.patch.*
|
||||||
import app.revanced.patcher.patch.BytecodePatchContext.LookupMaps
|
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.iface.ClassDef
|
|
||||||
import com.android.tools.smali.dexlib2.iface.Method
|
|
||||||
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.just
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.runs
|
|
||||||
import jdk.internal.module.ModuleBootstrap.patcher
|
|
||||||
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.assertAll
|
import org.junit.jupiter.api.assertAll
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
import kotlin.test.*
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
internal object PatcherTest {
|
internal object PatcherTest {
|
||||||
private lateinit var patcher: Patcher
|
private lateinit var patcher: Patcher
|
||||||
@@ -153,10 +150,10 @@ internal object PatcherTest {
|
|||||||
val patch = bytecodePatch {
|
val patch = bytecodePatch {
|
||||||
execute {
|
execute {
|
||||||
// Fingerprint can never match.
|
// Fingerprint can never match.
|
||||||
val match by fingerprint { }
|
val fingerprint = fingerprint { }
|
||||||
|
|
||||||
// Throws, because the fingerprint can't be matched.
|
// Throws, because the fingerprint can't be matched.
|
||||||
match.patternMatch
|
fingerprint.patternMatch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,11 +190,6 @@ internal object PatcherTest {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
every { with(patcher.context.bytecodeContext) { any<Fingerprint>().match } } answers { callOriginal() }
|
|
||||||
every { with(patcher.context.bytecodeContext) { any<Fingerprint>().match(any<ClassDef>()) } } answers { callOriginal() }
|
|
||||||
every { with(patcher.context.bytecodeContext) { any<Fingerprint>().match(any<Method>()) } } answers { callOriginal() }
|
|
||||||
every { patcher.context.bytecodeContext.classBy(any()) } answers { callOriginal() }
|
|
||||||
every { patcher.context.bytecodeContext.proxy(any()) } answers { callOriginal() }
|
|
||||||
|
|
||||||
val fingerprint = fingerprint { returns("V") }
|
val fingerprint = fingerprint { returns("V") }
|
||||||
val fingerprint2 = fingerprint { returns("V") }
|
val fingerprint2 = fingerprint { returns("V") }
|
||||||
@@ -208,19 +200,21 @@ internal object PatcherTest {
|
|||||||
execute {
|
execute {
|
||||||
fingerprint.match(classes.first().methods.first())
|
fingerprint.match(classes.first().methods.first())
|
||||||
fingerprint2.match(classes.first())
|
fingerprint2.match(classes.first())
|
||||||
fingerprint3.match
|
fingerprint3.originalClassDef
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
patches()
|
patches()
|
||||||
|
|
||||||
assertAll(
|
with(patcher.context.bytecodeContext) {
|
||||||
"Expected fingerprints to match.",
|
assertAll(
|
||||||
{ assertNotNull(fingerprint._match) },
|
"Expected fingerprints to match.",
|
||||||
{ assertNotNull(fingerprint2._match) },
|
{ assertNotNull(fingerprint.originalClassDefOrNull) },
|
||||||
{ assertNotNull(fingerprint3._match) },
|
{ assertNotNull(fingerprint2.originalClassDefOrNull) },
|
||||||
)
|
{ assertNotNull(fingerprint3.originalClassDefOrNull) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
|
private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
|
||||||
|
Reference in New Issue
Block a user