You've already forked revanced-patcher
							
							
				mirror of
				https://github.com/revanced/revanced-patcher
				synced 2025-11-02 07:30:52 +01:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					c2e0f57579 | ||
| 
						 | 
					242e244b18 | ||
| 
						 | 
					f338ebff6c | ||
| 
						 | 
					00e5950cf2 | ||
| 
						 | 
					5a0e3841ff | ||
| 
						 | 
					cfb459b832 | ||
| 
						 | 
					2096306107 | ||
| 
						 | 
					92eaba8081 | ||
| 
						 | 
					7be0cd8548 | ||
| 
						 | 
					ab624f04f6 | ||
| 
						 | 
					21b5c079fb | ||
| 
						 | 
					5024204046 | ||
| 
						 | 
					a44802ef4e | 
							
								
								
									
										14
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,3 +1,17 @@
 | 
			
		||||
## [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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,4 @@
 | 
			
		||||
public final class app/revanced/patcher/Fingerprint {
 | 
			
		||||
	public final fun getMatch ()Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Z
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/FingerprintBuilder {
 | 
			
		||||
@@ -18,20 +15,17 @@ public final class app/revanced/patcher/FingerprintBuilder {
 | 
			
		||||
 | 
			
		||||
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 (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 (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 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 ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
 | 
			
		||||
	public final fun getMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun getMutableClass ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
 | 
			
		||||
	public final fun getMutableMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun getClassDef ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
 | 
			
		||||
	public final fun getMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun getOriginalClassDef ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
 | 
			
		||||
	public final fun getOriginalMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun getPatternMatch ()Lapp/revanced/patcher/Match$PatternMatch;
 | 
			
		||||
	public final fun getStringMatches ()Ljava/util/List;
 | 
			
		||||
}
 | 
			
		||||
@@ -63,8 +57,8 @@ public final class app/revanced/patcher/Patcher : java/io/Closeable {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ZILkotlin/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 {
 | 
			
		||||
@@ -135,31 +129,28 @@ public final class app/revanced/patcher/extensions/InstructionExtensions {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/patch/BytecodePatch : app/revanced/patcher/patch/Patch {
 | 
			
		||||
	public final fun getExtension ()Ljava/io/InputStream;
 | 
			
		||||
	public final fun getFingerprints ()Ljava/util/Set;
 | 
			
		||||
	public final fun getExtensionInputStream ()Ljava/util/function/Supplier;
 | 
			
		||||
	public fun toString ()Ljava/lang/String;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 final fun extendWith (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatchBuilder;
 | 
			
		||||
	public final fun getExtension ()Ljava/io/InputStream;
 | 
			
		||||
	public final fun invoke (Lapp/revanced/patcher/Fingerprint;)Lapp/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint;
 | 
			
		||||
	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 fun getExtensionInputStream ()Ljava/util/function/Supplier;
 | 
			
		||||
	public final fun setExtensionInputStream (Ljava/util/function/Supplier;)V
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 classByType (Ljava/lang/String;)Lapp/revanced/patcher/util/proxy/ClassProxy;
 | 
			
		||||
	public fun close ()V
 | 
			
		||||
	public synthetic fun get ()Ljava/lang/Object;
 | 
			
		||||
	public fun get ()Ljava/util/Set;
 | 
			
		||||
	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 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 proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -286,7 +277,7 @@ public final class app/revanced/patcher/patch/Options : java/util/Map, kotlin/jv
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 finalize (Lapp/revanced/patcher/patch/PatchContext;)V
 | 
			
		||||
	public final fun getCompatiblePackages ()Ljava/util/Set;
 | 
			
		||||
@@ -303,13 +294,13 @@ public abstract class app/revanced/patcher/patch/PatchBuilder {
 | 
			
		||||
	public final fun compatibleWith ([Ljava/lang/String;)V
 | 
			
		||||
	public final fun compatibleWith ([Lkotlin/Pair;)V
 | 
			
		||||
	public final fun dependsOn ([Lapp/revanced/patcher/patch/Patch;)V
 | 
			
		||||
	public final fun execute (Lkotlin/jvm/functions/Function2;)V
 | 
			
		||||
	public final fun finalize (Lkotlin/jvm/functions/Function2;)V
 | 
			
		||||
	public final fun execute (Lkotlin/jvm/functions/Function1;)V
 | 
			
		||||
	public final fun finalize (Lkotlin/jvm/functions/Function1;)V
 | 
			
		||||
	protected final fun getCompatiblePackages ()Ljava/util/Set;
 | 
			
		||||
	protected final fun getDependencies ()Ljava/util/Set;
 | 
			
		||||
	protected final fun getDescription ()Ljava/lang/String;
 | 
			
		||||
	protected final fun getExecutionBlock ()Lkotlin/jvm/functions/Function2;
 | 
			
		||||
	protected final fun getFinalizeBlock ()Lkotlin/jvm/functions/Function2;
 | 
			
		||||
	protected final fun getExecutionBlock ()Lkotlin/jvm/functions/Function1;
 | 
			
		||||
	protected final fun getFinalizeBlock ()Lkotlin/jvm/functions/Function1;
 | 
			
		||||
	protected final fun getName ()Ljava/lang/String;
 | 
			
		||||
	protected final fun getOptions ()Ljava/util/Set;
 | 
			
		||||
	protected final fun getUse ()Z
 | 
			
		||||
@@ -317,8 +308,8 @@ public abstract class app/revanced/patcher/patch/PatchBuilder {
 | 
			
		||||
	public final fun invoke (Ljava/lang/String;[Ljava/lang/String;)Lkotlin/Pair;
 | 
			
		||||
	protected final fun setCompatiblePackages (Ljava/util/Set;)V
 | 
			
		||||
	protected final fun setDependencies (Ljava/util/Set;)V
 | 
			
		||||
	protected final fun setExecutionBlock (Lkotlin/jvm/functions/Function2;)V
 | 
			
		||||
	protected final fun setFinalizeBlock (Lkotlin/jvm/functions/Function2;)V
 | 
			
		||||
	protected final fun setExecutionBlock (Lkotlin/jvm/functions/Function1;)V
 | 
			
		||||
	protected final fun setFinalizeBlock (Lkotlin/jvm/functions/Function1;)V
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public abstract interface class app/revanced/patcher/patch/PatchContext : java/util/function/Supplier {
 | 
			
		||||
@@ -395,18 +386,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 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 synthetic fun get ()Ljava/lang/Object;
 | 
			
		||||
	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 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 {
 | 
			
		||||
@@ -485,8 +471,9 @@ 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 immutable ()Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun mutable ()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 stop ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList {
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 | 
			
		||||
> [!NOTE]
 | 
			
		||||
> To start a fresh project, 
 | 
			
		||||
> you can use the [ReVanced Patches template](https://github.com/revanced/revanced-patches-template).
 | 
			
		||||
 | 
			
		||||
1. Clone the repository
 | 
			
		||||
 | 
			
		||||
   ```bash
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -76,23 +76,23 @@ val disableAdsPatch = bytecodePatch(
 | 
			
		||||
) { 
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    // 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")
 | 
			
		||||
 | 
			
		||||
    // 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.
 | 
			
		||||
    execute {
 | 
			
		||||
        // Fingerprint to find the method to patch.
 | 
			
		||||
        val showAdsMatch by showAdsFingerprint {
 | 
			
		||||
          // More about fingerprints on the next page of the documentation.
 | 
			
		||||
        }
 | 
			
		||||
      
 | 
			
		||||
        // In the method that shows ads,
 | 
			
		||||
        // call DisableAdsPatch.shouldDisableAds() from the extension (precompiled DEX file)
 | 
			
		||||
        // to enable or disable ads.
 | 
			
		||||
        showAdsMatch.mutableMethod.addInstructions(
 | 
			
		||||
        showAdsMatch.method.addInstructions(
 | 
			
		||||
            0,
 | 
			
		||||
            """
 | 
			
		||||
                invoke-static {}, LDisableAdsPatch;->shouldDisableAds()Z
 | 
			
		||||
@@ -146,10 +146,10 @@ loadPatchesJar(patches).apply {
 | 
			
		||||
The type of an option can be obtained from the `type` property of the option:
 | 
			
		||||
 | 
			
		||||
```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
 | 
			
		||||
val option = stringOption(key = "option")
 | 
			
		||||
@@ -183,11 +183,9 @@ and use it in a patch:
 | 
			
		||||
```kt
 | 
			
		||||
val patch = bytecodePatch(name = "Complex patch") {
 | 
			
		||||
    extendWith("complex-patch.rve")
 | 
			
		||||
    
 | 
			
		||||
    val match by methodFingerprint()
 | 
			
		||||
    
 | 
			
		||||
  
 | 
			
		||||
    execute { 
 | 
			
		||||
        match.mutableMethod.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V")
 | 
			
		||||
        fingerprint.match!!.mutableMethod.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -96,21 +96,21 @@ Example of patches:
 | 
			
		||||
@Surpress("unused")
 | 
			
		||||
val bytecodePatch = bytecodePatch {
 | 
			
		||||
    execute { 
 | 
			
		||||
        // TODO
 | 
			
		||||
        // More about this on the next page of the documentation.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Surpress("unused")
 | 
			
		||||
val rawResourcePatch = rawResourcePatch {
 | 
			
		||||
    execute { 
 | 
			
		||||
        // TODO
 | 
			
		||||
    execute {
 | 
			
		||||
        // More about this on the next page of the documentation.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Surpress("unused")
 | 
			
		||||
val resourcePatch = resourcePatch {
 | 
			
		||||
    execute { 
 | 
			
		||||
        // TODO
 | 
			
		||||
    execute {
 | 
			
		||||
        // More about this on the next page of the documentation.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										107
									
								
								docs/4_apis.md
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								docs/4_apis.md
									
									
									
									
									
								
							@@ -4,18 +4,107 @@ A handful of APIs are available to make patch development easier and more effici
 | 
			
		||||
 | 
			
		||||
## 📙 Overview
 | 
			
		||||
 | 
			
		||||
1. 👹 Mutate classes with `context.proxy(ClassDef)`
 | 
			
		||||
2. 🔍 Find and proxy existing classes with `classBy(Predicate)` and `classByType(String)`
 | 
			
		||||
3. 🏃 Easily access referenced methods recursively by index with `MethodNavigator`
 | 
			
		||||
4. 🔨 Make use of extension functions from `BytecodeUtils` and `ResourceUtils` with certain applications
 | 
			
		||||
(Available in ReVanced Patches)
 | 
			
		||||
5. 💾 Read and write (decoded) resources with `ResourcePatchContext.get(Path, Boolean)`
 | 
			
		||||
6. 📃 Read and write DOM files using `ResourcePatchContext.document`
 | 
			
		||||
1. 👹 Create mutable replacements of classes with `proxy(ClassDef)`
 | 
			
		||||
2. 🔍 Find and create mutable replaces with `classBy(Predicate)`
 | 
			
		||||
3. 🏃 Navigate method calls recursively by index with `navigate(Method)`
 | 
			
		||||
4. 💾 Read and write resource files with `get(String, Boolean)` and `delete(String)`
 | 
			
		||||
5. 📃 Read and write DOM files using `document(String)` and  `document(InputStream)`
 | 
			
		||||
 | 
			
		||||
### 🧰 APIs
 | 
			
		||||
 | 
			
		||||
> [!WARNING]
 | 
			
		||||
> This section is still under construction and may be incomplete.
 | 
			
		||||
#### 👹 `proxy(ClassDef)`
 | 
			
		||||
 | 
			
		||||
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).at(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).at(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
 | 
			
		||||
    
 | 
			
		||||
    // Alternatively, to stop(), you can delegate the method to a variable.
 | 
			
		||||
    val method by navigate(someMethod).at(1)
 | 
			
		||||
    
 | 
			
		||||
    // 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)
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 💾 `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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
org.gradle.parallel = true
 | 
			
		||||
org.gradle.caching = true
 | 
			
		||||
version = 20.0.1
 | 
			
		||||
version = 20.0.2
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -91,19 +91,15 @@ class Patcher(private val config: PatcherConfig) : Closeable {
 | 
			
		||||
            }.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) {
 | 
			
		||||
            context.resourceContext.decodeResources(config.resourceMode)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        logger.info("Merging extensions")
 | 
			
		||||
        logger.info("Initializing lookup maps")
 | 
			
		||||
 | 
			
		||||
        with(context.bytecodeContext) {
 | 
			
		||||
            context.executablePatches.mergeExtensions()
 | 
			
		||||
 | 
			
		||||
            // Initialize lookup maps.
 | 
			
		||||
           lookupMaps
 | 
			
		||||
        }
 | 
			
		||||
        // Accessing the lazy lookup maps to initialize them.
 | 
			
		||||
        context.bytecodeContext.lookupMaps
 | 
			
		||||
 | 
			
		||||
        logger.info("Executing patches")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,16 +12,12 @@ import java.util.logging.Logger
 | 
			
		||||
 * @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.
 | 
			
		||||
 * @param multithreadingDexFileWriter Whether to use multiple threads for writing dex files.
 | 
			
		||||
 * This has impact on memory usage and performance.
 | 
			
		||||
 */
 | 
			
		||||
class PatcherConfig(
 | 
			
		||||
    internal val apkFile: File,
 | 
			
		||||
    private val temporaryFilesPath: File = File("revanced-temporary-files"),
 | 
			
		||||
    aaptBinaryPath: String? = null,
 | 
			
		||||
    frameworkFileDirectory: String? = null,
 | 
			
		||||
    @Deprecated("This is going to be removed in the future because it is not needed anymore.")
 | 
			
		||||
    internal val multithreadingDexFileWriter: Boolean = false,
 | 
			
		||||
) {
 | 
			
		||||
    private val logger = Logger.getLogger(PatcherConfig::class.java.name)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -29,12 +29,12 @@ class PatcherResult internal constructor(
 | 
			
		||||
     * @param resourcesApk The compiled resources.apk file.
 | 
			
		||||
     * @param otherResources The directory containing other resources files.
 | 
			
		||||
     * @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(
 | 
			
		||||
        val resourcesApk: File?,
 | 
			
		||||
        val otherResources: File?,
 | 
			
		||||
        val doNotCompress: Set<String>,
 | 
			
		||||
        val deleteResources: Set<(String) -> Boolean>,
 | 
			
		||||
        val deleteResources: Set<String>,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
package app.revanced.patcher.patch
 | 
			
		||||
 | 
			
		||||
import app.revanced.patcher.InternalApi
 | 
			
		||||
import app.revanced.patcher.PatcherConfig
 | 
			
		||||
import app.revanced.patcher.PatcherResult
 | 
			
		||||
import app.revanced.patcher.*
 | 
			
		||||
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
 | 
			
		||||
import app.revanced.patcher.util.ClassMerger.merge
 | 
			
		||||
import app.revanced.patcher.util.MethodNavigator
 | 
			
		||||
@@ -14,6 +12,7 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
 | 
			
		||||
import com.android.tools.smali.dexlib2.iface.DexFile
 | 
			
		||||
import com.android.tools.smali.dexlib2.iface.Method
 | 
			
		||||
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 lanchon.multidexlib2.BasicDexFileNamer
 | 
			
		||||
import lanchon.multidexlib2.DexIO
 | 
			
		||||
@@ -23,6 +22,7 @@ import java.io.Closeable
 | 
			
		||||
import java.io.FileFilter
 | 
			
		||||
import java.util.*
 | 
			
		||||
import java.util.logging.Logger
 | 
			
		||||
import kotlin.reflect.KProperty
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A context for patches containing the current state of the bytecode.
 | 
			
		||||
@@ -53,60 +53,74 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
        ).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].
 | 
			
		||||
     */
 | 
			
		||||
    internal val lookupMaps by lazy { LookupMaps(classes) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Merge the extensions for this set of patches.
 | 
			
		||||
     * Merge the extension of [bytecodePatch] into the [BytecodePatchContext].
 | 
			
		||||
     * If no extension is present, the function will return early.
 | 
			
		||||
     *
 | 
			
		||||
     * @param bytecodePatch The [BytecodePatch] to merge the extension of.
 | 
			
		||||
     */
 | 
			
		||||
    internal fun Set<Patch<*>>.mergeExtensions() {
 | 
			
		||||
        // 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) }
 | 
			
		||||
        }
 | 
			
		||||
    internal fun mergeExtension(bytecodePatch: BytecodePatch) {
 | 
			
		||||
        bytecodePatch.extensionInputStream?.get()?.use { extensionStream ->
 | 
			
		||||
            RawDexIO.readRawDexFile(extensionStream, 0, null).classes.forEach { classDef ->
 | 
			
		||||
                val existingClass = lookupMaps.classesByType[classDef.type] ?: run {
 | 
			
		||||
                    logger.fine("Adding class \"$classDef\"")
 | 
			
		||||
 | 
			
		||||
        forEachRecursively { patch ->
 | 
			
		||||
            if (patch is BytecodePatch && patch.extension != null) {
 | 
			
		||||
                    classes += classDef
 | 
			
		||||
                    lookupMaps.classesByType[classDef.type] = classDef
 | 
			
		||||
 | 
			
		||||
                val extension = patch.extension.readAllBytes()
 | 
			
		||||
                    return@forEach
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                RawDexIO.readRawDexFile(extension, 0, null).classes.forEach { classDef ->
 | 
			
		||||
                    val existingClass = classesByType[classDef.type] ?: run {
 | 
			
		||||
                        logger.fine("Adding class \"$classDef\"")
 | 
			
		||||
                logger.fine("Class \"$classDef\" exists already. Adding missing methods and fields.")
 | 
			
		||||
 | 
			
		||||
                        classes += classDef
 | 
			
		||||
                        classesByType[classDef.type] = classDef
 | 
			
		||||
 | 
			
		||||
                        return@forEach
 | 
			
		||||
                existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
 | 
			
		||||
                    // If the class was merged, replace the original class with the merged class.
 | 
			
		||||
                    if (mergedClass === existingClass) {
 | 
			
		||||
                        return@let
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    logger.fine("Class \"$classDef\" exists already. Adding missing methods and fields.")
 | 
			
		||||
 | 
			
		||||
                    existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
 | 
			
		||||
                        // If the class was merged, replace the original class with the merged class.
 | 
			
		||||
                        if (mergedClass === existingClass) {
 | 
			
		||||
                            return@let
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        classes -= existingClass
 | 
			
		||||
                        classes += mergedClass
 | 
			
		||||
                    }
 | 
			
		||||
                    classes -= existingClass
 | 
			
		||||
                    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.
 | 
			
		||||
     *
 | 
			
		||||
@@ -134,7 +148,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
     *
 | 
			
		||||
     * @return A [MethodNavigator] for the method.
 | 
			
		||||
     */
 | 
			
		||||
    fun navigate(method: Method) = MethodNavigator(this@BytecodePatchContext, method)
 | 
			
		||||
    fun navigate(method: MethodReference) = MethodNavigator(this@BytecodePatchContext, method)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compile bytecode from the [BytecodePatchContext].
 | 
			
		||||
@@ -155,7 +169,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
            }.apply {
 | 
			
		||||
                MultiDexIO.writeDexFile(
 | 
			
		||||
                    true,
 | 
			
		||||
                    if (config.multithreadingDexFileWriter) -1 else 1,
 | 
			
		||||
                    -1,
 | 
			
		||||
                    this,
 | 
			
		||||
                    BasicDexFileNamer(),
 | 
			
		||||
                    object : DexFile {
 | 
			
		||||
@@ -186,6 +200,11 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
         */
 | 
			
		||||
        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 {
 | 
			
		||||
            classes.forEach { classDef ->
 | 
			
		||||
                classDef.methods.forEach { method ->
 | 
			
		||||
@@ -232,6 +251,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
 | 
			
		||||
        override fun close() {
 | 
			
		||||
            methodsByStrings.clear()
 | 
			
		||||
            classesByType.clear()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@
 | 
			
		||||
 | 
			
		||||
package app.revanced.patcher.patch
 | 
			
		||||
 | 
			
		||||
import app.revanced.patcher.Fingerprint
 | 
			
		||||
import app.revanced.patcher.Patcher
 | 
			
		||||
import app.revanced.patcher.PatcherContext
 | 
			
		||||
import dalvik.system.DexClassLoader
 | 
			
		||||
@@ -14,8 +13,8 @@ import java.lang.reflect.Member
 | 
			
		||||
import java.lang.reflect.Method
 | 
			
		||||
import java.lang.reflect.Modifier
 | 
			
		||||
import java.net.URLClassLoader
 | 
			
		||||
import java.util.function.Supplier
 | 
			
		||||
import java.util.jar.JarFile
 | 
			
		||||
import kotlin.reflect.KProperty
 | 
			
		||||
 | 
			
		||||
typealias PackageName = String
 | 
			
		||||
typealias VersionName = String
 | 
			
		||||
@@ -46,10 +45,10 @@ sealed class Patch<C : PatchContext<*>>(
 | 
			
		||||
    val dependencies: Set<Patch<*>>,
 | 
			
		||||
    val compatiblePackages: Set<Package>?,
 | 
			
		||||
    options: Set<Option<*>>,
 | 
			
		||||
    private val executeBlock: Patch<C>.(C) -> Unit,
 | 
			
		||||
    private val executeBlock: (C) -> Unit,
 | 
			
		||||
    // Must be internal and nullable, so that Patcher.invoke can check,
 | 
			
		||||
    // if a patch has a finalizing block in order to not emit it twice.
 | 
			
		||||
    internal var finalizeBlock: (Patch<C>.(C) -> Unit)?,
 | 
			
		||||
    internal var finalizeBlock: ((C) -> Unit)?,
 | 
			
		||||
) {
 | 
			
		||||
    /**
 | 
			
		||||
     * The options of the patch.
 | 
			
		||||
@@ -57,35 +56,35 @@ sealed class Patch<C : PatchContext<*>>(
 | 
			
		||||
    val options = Options(options)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Runs the execution block of the patch.
 | 
			
		||||
     * Called by [Patcher].
 | 
			
		||||
     * Calls the execution block of the patch.
 | 
			
		||||
     * This function is called by [Patcher.invoke].
 | 
			
		||||
     *
 | 
			
		||||
     * @param context The [PatcherContext] to get the [PatchContext] from to execute the patch with.
 | 
			
		||||
     */
 | 
			
		||||
    internal abstract fun execute(context: PatcherContext)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Runs the execution block of the patch.
 | 
			
		||||
     * Calls the execution block of the patch.
 | 
			
		||||
     *
 | 
			
		||||
     * @param context The [PatchContext] to execute the patch with.
 | 
			
		||||
     */
 | 
			
		||||
    fun execute(context: C) = executeBlock(context)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Runs the finalizing block of the patch.
 | 
			
		||||
     * Called by [Patcher].
 | 
			
		||||
     * Calls the finalizing block of the patch.
 | 
			
		||||
     * This function is called by [Patcher.invoke].
 | 
			
		||||
     *
 | 
			
		||||
     * @param context The [PatcherContext] to get the [PatchContext] from to finalize the patch with.
 | 
			
		||||
     */
 | 
			
		||||
    internal abstract fun finalize(context: PatcherContext)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Runs the finalizing block of the patch.
 | 
			
		||||
     * Calls the finalizing block of the patch.
 | 
			
		||||
     *
 | 
			
		||||
     * @param context The [PatchContext] to finalize the patch with.
 | 
			
		||||
     */
 | 
			
		||||
    fun finalize(context: C) {
 | 
			
		||||
        finalizeBlock?.invoke(this, context)
 | 
			
		||||
        finalizeBlock?.invoke(context)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun toString() = name ?: "Patch"
 | 
			
		||||
@@ -127,8 +126,7 @@ internal fun Iterable<Patch<*>>.forEachRecursively(
 | 
			
		||||
 * If null, the patch is compatible with all packages.
 | 
			
		||||
 * @param dependencies Other patches this patch depends on.
 | 
			
		||||
 * @param options The options of the patch.
 | 
			
		||||
 * @param fingerprints The fingerprints that are resolved before the patch is executed.
 | 
			
		||||
 * @property extension An input stream of the extension resource this patch uses.
 | 
			
		||||
 * @property extensionInputStream Getter for the extension input stream of the patch.
 | 
			
		||||
 * An extension is a precompiled DEX file that is merged into the patched app before this patch is executed.
 | 
			
		||||
 * @param executeBlock The execution block of the patch.
 | 
			
		||||
 * @param finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
 | 
			
		||||
@@ -143,10 +141,9 @@ class BytecodePatch internal constructor(
 | 
			
		||||
    compatiblePackages: Set<Package>?,
 | 
			
		||||
    dependencies: Set<Patch<*>>,
 | 
			
		||||
    options: Set<Option<*>>,
 | 
			
		||||
    val fingerprints: Set<Fingerprint>,
 | 
			
		||||
    val extension: InputStream?,
 | 
			
		||||
    executeBlock: Patch<BytecodePatchContext>.(BytecodePatchContext) -> Unit,
 | 
			
		||||
    finalizeBlock: (Patch<BytecodePatchContext>.(BytecodePatchContext) -> Unit)?,
 | 
			
		||||
    val extensionInputStream: Supplier<InputStream>?,
 | 
			
		||||
    executeBlock: (BytecodePatchContext) -> Unit,
 | 
			
		||||
    finalizeBlock: ((BytecodePatchContext) -> Unit)?,
 | 
			
		||||
) : Patch<BytecodePatchContext>(
 | 
			
		||||
    name,
 | 
			
		||||
    description,
 | 
			
		||||
@@ -158,8 +155,7 @@ class BytecodePatch internal constructor(
 | 
			
		||||
    finalizeBlock,
 | 
			
		||||
) {
 | 
			
		||||
    override fun execute(context: PatcherContext) = with(context.bytecodeContext) {
 | 
			
		||||
        fingerprints.forEach { it.match(this) }
 | 
			
		||||
 | 
			
		||||
        mergeExtension(this@BytecodePatch)
 | 
			
		||||
        execute(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -192,8 +188,8 @@ class RawResourcePatch internal constructor(
 | 
			
		||||
    compatiblePackages: Set<Package>?,
 | 
			
		||||
    dependencies: Set<Patch<*>>,
 | 
			
		||||
    options: Set<Option<*>>,
 | 
			
		||||
    executeBlock: Patch<ResourcePatchContext>.(ResourcePatchContext) -> Unit,
 | 
			
		||||
    finalizeBlock: (Patch<ResourcePatchContext>.(ResourcePatchContext) -> Unit)?,
 | 
			
		||||
    executeBlock: (ResourcePatchContext) -> Unit,
 | 
			
		||||
    finalizeBlock: ((ResourcePatchContext) -> Unit)?,
 | 
			
		||||
) : Patch<ResourcePatchContext>(
 | 
			
		||||
    name,
 | 
			
		||||
    description,
 | 
			
		||||
@@ -235,8 +231,8 @@ class ResourcePatch internal constructor(
 | 
			
		||||
    compatiblePackages: Set<Package>?,
 | 
			
		||||
    dependencies: Set<Patch<*>>,
 | 
			
		||||
    options: Set<Option<*>>,
 | 
			
		||||
    executeBlock: Patch<ResourcePatchContext>.(ResourcePatchContext) -> Unit,
 | 
			
		||||
    finalizeBlock: (Patch<ResourcePatchContext>.(ResourcePatchContext) -> Unit)?,
 | 
			
		||||
    executeBlock: (ResourcePatchContext) -> Unit,
 | 
			
		||||
    finalizeBlock: ((ResourcePatchContext) -> Unit)?,
 | 
			
		||||
) : Patch<ResourcePatchContext>(
 | 
			
		||||
    name,
 | 
			
		||||
    description,
 | 
			
		||||
@@ -281,8 +277,8 @@ sealed class PatchBuilder<C : PatchContext<*>>(
 | 
			
		||||
    protected var dependencies = mutableSetOf<Patch<*>>()
 | 
			
		||||
    protected val options = mutableSetOf<Option<*>>()
 | 
			
		||||
 | 
			
		||||
    protected var executionBlock: (Patch<C>.(C) -> Unit) = { }
 | 
			
		||||
    protected var finalizeBlock: (Patch<C>.(C) -> Unit)? = null
 | 
			
		||||
    protected var executionBlock: ((C) -> Unit) = { }
 | 
			
		||||
    protected var finalizeBlock: ((C) -> Unit)? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add an option to the patch.
 | 
			
		||||
@@ -341,7 +337,7 @@ sealed class PatchBuilder<C : PatchContext<*>>(
 | 
			
		||||
     *
 | 
			
		||||
     * @param block The execution block of the patch.
 | 
			
		||||
     */
 | 
			
		||||
    fun execute(block: Patch<C>.(C) -> Unit) {
 | 
			
		||||
    fun execute(block: C.() -> Unit) {
 | 
			
		||||
        executionBlock = block
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -350,7 +346,7 @@ sealed class PatchBuilder<C : PatchContext<*>>(
 | 
			
		||||
     *
 | 
			
		||||
     * @param block The finalizing block of the patch.
 | 
			
		||||
     */
 | 
			
		||||
    fun finalize(block: Patch<C>.(C) -> Unit) {
 | 
			
		||||
    fun finalize(block: C.() -> Unit) {
 | 
			
		||||
        finalizeBlock = block
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -379,8 +375,7 @@ private fun <B : PatchBuilder<*>> B.buildPatch(block: B.() -> Unit = {}) = apply
 | 
			
		||||
 * If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
 | 
			
		||||
 * @param description The description of the patch.
 | 
			
		||||
 * @param use Weather or not the patch should be used.
 | 
			
		||||
 * @property fingerprints The fingerprints that are resolved before the patch is executed.
 | 
			
		||||
 * @property extension An input stream of the extension resource this patch uses.
 | 
			
		||||
 * @property extensionInputStream Getter for the extension input stream of the patch.
 | 
			
		||||
 * An extension is a precompiled DEX file that is merged into the patched app before this patch is executed.
 | 
			
		||||
 *
 | 
			
		||||
 * @constructor Create a new [BytecodePatchBuilder] builder.
 | 
			
		||||
@@ -390,27 +385,9 @@ class BytecodePatchBuilder internal constructor(
 | 
			
		||||
    description: String?,
 | 
			
		||||
    use: Boolean,
 | 
			
		||||
) : PatchBuilder<BytecodePatchContext>(name, description, use) {
 | 
			
		||||
    private val fingerprints = mutableSetOf<Fingerprint>()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add the fingerprint to the patch.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A wrapper for the fingerprint with the ability to delegate the match to the fingerprint.
 | 
			
		||||
     */
 | 
			
		||||
    operator fun Fingerprint.invoke() = InvokedFingerprint(also { fingerprints.add(it) })
 | 
			
		||||
 | 
			
		||||
    class InvokedFingerprint internal constructor(private val fingerprint: Fingerprint) {
 | 
			
		||||
        // The reason getValue isn't extending the Fingerprint class is
 | 
			
		||||
        // because delegating makes only sense if the fingerprint was previously added to the patch by invoking it.
 | 
			
		||||
        // It may be likely to forget invoking it. By wrapping the fingerprint into this class,
 | 
			
		||||
        // the compiler will throw an error if the fingerprint was not invoked if attempting to delegate the match.
 | 
			
		||||
        operator fun getValue(nothing: Nothing?, property: KProperty<*>) = fingerprint.match
 | 
			
		||||
            ?: throw PatchException("No fingerprint match to delegate to \"${property.name}\".")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Must be internal for the inlined function "extendWith".
 | 
			
		||||
    @PublishedApi
 | 
			
		||||
    internal var extension: InputStream? = null
 | 
			
		||||
    internal var extensionInputStream: Supplier<InputStream>? = null
 | 
			
		||||
 | 
			
		||||
    // Inlining is necessary to get the class loader that loaded the patch
 | 
			
		||||
    // to load the extension from the resources.
 | 
			
		||||
@@ -421,8 +398,11 @@ class BytecodePatchBuilder internal constructor(
 | 
			
		||||
     */
 | 
			
		||||
    @Suppress("NOTHING_TO_INLINE")
 | 
			
		||||
    inline fun extendWith(extension: String) = apply {
 | 
			
		||||
        this.extension = object {}.javaClass.classLoader.getResourceAsStream(extension)
 | 
			
		||||
            ?: throw PatchException("Extension \"$extension\" not found")
 | 
			
		||||
        val classLoader = object {}.javaClass.classLoader
 | 
			
		||||
 | 
			
		||||
        extensionInputStream = Supplier {
 | 
			
		||||
            classLoader.getResourceAsStream(extension) ?: throw PatchException("Extension \"$extension\" not found")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun build() = BytecodePatch(
 | 
			
		||||
@@ -432,8 +412,7 @@ class BytecodePatchBuilder internal constructor(
 | 
			
		||||
        compatiblePackages,
 | 
			
		||||
        dependencies,
 | 
			
		||||
        options,
 | 
			
		||||
        fingerprints,
 | 
			
		||||
        extension,
 | 
			
		||||
        extensionInputStream,
 | 
			
		||||
        executionBlock,
 | 
			
		||||
        finalizeBlock,
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -32,14 +32,19 @@ class ResourcePatchContext internal constructor(
 | 
			
		||||
    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].
 | 
			
		||||
@@ -201,11 +206,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.
 | 
			
		||||
@@ -227,10 +232,4 @@ class ResourcePatchContext internal constructor(
 | 
			
		||||
         */
 | 
			
		||||
        NONE,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inner class DocumentOperatable {
 | 
			
		||||
        operator fun get(inputStream: InputStream) = Document(inputStream)
 | 
			
		||||
 | 
			
		||||
        operator fun get(path: String) = Document(this@ResourcePatchContext[path])
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ 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.reference.MethodReference
 | 
			
		||||
import com.android.tools.smali.dexlib2.util.MethodUtil
 | 
			
		||||
import kotlin.reflect.KProperty
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A navigator for methods.
 | 
			
		||||
@@ -27,7 +28,7 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
 | 
			
		||||
class MethodNavigator internal constructor(private val context: BytecodePatchContext, private var startMethod: MethodReference) {
 | 
			
		||||
    private var lastNavigatedMethodReference = startMethod
 | 
			
		||||
 | 
			
		||||
    private val lastNavigatedMethodInstructions get() = with(immutable()) {
 | 
			
		||||
    private val lastNavigatedMethodInstructions get() = with(original()) {
 | 
			
		||||
        instructionsOrNull ?: throw NavigateException("Method $definingClass.$name does not have an implementation.")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -76,15 +77,22 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
 | 
			
		||||
     *
 | 
			
		||||
     * @return The last navigated method mutably.
 | 
			
		||||
     */
 | 
			
		||||
    fun mutable() = context.classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
 | 
			
		||||
    fun stop() = context.classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
 | 
			
		||||
        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.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The last navigated method immutably.
 | 
			
		||||
     */
 | 
			
		||||
    fun immutable() = context.classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
 | 
			
		||||
    fun original() = context.classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Predicate to match the class defining the current method reference.
 | 
			
		||||
 
 | 
			
		||||
@@ -3,21 +3,21 @@ package app.revanced.patcher
 | 
			
		||||
import app.revanced.patcher.patch.*
 | 
			
		||||
import app.revanced.patcher.patch.BytecodePatchContext.LookupMaps
 | 
			
		||||
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.ImmutableMethod
 | 
			
		||||
import io.mockk.every
 | 
			
		||||
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.runBlocking
 | 
			
		||||
import org.junit.jupiter.api.BeforeEach
 | 
			
		||||
import org.junit.jupiter.api.assertDoesNotThrow
 | 
			
		||||
import org.junit.jupiter.api.assertAll
 | 
			
		||||
import java.util.logging.Logger
 | 
			
		||||
import kotlin.test.Test
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
import kotlin.test.assertNull
 | 
			
		||||
import kotlin.test.assertTrue
 | 
			
		||||
import kotlin.test.*
 | 
			
		||||
 | 
			
		||||
internal object PatcherTest {
 | 
			
		||||
    private lateinit var patcher: Patcher
 | 
			
		||||
@@ -151,19 +151,15 @@ internal object PatcherTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `throws if unmatched fingerprint match is delegated`() {
 | 
			
		||||
        val patch = bytecodePatch {
 | 
			
		||||
            // Fingerprint can never match.
 | 
			
		||||
            val match by fingerprint { }
 | 
			
		||||
            // Manually add the fingerprint.
 | 
			
		||||
            app.revanced.patcher.fingerprint { }()
 | 
			
		||||
 | 
			
		||||
            execute {
 | 
			
		||||
                // Fingerprint can never match.
 | 
			
		||||
                val match by fingerprint { }
 | 
			
		||||
 | 
			
		||||
                // Throws, because the fingerprint can't be matched.
 | 
			
		||||
                match.patternMatch
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assertEquals(2, patch.fingerprints.size)
 | 
			
		||||
 | 
			
		||||
        assertTrue(
 | 
			
		||||
            patch().exception != null,
 | 
			
		||||
            "Expected an exception because the fingerprint can't match.",
 | 
			
		||||
@@ -172,44 +168,6 @@ internal object PatcherTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    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)
 | 
			
		||||
        every { with(patcher.context.bytecodeContext) { any<Set<Patch<*>>>().mergeExtensions() } } 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)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun mockClassWithMethod() {
 | 
			
		||||
        every { patcher.context.bytecodeContext.classes } returns ProxyClassList(
 | 
			
		||||
            mutableListOf(
 | 
			
		||||
                ImmutableClassDef(
 | 
			
		||||
@@ -235,6 +193,50 @@ 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 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.match
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        patches()
 | 
			
		||||
 | 
			
		||||
        assertAll(
 | 
			
		||||
            "Expected fingerprints to match.",
 | 
			
		||||
            { assertNotNull(fingerprint._match) },
 | 
			
		||||
            { assertNotNull(fingerprint2._match) },
 | 
			
		||||
            { assertNotNull(fingerprint3._match) },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 { 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)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
package app.revanced.patcher.patch
 | 
			
		||||
 | 
			
		||||
import app.revanced.patcher.fingerprint
 | 
			
		||||
import kotlin.test.Test
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
 | 
			
		||||
@@ -24,23 +23,6 @@ internal object PatchTest {
 | 
			
		||||
        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
 | 
			
		||||
    fun `can create patch with dependencies`() {
 | 
			
		||||
        val patch = bytecodePatch(name = "Test") {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user