Compare commits

...

11 Commits

Author SHA1 Message Date
oSumAtrIX 7fee767b0b
feat: Use vararg for access flags 2024-04-28 19:19:38 +02:00
oSumAtrIX d6012a4fe5
docs: Add note 2024-04-28 18:35:51 +02:00
oSumAtrIX ea5b76e1d1
docs: Improve wording 2024-04-28 18:33:40 +02:00
oSumAtrIX c2b4f2cda0
docs: Fix mistakes 2024-04-28 18:32:58 +02:00
oSumAtrIX 25d1b65cf1
docs: Fix mistakes 2024-04-28 18:30:55 +02:00
oSumAtrIX 2503283941
docs: Update docs to match new changes 2024-04-28 02:47:58 +02:00
oSumAtrIX 85c83e0d34
fix: Modernize some APIs 2024-04-28 02:47:07 +02:00
oSumAtrIX cf59950b95
fix: Move annotation to DSL api 2024-04-28 01:21:00 +02:00
oSumAtrIX c84c0e0754
feat: Simplify options API 2024-04-28 00:12:49 +02:00
oSumAtrIX 347cda616e
feat: Use a builder for declaring dependencies 2024-04-27 20:46:21 +02:00
oSumAtrIX e4e4c2a7f4
feat: Use a builder for declaring compatible packages 2024-04-27 02:39:40 +02:00
26 changed files with 876 additions and 820 deletions

View File

@ -1,7 +1,3 @@
public abstract interface class app/revanced/patcher/IntegrationsConsumer {
public abstract fun acceptIntegrations (Ljava/util/Set;)V
}
public abstract interface annotation class app/revanced/patcher/InternalApi : java/lang/annotation/Annotation {
}
@ -13,10 +9,9 @@ public final class app/revanced/patcher/PackageMetadata {
public abstract interface class app/revanced/patcher/PatchExecutorFunction : java/util/function/Function {
}
public final class app/revanced/patcher/Patcher : app/revanced/patcher/IntegrationsConsumer, app/revanced/patcher/PatchExecutorFunction, app/revanced/patcher/PatchesConsumer, java/io/Closeable, java/util/function/Supplier {
public final class app/revanced/patcher/Patcher : app/revanced/patcher/PatchExecutorFunction, app/revanced/patcher/PatcherResultSupplier, app/revanced/patcher/PatchesConsumer {
public fun <init> (Lapp/revanced/patcher/PatcherConfig;)V
public fun acceptIntegrations (Ljava/util/Set;)V
public fun acceptPatches (Ljava/util/Set;)V
public fun accept (Ljava/util/Set;Ljava/util/Set;)V
public synthetic fun apply (Ljava/lang/Object;)Ljava/lang/Object;
public fun apply (Z)Lkotlinx/coroutines/flow/Flow;
public fun close ()V
@ -59,15 +54,19 @@ public final class app/revanced/patcher/PatcherResult$PatchedResources {
public final fun getResourcesApk ()Ljava/io/File;
}
public abstract interface class app/revanced/patcher/PatcherResultSupplier : java/io/Closeable, java/util/function/Supplier {
}
public abstract interface class app/revanced/patcher/PatchesConsumer {
public abstract fun acceptPatches (Ljava/util/Set;)V
public abstract fun accept (Ljava/util/Set;Ljava/util/Set;)V
}
public final class app/revanced/patcher/PatchesConsumer$DefaultImpls {
public static synthetic fun accept$default (Lapp/revanced/patcher/PatchesConsumer;Ljava/util/Set;Ljava/util/Set;ILjava/lang/Object;)V
}
public final class app/revanced/patcher/extensions/ExtensionsKt {
public static final fun newLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)Lcom/android/tools/smali/dexlib2/builder/Label;
public static final fun or (ILcom/android/tools/smali/dexlib2/AccessFlags;)I
public static final fun or (Lcom/android/tools/smali/dexlib2/AccessFlags;I)I
public static final fun or (Lcom/android/tools/smali/dexlib2/AccessFlags;Lcom/android/tools/smali/dexlib2/AccessFlags;)I
}
public final class app/revanced/patcher/extensions/InstructionExtensions {
@ -100,23 +99,17 @@ public final class app/revanced/patcher/extensions/InstructionExtensions {
public final fun replaceInstructions (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;ILjava/util/List;)V
}
public abstract interface annotation class app/revanced/patcher/fingerprint/FuzzyPatternScanMethod : java/lang/annotation/Annotation {
public abstract fun threshold ()I
}
public final class app/revanced/patcher/fingerprint/MethodFingerprint {
public fun <init> ()V
public fun <init> (Ljava/lang/String;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function2;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getFuzzyPatternScanMethod ()Lapp/revanced/patcher/fingerprint/FuzzyPatternScanMethod;
public final fun getResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;
public final fun resolve (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
public final fun resolve (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
}
public final class app/revanced/patcher/fingerprint/MethodFingerprintBuilder {
public fun <init> ()V
public final fun accessFlags (I)V
public final fun accessFlags (Lcom/android/tools/smali/dexlib2/AccessFlags;)V
public final fun accessFlags ([Lcom/android/tools/smali/dexlib2/AccessFlags;)V
public final fun custom (Lkotlin/jvm/functions/Function2;)V
public final fun opcodes (Ljava/lang/String;)V
public final fun opcodes ([Lcom/android/tools/smali/dexlib2/Opcode;)V
@ -126,8 +119,10 @@ public final class app/revanced/patcher/fingerprint/MethodFingerprintBuilder {
}
public final class app/revanced/patcher/fingerprint/MethodFingerprintKt {
public static final fun methodFingerprint (Lapp/revanced/patcher/patch/BytecodePatchBuilder;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/fingerprint/MethodFingerprint;
public static final fun methodFingerprint (Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/fingerprint/MethodFingerprint;
public static final fun methodFingerprint (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/fingerprint/MethodFingerprint;
public static final fun methodFingerprint (Lapp/revanced/patcher/patch/BytecodePatchBuilder;ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/fingerprint/MethodFingerprint;
public static synthetic fun methodFingerprint$default (ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/fingerprint/MethodFingerprint;
public static synthetic fun methodFingerprint$default (Lapp/revanced/patcher/patch/BytecodePatchBuilder;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/fingerprint/MethodFingerprint;
public static final fun resolve (Ljava/lang/Iterable;Lapp/revanced/patcher/patch/BytecodePatchContext;Ljava/lang/Iterable;)V
}
@ -173,13 +168,119 @@ public final class app/revanced/patcher/patch/BytecodePatchBuilder : app/revance
}
public final class app/revanced/patcher/patch/BytecodePatchContext : app/revanced/patcher/patch/PatchContext {
public final fun findClass (Ljava/lang/String;)Lapp/revanced/patcher/util/proxy/ClassProxy;
public final fun findClass (Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/proxy/ClassProxy;
public final fun classBy (Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/proxy/ClassProxy;
public final fun classByType (Ljava/lang/String;)Lapp/revanced/patcher/util/proxy/ClassProxy;
public 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 proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
public final fun toMethodWalker (Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/method/MethodWalker;
}
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 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 final fun getDefault ()Ljava/lang/Object;
public final fun getDescription ()Ljava/lang/String;
public final fun getKey ()Ljava/lang/String;
public final fun getRequired ()Z
public final fun getTitle ()Ljava/lang/String;
public final fun getType ()Lkotlin/reflect/KType;
public final fun getValidator ()Lkotlin/jvm/functions/Function2;
public final fun getValue ()Ljava/lang/Object;
public final fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object;
public final fun getValues ()Ljava/util/Map;
public final fun reset ()V
public final fun setValue (Ljava/lang/Object;)V
public final fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V
public fun toString ()Ljava/lang/String;
}
public abstract class app/revanced/patcher/patch/OptionException : java/lang/Exception {
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/patcher/patch/OptionException$InvalidValueTypeException : app/revanced/patcher/patch/OptionException {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/patcher/patch/OptionException$OptionNotFoundException : app/revanced/patcher/patch/OptionException {
public fun <init> (Ljava/lang/String;)V
}
public final class app/revanced/patcher/patch/OptionException$ValueRequiredException : app/revanced/patcher/patch/OptionException {
public fun <init> (Lapp/revanced/patcher/patch/Option;)V
}
public final class app/revanced/patcher/patch/OptionException$ValueValidationException : app/revanced/patcher/patch/OptionException {
public fun <init> (Ljava/lang/Object;Lapp/revanced/patcher/patch/Option;)V
}
public final class app/revanced/patcher/patch/OptionKt {
public static final fun booleanOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
public static synthetic fun booleanOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
public static final fun booleansOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
public static synthetic fun booleansOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
public static final fun floatOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
public static synthetic fun floatOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
public static final fun floatsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
public static synthetic fun floatsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
public static final fun intOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
public static synthetic fun intOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
public static final fun intsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
public static synthetic fun intsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
public static final fun longOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
public static synthetic fun longOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
public static final fun longsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
public static synthetic fun longsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
public static final fun stringOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
public static synthetic fun stringOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
public static final fun stringsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
public static synthetic fun stringsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
}
public final class app/revanced/patcher/patch/Options : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker {
public fun <init> (Ljava/util/Map;)V
public fun <init> (Ljava/util/Set;)V
public fun clear ()V
public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
public fun compute (Ljava/lang/String;Ljava/util/function/BiFunction;)Lapp/revanced/patcher/patch/Option;
public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;
public fun computeIfAbsent (Ljava/lang/String;Ljava/util/function/Function;)Lapp/revanced/patcher/patch/Option;
public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
public fun computeIfPresent (Ljava/lang/String;Ljava/util/function/BiFunction;)Lapp/revanced/patcher/patch/Option;
public final fun containsKey (Ljava/lang/Object;)Z
public fun containsKey (Ljava/lang/String;)Z
public fun containsValue (Lapp/revanced/patcher/patch/Option;)Z
public final fun containsValue (Ljava/lang/Object;)Z
public final fun entrySet ()Ljava/util/Set;
public final fun get (Ljava/lang/Object;)Lapp/revanced/patcher/patch/Option;
public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object;
public fun get (Ljava/lang/String;)Lapp/revanced/patcher/patch/Option;
public fun getEntries ()Ljava/util/Set;
public fun getKeys ()Ljava/util/Set;
public fun getSize ()I
public fun getValues ()Ljava/util/Collection;
public fun isEmpty ()Z
public final fun keySet ()Ljava/util/Set;
public synthetic fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
public fun merge (Ljava/lang/String;Lapp/revanced/patcher/patch/Option;Ljava/util/function/BiFunction;)Lapp/revanced/patcher/patch/Option;
public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
public fun put (Ljava/lang/String;Lapp/revanced/patcher/patch/Option;)Lapp/revanced/patcher/patch/Option;
public fun putAll (Ljava/util/Map;)V
public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
public fun putIfAbsent (Ljava/lang/String;Lapp/revanced/patcher/patch/Option;)Lapp/revanced/patcher/patch/Option;
public fun remove (Ljava/lang/Object;)Lapp/revanced/patcher/patch/Option;
public synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object;
public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z
public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
public fun replace (Ljava/lang/String;Lapp/revanced/patcher/patch/Option;)Lapp/revanced/patcher/patch/Option;
public fun replace (Ljava/lang/String;Lapp/revanced/patcher/patch/Option;Lapp/revanced/patcher/patch/Option;)Z
public fun replaceAll (Ljava/util/function/BiFunction;)V
public final fun set (Ljava/lang/String;Ljava/lang/Object;)V
public final fun size ()I
public final fun values ()Ljava/util/Collection;
}
public abstract class app/revanced/patcher/patch/Patch {
@ -190,7 +291,7 @@ public abstract class app/revanced/patcher/patch/Patch {
public final fun getDependencies ()Ljava/util/Set;
public final fun getDescription ()Ljava/lang/String;
public final fun getName ()Ljava/lang/String;
public final fun getOptions ()Lapp/revanced/patcher/patch/PatchOptions;
public final fun getOptions ()Lapp/revanced/patcher/patch/Options;
public final fun getRequiresIntegrations ()Z
public final fun getUse ()Z
public fun toString ()Ljava/lang/String;
@ -198,6 +299,8 @@ public abstract class app/revanced/patcher/patch/Patch {
public abstract class app/revanced/patcher/patch/PatchBuilder {
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun compatibleWith (Lkotlin/jvm/functions/Function1;)V
public final fun dependsOn (Lkotlin/jvm/functions/Function1;)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;
@ -209,14 +312,22 @@ public abstract class app/revanced/patcher/patch/PatchBuilder {
protected final fun getOptions ()Ljava/util/Set;
protected final fun getRequiresIntegrations ()Z
protected final fun getUse ()Z
public final fun invoke (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/patcher/patch/Patch;
public final fun invoke (Ljava/lang/String;[Ljava/lang/String;)V
public final fun option ([Lapp/revanced/patcher/patch/PatchOption;)V
public final fun invoke (Lapp/revanced/patcher/patch/Option;)Lapp/revanced/patcher/patch/Option;
protected final fun setCompatiblePackages (Ljava/util/Set;)V
protected final fun setDependencies (Ljava/util/Set;)V
protected final fun setExecutionBlock (Lkotlin/jvm/functions/Function1;)V
protected final fun setFinalizeBlock (Lkotlin/jvm/functions/Function1;)V
}
public final class app/revanced/patcher/patch/PatchBuilder$CompatibleWithBuilder {
public final fun invoke (Ljava/lang/String;)V
public final fun invoke (Ljava/lang/String;[Ljava/lang/String;)V
}
public final class app/revanced/patcher/patch/PatchBuilder$DependenciesBuilder {
public final fun invoke (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/patcher/patch/Patch;
}
public abstract interface class app/revanced/patcher/patch/PatchContext : java/util/function/Supplier {
}
@ -227,20 +338,20 @@ public final class app/revanced/patcher/patch/PatchException : java/lang/Excepti
}
public final class app/revanced/patcher/patch/PatchKt {
public static final fun bytecodePatch (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun bytecodePatch (Lapp/revanced/patcher/patch/PatchBuilder$DependenciesBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun bytecodePatch (Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
public static synthetic fun bytecodePatch$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
public static synthetic fun bytecodePatch$default (Lapp/revanced/patcher/patch/PatchBuilder$DependenciesBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
public static synthetic fun bytecodePatch$default (Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun loadPatchesFromDex (Ljava/util/Set;Ljava/io/File;)Ljava/util/Set;
public static synthetic fun loadPatchesFromDex$default (Ljava/util/Set;Ljava/io/File;ILjava/lang/Object;)Ljava/util/Set;
public static final fun loadPatchesFromJar (Ljava/util/Set;)Ljava/util/Set;
public static final fun rawResourcePatch (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/RawResourcePatch;
public static final fun rawResourcePatch (Lapp/revanced/patcher/patch/PatchBuilder$DependenciesBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/RawResourcePatch;
public static final fun rawResourcePatch (Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/RawResourcePatch;
public static synthetic fun rawResourcePatch$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/RawResourcePatch;
public static synthetic fun rawResourcePatch$default (Lapp/revanced/patcher/patch/PatchBuilder$DependenciesBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/RawResourcePatch;
public static synthetic fun rawResourcePatch$default (Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/RawResourcePatch;
public static final fun resourcePatch (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/ResourcePatch;
public static final fun resourcePatch (Lapp/revanced/patcher/patch/PatchBuilder$DependenciesBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/ResourcePatch;
public static final fun resourcePatch (Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/ResourcePatch;
public static synthetic fun resourcePatch$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/ResourcePatch;
public static synthetic fun resourcePatch$default (Lapp/revanced/patcher/patch/PatchBuilder$DependenciesBuilder;Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/ResourcePatch;
public static synthetic fun resourcePatch$default (Ljava/lang/String;Ljava/lang/String;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/ResourcePatch;
}
@ -264,113 +375,6 @@ public abstract class app/revanced/patcher/patch/PatchLoader : java/util/Set, ko
public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
}
public class app/revanced/patcher/patch/PatchOption {
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;Lkotlin/jvm/functions/Function2;)V
public final fun getDefault ()Ljava/lang/Object;
public final fun getDescription ()Ljava/lang/String;
public final fun getKey ()Ljava/lang/String;
public final fun getRequired ()Z
public final fun getTitle ()Ljava/lang/String;
public final fun getValidator ()Lkotlin/jvm/functions/Function2;
public final fun getValue ()Ljava/lang/Object;
public final fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object;
public final fun getValueType ()Ljava/lang/String;
public final fun getValues ()Ljava/util/Map;
public fun reset ()V
public final fun setValue (Ljava/lang/Object;)V
public final fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V
public fun toString ()Ljava/lang/String;
}
public abstract class app/revanced/patcher/patch/PatchOptionException : java/lang/Exception {
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/patcher/patch/PatchOptionException$InvalidValueTypeException : app/revanced/patcher/patch/PatchOptionException {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/patcher/patch/PatchOptionException$PatchOptionNotFoundException : app/revanced/patcher/patch/PatchOptionException {
public fun <init> (Ljava/lang/String;)V
}
public final class app/revanced/patcher/patch/PatchOptionException$ValueRequiredException : app/revanced/patcher/patch/PatchOptionException {
public fun <init> (Lapp/revanced/patcher/patch/PatchOption;)V
}
public final class app/revanced/patcher/patch/PatchOptionException$ValueValidationException : app/revanced/patcher/patch/PatchOptionException {
public fun <init> (Ljava/lang/Object;Lapp/revanced/patcher/patch/PatchOption;)V
}
public final class app/revanced/patcher/patch/PatchOptionKt {
public static final fun addNewPatchOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;Lkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/PatchOption;
public static synthetic fun addNewPatchOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public static final fun booleanArrayPatchOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;[Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/PatchOption;
public static synthetic fun booleanArrayPatchOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;[Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public static final fun booleanPatchOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/PatchOption;
public static synthetic fun booleanPatchOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public static final fun floatArrayPatchOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;[Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/PatchOption;
public static synthetic fun floatArrayPatchOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;[Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public static final fun floatPatchOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/PatchOption;
public static synthetic fun floatPatchOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public static final fun intArrayPatchOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;[Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/PatchOption;
public static synthetic fun intArrayPatchOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;[Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public static final fun intPatchOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/PatchOption;
public static synthetic fun intPatchOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public static final fun longArrayPatchOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;[Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/PatchOption;
public static synthetic fun longArrayPatchOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;[Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public static final fun longPatchOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/PatchOption;
public static synthetic fun longPatchOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public static final fun stringArrayPatchOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;[Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/PatchOption;
public static synthetic fun stringArrayPatchOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;[Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public static final fun stringPatchOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/PatchOption;
public static synthetic fun stringPatchOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
}
public final class app/revanced/patcher/patch/PatchOptions : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker {
public fun <init> (Ljava/util/Map;)V
public fun <init> (Ljava/util/Set;)V
public fun clear ()V
public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
public fun compute (Ljava/lang/String;Ljava/util/function/BiFunction;)Lapp/revanced/patcher/patch/PatchOption;
public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;
public fun computeIfAbsent (Ljava/lang/String;Ljava/util/function/Function;)Lapp/revanced/patcher/patch/PatchOption;
public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
public fun computeIfPresent (Ljava/lang/String;Ljava/util/function/BiFunction;)Lapp/revanced/patcher/patch/PatchOption;
public final fun containsKey (Ljava/lang/Object;)Z
public fun containsKey (Ljava/lang/String;)Z
public fun containsValue (Lapp/revanced/patcher/patch/PatchOption;)Z
public final fun containsValue (Ljava/lang/Object;)Z
public final fun entrySet ()Ljava/util/Set;
public final fun get (Ljava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object;
public fun get (Ljava/lang/String;)Lapp/revanced/patcher/patch/PatchOption;
public fun getEntries ()Ljava/util/Set;
public fun getKeys ()Ljava/util/Set;
public fun getSize ()I
public fun getValues ()Ljava/util/Collection;
public fun isEmpty ()Z
public final fun keySet ()Ljava/util/Set;
public synthetic fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
public fun merge (Ljava/lang/String;Lapp/revanced/patcher/patch/PatchOption;Ljava/util/function/BiFunction;)Lapp/revanced/patcher/patch/PatchOption;
public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
public fun put (Ljava/lang/String;Lapp/revanced/patcher/patch/PatchOption;)Lapp/revanced/patcher/patch/PatchOption;
public fun putAll (Ljava/util/Map;)V
public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
public fun putIfAbsent (Ljava/lang/String;Lapp/revanced/patcher/patch/PatchOption;)Lapp/revanced/patcher/patch/PatchOption;
public fun remove (Ljava/lang/Object;)Lapp/revanced/patcher/patch/PatchOption;
public synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object;
public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z
public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
public fun replace (Ljava/lang/String;Lapp/revanced/patcher/patch/PatchOption;)Lapp/revanced/patcher/patch/PatchOption;
public fun replace (Ljava/lang/String;Lapp/revanced/patcher/patch/PatchOption;Lapp/revanced/patcher/patch/PatchOption;)Z
public fun replaceAll (Ljava/util/function/BiFunction;)V
public final fun set (Ljava/lang/String;Ljava/lang/Object;)V
public final fun size ()I
public final fun values ()Ljava/util/Collection;
}
public final class app/revanced/patcher/patch/PatchResult {
public final fun getException ()Lapp/revanced/patcher/patch/PatchException;
public final fun getPatch ()Lapp/revanced/patcher/patch/Patch;
@ -477,33 +481,51 @@ public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w
public fun setXmlVersion (Ljava/lang/String;)V
}
public final class app/revanced/patcher/util/ProxyClassList : java/util/Set, kotlin/jvm/internal/markers/KMutableSet {
public final fun add (Lapp/revanced/patcher/util/proxy/ClassProxy;)Z
public final class app/revanced/patcher/util/MethodNavigator {
public final fun at ([I)Lapp/revanced/patcher/util/MethodNavigator;
public final fun getReference ()Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;
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 setReference (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)V
}
public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList {
public fun add (ILcom/android/tools/smali/dexlib2/iface/ClassDef;)V
public synthetic fun add (ILjava/lang/Object;)V
public fun add (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
public synthetic fun add (Ljava/lang/Object;)Z
public fun addAll (ILjava/util/Collection;)Z
public fun addAll (Ljava/util/Collection;)Z
public fun clear ()V
public fun contains (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
public final fun contains (Ljava/lang/Object;)Z
public fun containsAll (Ljava/util/Collection;)Z
public fun get (I)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
public synthetic fun get (I)Ljava/lang/Object;
public fun getSize ()I
public fun indexOf (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)I
public final fun indexOf (Ljava/lang/Object;)I
public fun isEmpty ()Z
public fun iterator ()Ljava/util/Iterator;
public fun lastIndexOf (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)I
public final fun lastIndexOf (Ljava/lang/Object;)I
public fun listIterator ()Ljava/util/ListIterator;
public fun listIterator (I)Ljava/util/ListIterator;
public final fun remove (I)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
public synthetic fun remove (I)Ljava/lang/Object;
public fun remove (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
public final fun remove (Ljava/lang/Object;)Z
public fun removeAll (Ljava/util/Collection;)Z
public fun removeAt (I)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
public fun retainAll (Ljava/util/Collection;)Z
public fun set (ILcom/android/tools/smali/dexlib2/iface/ClassDef;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
public synthetic fun set (ILjava/lang/Object;)Ljava/lang/Object;
public final fun size ()I
public fun subList (II)Ljava/util/List;
public fun toArray ()[Ljava/lang/Object;
public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
}
public final class app/revanced/patcher/util/method/MethodWalker {
public final fun getMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
public final fun nextMethod (IZ)Lapp/revanced/patcher/util/method/MethodWalker;
public static synthetic fun nextMethod$default (Lapp/revanced/patcher/util/method/MethodWalker;IZILjava/lang/Object;)Lapp/revanced/patcher/util/method/MethodWalker;
}
public final class app/revanced/patcher/util/proxy/ClassProxy {
public final fun getImmutableClass ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
public final fun getMutableClass ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;

View File

@ -71,29 +71,26 @@ ReVanced Patcher has a simple API that allows you to load patches and integratio
Later on, you will learn how to create patches.
```kt
// Executed patches do not necessarily reset their state.
// For that reason it is important to create a new instance of the PatchBundleLoader
// once the patches are executed instead of reusing the same instance of patches loaded by PatchBundleLoader.
val patches: PatchSet /* = Set<Patch<*>> */ = PatchBundleLoader.Jar(File("revanced-patches.jar"))
val patches = loadPatchesFromJar(File("revanced-patches.jar"))
val integrations = setOf(File("integrations.apk"))
// Instantiating the patcher will decode the manifest of the APK file to read the package and version name.
val patcherConfig = PatcherConfig(apkFile = File("some.apk"))
val patcherResult = Patcher(patcherConfig).use { patcher ->
patcher.apply {
acceptIntegrations(integrations)
acceptPatches(patches)
patcher.accept(patches, integrations)
// Execute patches.
runBlocking {
patcher.apply(returnOnError = false).collect { patchResult ->
if (patchResult.exception != null)
println("${patchResult.patchName} failed:\n${patchResult.exception}")
else
println("${patchResult.patchName} succeeded")
}
// Execute patches.
patcher.runBlocking {
patcher.apply(returnOnError = false).collect { patchResult ->
if (patchResult.exception != null)
println("${patchResult.patchName} failed:\n${patchResult.exception}")
else
println("${patchResult.patchName} succeeded")
}
}.get()
}
// Compile and save the patched APK file.
patcher.get()
}
// The result of the patcher contains the modified components of the APK file that can be repackaged into a new APK file.

View File

@ -72,14 +72,19 @@ Throughout the documentation, the following example will be used to demonstrate
package app.revanced.patches.ads.fingerprints
object ShowAdsFingerprint : MethodFingerprint(
returnType = "Z",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Z"),
opcodes = listOf(Opcode.RETURN),
strings = listOf("pro"),
customFingerprint = { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;" }
)
methodFingerprint {
returns("Z")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters("Z")
opcodes(Opcode.RETURN)
strings("pro")
custom { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;" }
}
```
## 🔎 Reconstructing the original code from a fingerprint
@ -91,22 +96,22 @@ The fingerprint contains the following information:
- Method signature:
```kt
returnType = "Z",
access = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Z"),
returns("Z")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters("Z")
```
- Method implementation:
```kt
opcodes = listOf(Opcode.RETURN)
strings = listOf("pro"),
opcodes(Opcode.RETURN)
strings("pro"),
```
- Package and class name:
```kt
customFingerprint = { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;"}
custom = { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;"}
```
With this information, the original code can be reconstructed:
@ -129,49 +134,63 @@ With this information, the original code can be reconstructed:
> [!TIP]
> 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 is likely to change with each update in an obfuscated app. In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the same.
> A method's name is not included in the fingerprint because it will likely change with each update in an obfuscated app. In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the same.
## 🔨 How to use fingerprints
After creating a fingerprint, add it to the constructor of a `BytecodePatch`:
Fingerprints can be added to a patch by directly creating and adding them or by invoking them manually. Fingerprints added to a patch are resolved by ReVanced Patcher before the patch is executed.
```kt
object DisableAdsPatch : BytecodePatch(
setOf(ShowAdsFingerprint)
) {
val fingerprint = methodFingerprint {
// ...
}
```
}
> [!NOTE]
> Fingerprints passed to the constructor of `BytecodePatch` are resolved by ReVanced Patcher before the patch is executed.
val patch = bytecodePatch {
// Directly create and add a fingerprint.
methodFingerprint {
// ...
}
// Add a fingerprint manually by invoking it.
fingerprint()
}
```
> [!TIP]
> Multiple patches can share fingerprints. If a fingerprint is resolved once, it will not be resolved again.
> [!TIP]
> If a fingerprint has an opcode pattern, you can use the `FuzzyPatternScanMethod` annotation to fuzzy match the pattern.
> If a fingerprint has an opcode pattern, you can use the `fuzzyPatternScanThreshhold` parameter of the `opcode` function to fuzzy match the pattern.
> Opcode pattern arrays can contain `null` values to indicate that the opcode at the index is unknown.
> Any opcode will match to a `null` value.
> [!WARNING]
> If the fingerprint can not be resolved because it does not match any method, the result of a fingerprint is `null`.
> Any opcode will match to a `null` value:
>
> ```kt
> methodFingerprint(fuzzyPatternScanThreshhold = 0.5) {
> opcodes(
> Opcode.ICONST_0,
> null,
> Opcode.ICONST_1,
> Opcode.IRETURN,
> )
>}
> ```
Once the fingerprint is resolved, the result can be used in the patch:
```kt
object DisableAdsPatch : BytecodePatch(
setOf(ShowAdsFingerprint)
) {
override fun execute(context: BytecodeContext) {
val result = ShowAdsFingerprint.result
?: throw PatchException("ShowAdsFingerprint not found")
val patch = bytecodePatch {
// Add a fingerprint and delegate it's result to a variable.
val result by showAdsFingerprint
// ...
execute {
val method = result.method
}
}
```
> [!WARNING]
> If the fingerprint can not be resolved because it does not match any method, the result of a fingerprint is `null`. If the result is delegated to a variable, accessing it will raise an exception.
The result of a fingerprint that resolved successfully contains mutable and immutable references to the method and the class it is defined in.
```kt
@ -207,8 +226,8 @@ class MethodFingerprintScanResult(
## 🏹 Manual resolution of fingerprints
Unless a fingerprint is added to the constructor of `BytecodePatch`, the fingerprint will not be resolved automatically by ReVanced Patcher before the patch is executed.
Instead, the fingerprint can be resolved manually using various overloads of the `resolve` function of a fingerprint.
Unless a fingerprint is added to a patch, the fingerprint will not be resolved automatically by ReVanced Patcher before the patch is executed.
Instead, the fingerprint can be resolved manually using various overloads of a fingerprint's `resolve` function.
You can resolve a fingerprint in the following ways:
@ -217,12 +236,11 @@ You can resolve a fingerprint in the following ways:
If you have a known list of classes you know the fingerprint can resolve on, you can resolve the fingerprint on the list of classes:
```kt
override fun execute(context: BytecodeContext) {
val result = ShowAdsFingerprint.also { it.resolve(context, context.classes) }.result
?: throw PatchException("ShowAdsFingerprint not found")
// ...
}
execute {
val result = showAdsFingerprint.also {
it.resolve(context, this.classes)
}.result ?: throw PatchException("showAdsFingerprint not found")
}
```
- On a **single class**, if the fingerprint can resolve on a single known class
@ -230,13 +248,12 @@ You can resolve a fingerprint in the following ways:
If you know the fingerprint can resolve to a method in a specific class, you can resolve the fingerprint on the class:
```kt
override fun execute(context: BytecodeContext) {
val adsLoaderClass = context.classes.single { it.name == "Lcom/some/app/ads/Loader;" }
execute {
val adsLoaderClass = classes.single { it.name == "Lcom/some/app/ads/Loader;" }
val result = ShowAdsFingerprint.also { it.resolve(context, adsLoaderClass) }.result
?: throw PatchException("ShowAdsFingerprint not found")
// ...
val result = showAdsFingerprint.also {
it.resolve(context, adsLoaderClass)
}.result ?: throw PatchException("showAdsFingerprint not found")
}
```
@ -246,13 +263,13 @@ You can resolve a fingerprint in the following ways:
A fingerprint can be leveraged to extract such information from a method instead of manually figuring it out:
```kt
override fun execute(context: BytecodeContext) {
val adsFingerprintResult = ShowAdsFingerprint.result
?: throw PatchException("ShowAdsFingerprint not found")
execute {
val adsFingerprintResult = showAdsFingerprint.result
?: throw PatchException("showAdsFingerprint not found")
val proStringsFingerprint = object : MethodFingerprint(
strings = listOf("free", "trial")
) {}
val proStringsFingerprint = methodFingerprint {
strings("free", "trial")
}
proStringsFingerprint.also {
it.resolve(context, adsFingerprintResult.method)
@ -260,13 +277,12 @@ You can resolve a fingerprint in the following ways:
result.scanResult.stringsScanResult!!.matches.forEach { match ->
println("The index of the string '${match.string}' is ${match.index}")
}
} ?: throw PatchException("pro strings fingerprint not found")
}
```
> [!TIP]
> To see real-world examples of fingerprints, check out the [ReVanced Patches](https://github.com/revanced/revanced-patches) repository.
> To see real-world examples of fingerprints, check out the repository for [ReVanced Patches](https://github.com/revanced/revanced-patches).
## ⏭️ What's next

View File

@ -64,145 +64,128 @@ Learn the API to create patches using ReVanced Patcher.
## ⛳️ Example patch
Throughout the documentation, the following example will be used to demonstrate the concepts of patches:
```kt
package app.revanced.patches.ads
@Patch(
val disableAdsPatch = bytecodePatch(
name = "Disable ads",
description = "Disable ads in the app.",
dependencies = [DisableAdsResourcePatch::class],
compatiblePackages = [CompatiblePackage("com.some.app", ["1.3.0"])]
)
object DisableAdsPatch : BytecodePatch(
setOf(ShowAdsFingerprint)
) {
override fun execute(context: BytecodeContext) {
ShowAdsFingerprint.result?.let { result ->
result.mutableMethod.addInstructions(
0,
"""
# Return false.
const/4 v0, 0x0
return v0
"""
)
} ?: throw PatchException("ShowAdsFingerprint not found")
) {
compatibleWith {
"com.some.app"("1.0.0")
}
dependsOn {
disableAdsResourcePatch()
}
val showAdsFingerprintResult by methodFingerprint {
// ...
}
execute {
showAdsFingerprintResult.mutableMethod.addInstructions(
0,
"""
# Return false.
const/4 v0, 0x0
return v0
"""
)
}
}
```
## 🔎 Breakdown
The example patch consists of the following parts:
### 📝 Patch annotation
```kt
@Patch(
name = "Disable ads",
description = "Disable ads in the app.",
dependencies = [DisableAdsResourcePatch::class],
compatiblePackages = [CompatiblePackage("com.some.app", ["1.3.0"])]
)
```
The `@Patch` annotation is used to provide metadata about the patch.
Notable annotation parameters are:
- `name`: The name of the patch. This is used as an identifier for the patch.
If this parameter is not set, `PatchBundleLoader` will not load the patch.
Other patches can still use this patch as a dependency
- `description`: A description of the patch. Can be unset if the name is descriptive enough
- `dependencies`: A set of patches which the patch depends on. The patches in this set will be executed before this patch. If a dependency patch raises an exception, this patch will not be executed; subsquently, other patches that depend on this patch will not be executed.
- `compatiblePackages`: A set of `CompatiblePackage` objects. Each `CompatiblePackage` object contains the package name and a set of compatible version names. This parameter can specify the packages and versions the patch is compatible with. Patches can still execute on incompatible packages, but it is recommended to use this parameter to list known compatible packages
- If unset, it is implied that the patch is compatible with all packages
- If the set of versions is unset, it is implied that the patch is compatible with all versions of the package
- If the set of versions is empty, it is implied that the patch is not compatible with any version of the package. This can be useful, for example, to prevent a patch from executing on specific packages that are known to be incompatible
> [!NOTE]
>
> - Patches do not require a name, but `PatchLoader` will only load named patches.
> - Patches can depend on others. Dependencies are executed first.
> The dependent patch will not be executed if a dependency raises an exception.
> - A patch can declare compatibility with specific packages and versions, but patches can still be executed on any package or version. It is recommended to declare explicit compatibility to list known compatible packages.
> - If `compatibleWith` is not called, the patch is compatible with any 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. This is useful for declaring explicit incompatibility with a specific package.
> - This patch uses a fingerprint to find the method and replaces the method's instructions with new instructions.
> The fingerprint is resolved on the classes present in `BytecodePatchContext`.
> Fingerprints will be explained in more detail on the next page.
> - A patch can raise a `PatchException` at any time to indicate that the patch failed to execute. Any other `Exception` or `Throwable` raised will be wrapped in a `PatchException`.
> [!WARNING]
> Circular dependencies are not allowed. If a patch depends on another patch, the other patch cannot depend on the first patch.
>
> - Circular dependencies are not allowed. If a patch depends on another patch, the other patch cannot depend on the first patch.
> - Dependencies inherit compatibility from dependant patches.
> [!NOTE]
> The `@Patch` annotation is optional. If the patch does not require any metadata, it can be omitted.
> If the patch is only used as a dependency, the metadata, such as the `compatiblePackages` parameter, has no effect, as every dependency patch inherits the compatible packages of the patches that depend on it.
> [!TIP]
> An abstract patch class can be annotated with `@Patch`.
> Patches extending off the abstract patch class will inherit the metadata of the abstract patch class.
> To see real-world examples of patches, check out the repository for [ReVanced Patches](https://github.com/revanced/revanced-patches).
> [!TIP]
> Instead of the `@Patch` annotation, the superclass's constructor can be used. This is useful in the example scenario where you want to create an abstract patch class.
>
> Example:
>
> ```kt
> abstract class AbstractDisableAdsPatch(
> fingerprints: Set<Fingerprint>
> ) : BytecodePatch(
> name = "Disable ads",
> description = "Disable ads in the app.",
> fingerprints
> ) {
> // ...
> }
> ```
>
> Remember that this constructor has precedence over the `@Patch` annotation.
## 🧩 Patch API
### 🏗️ Patch class
### ♻️ Finalization
Patches can have a finalization block called after all patches have been executed, in reverse order of patch execution.
```kt
object DisableAdsPatch : BytecodePatch( /* Parameters */ ) {
// ...
val patch = bytecodePatch(name = "Patch") {
dependsOn {
bytecodePatch(name = "Dependency") {
execute {
print("1")
}
finalize {
print("4")
}
}
}
execute {
print("2")
}
finalize {
print("3")
}
}
```
Each patch class extends off a base class that implements the `Patch` interface.
The interface requires the `execute` method to be implemented.
Depending on which base class is extended, the patch can modify different parts of the APK as described in [🧩 Introduction to ReVanced Patches](2_introduction_to_patches.md).
Because `Patch` depends on `Dependency`, first `Dependency` is executed, then `Patch`. The finalization blocks are called in reverse order of patch execution, which means, first, the finalization block of `Patch`, then the finalization block of `Dependency` is called. The output of the above patch would be `1234`. The same order is followed for multiple patches depending on the patch.
> [!TIP]
> A patch is usually a singleton object, meaning only one patch instance exists in the JVM.
> Because dependencies are executed before the patch itself, a patch can rely on the state of the dependency patch.
> This is useful in the example scenario, where the `DisableAdsPatch` depends on the `DisableAdsResourcePatch`.
> The `DisableAdsResourcePatch` can, for example, be used to read the decoded resources of the app and provide the `DisableAdsPatch` with the necessary information to disable ads because the `DisableAdsResourcePatch` is executed before the `DisableAdsPatch` and is a singleton object.
### ⚙️ Patch options
### 🏁 The `execute` function
Patches can have options to get and set before a patch is executed. Multiple inbuilt types can be used as options.
The `execute` function is declared in the `Patch` interface and needs to be implemented.
The `execute` function receives an instance of a context object that provides access to the APK. The patch can use this context to modify the APK as described in [🧩 Introduction to ReVanced Patches](2_introduction_to_patches.md).
In the current example, the patch adds instructions at the beginning of a method implementation in the Dalvik VM bytecode. The added instructions return `false` to disable ads in the current example:
To define an option, use available `option` functions:
```kt
val result = LoadAdsFingerprint.result
?: throw PatchException("LoadAdsFingerprint not found")
val patch = bytecodePatch(name = "Patch") {
// Add an option with a custom type and delegate it's value to a variable.
val string by option<String>(key = "string")
result.mutableMethod.addInstructions(
0,
"""
# Return false.
const/4 v0, 0x0
return v0
"""
)
// Add an inbuilt option and delegate it's value to a variable.
val value by stringOption(key = "option")
execute {
println(string)
println(value)
}
}
```
> [!NOTE]
> This patch uses a fingerprint to find the method and replaces the method's instructions with new instructions.
> The fingerprint is resolved on the classes present in `BytecodeContext`.
> Fingerprints will be explained in more detail on the next page.
Options of a patch can be set after loading the patches with `PatchLoader` by obtaining the instance for the patch:
> [!TIP]
> The patch can also raise any `Exception` or `Throwable` at any time to indicate that the patch failed to execute. A `PatchException` is recommended to be raised if the patch fails to execute.
> If any patch depends on this patch, the dependent patch will not be executed, whereas other patches that do not depend on this patch can still be executed.
> ReVanced Patcher will handle any exception raised by a patch.
```kt
loadPatchesJar(patchesJarFile).apply {
// Type is checked at runtime.
first { it.name == "Patch" }.options["option"] = "Value"
}
```
> [!TIP]
> To see real-world examples of patches, check out the [ReVanced Patches](https://github.com/revanced/revanced-patches) repository.
The type of an option can be obtained from the `type` property of the option:
```kt
option.type // The KType of the option.
```
## ⏭️ What's next

View File

@ -73,53 +73,42 @@ There are multiple types of patches. Each type can modify a different part of th
Each patch can declare a set of dependencies on other patches. ReVanced Patcher will first execute dependencies before executing the patch itself. This way, multiple patches can work together for abstract purposes in a modular way.
A patch class can be annotated with `@Patch` to provide metadata about and dependencies of the patch.
Alternatively, a constructor of the superclass can be used. This is useful in the example scenario where you want to create an abstract patch class.
The `execute` function is the entry point for a patch. It is called by ReVanced Patcher when the patch is executed. The `execute` function receives an instance of a context object that provides access to the APK. The patch can use this context to modify the APK.
The entry point of a patch is the `execute` function. This function is called by ReVanced Patcher when the patch is executed. The `execute` function receives an instance of the context object that provides access to the APK. The patch can use this context to modify the APK.
Each type of context provides different APIs to modify the APK. For example, the `BytecodePatchContext` provides APIs to modify the Dalvik VM bytecode, while the `ResourcePatchContext` provides APIs to modify resources.
Each type of context provides different APIs to modify the APK. For example, the `BytecodeContext` provides APIs to modify the Dalvik VM bytecode, while the `ResourceContext` provides APIs to modify resources.
The difference between `ResourcePatch` and `RawResourcePatch` is that ReVanced Patcher will decode the resources if it is supplied a `ResourcePatch` for execution or if any patch depends on a `ResourcePatch` and will not decode the resources before executing `RawResourcePatch`. Both, `ResourcePatch` and `RawResourcePatch` can modify arbitrary files in the APK, whereas only `ResourcePatch` can modify decoded resources. The choice of which type to use depends on the use case. Decoding and building resources is a time- and resource-consuming, so if the patch does not need to modify decoded resources, it is better to use `RawResourcePatch` or `BytecodePatch`.
The difference between `ResourcePatch` and `RawResourcePatch` is that ReVanced Patcher will decode the resources if it is supplied a `ResourcePatch` for execution or if any kind of patch depends on a `ResourcePatch` and will not decode the resources before executing `RawResourcePatch`. Both, `ResourcePatch` and `RawResourcePatch` can modify arbitrary files in the APK, whereas only `ResourcePatch` can modify decoded resources. The choice of which type to use depends on the use case. Decoding and building resources is a time- and resource-consuming process, so if the patch does not need to modify decoded resources, it is better to use `RawResourcePatch` or `BytecodePatch`.
Example of a `BytecodePatch`:
Example of patches:
```kt
@Surpress("unused")
object MyPatch : BytecodePatch() {
override fun execute(context: BytecodeContext) {
// Your patch code here
}
val bytecodePatch = bytecodePatch {
execute {
// TODO
}
}
```
Example of a `ResourcePatch`:
```kt
@Surpress("unused")
object MyPatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
// Your patch code here
}
val rawResourcePatch = rawResourcePatch {
execute {
// TODO
}
}
```
Example of a `RawResourcePatch`:
```kt
@Surpress("unused")
object MyPatch : RawResourcePatch() {
override fun execute(context: ResourceContext) {
// Your patch code here
}
val resourcePatch = rawResourcePatch {
execute {
// TODO
}
}
```
> [!TIP]
> To see real-world examples of patches, check out the [ReVanced Patches](https://github.com/revanced/revanced-patches) repository.
> To see real-world examples of patches, check out the repository for [ReVanced Patches](https://github.com/revanced/revanced-patches).
## ⏭️ Whats next
The next page will guide you through setting up a development environment for creating patches.
The next page will guide you through creating a development environment for creating patches.
Continue: [👶 Setting up a development environment](2_1_setup.md)

View File

@ -64,7 +64,7 @@ Over time, a specific project structure and conventions have been established.
## 📁 File structure
Patches are organized in a specific file structure. The file structure is as follows:
Patches are organized in a specific way. The file structure looks as follows:
```text
📦your.patches.app.category
@ -74,21 +74,24 @@ Patches are organized in a specific file structure. The file structure is as fol
└ 🧩SomePatch.kt
```
> [!NOTE]
> Moving fingerprints to a separate package isn't strictly necessary, but it helps the organization when a patch uses multiple fingerprints.
## 📙 Conventions
- 🔥 Name a patch after what it does. For example, if a patch removes ads, name it `RemoveAdsPatch`.
If a patch changes the color of a button, name it `ChangeButtonColorPatch`
- 🔥 Name a patch after what it does. For example, if a patch removes ads, name it `Remove ads`.
If a patch changes the color of a button, name it `Change button color`
- 🔥 Write the patch description in the third person, present tense, and end it with a period.
If a patch removes ads, the description can be omitted because of redundancy, but if a patch changes the color of a button, the description can be _Changes the color of the resume button to red._
- 🔥 Write patches with modularity and reusability in mind. Patches can depend on each other, so it is important to write patches in a way that can be used in different contexts.
- 🔥🔥 Keep patches as minimal as possible. This reduces the risk of failing patches.
Instead of involving many abstract changes in one patch or writing entire methods or classes in a patch,
you can write code in integrations. Integrations are compiled classes that are merged into the app before patches are executed as described in [💉 Introduction to ReVanced Patcher](1_patcher_intro).
you can write code in integrations. Integrations are compiled classes merged into the app before patches are executed as described in [💉 Introduction to ReVanced Patcher](1_patcher_intro).
Patches can then reference methods and classes from integrations.
A real-world example of integrations can be found in the [ReVanced Integrations](https://github.com/ReVanced/revanced-integrations) repository
- 🔥🔥🔥 Do not overload a fingerprint with information about a method that's likely to change.
In the example of an obfuscated method, it's better to fingerprint the method by its return type and parameters rather than its name because the name is likely to change. An intelligent selection of an opcode pattern or strings in a method can result in a strong fingerprint dynamic to app updates.
- 🔥🔥🔥 Document your patches. Patches are abstract by nature, so it is important to document parts of the code that are not self-explanatory. For example, explain why and how a certain method is patched or large blocks of instructions that are modified or added to a method
- 🔥🔥🔥 Document your patches. Patches are abstract, so it is important to document parts of the code that are not self-explanatory. For example, explain why and how a certain method is patched or large blocks of instructions that are modified or added to a method
## ⏭️ What's next

View File

@ -4,13 +4,12 @@ A handful of APIs are available to make patch development easier and more effici
## 📙 Overview
1. 👹 Create new mutable classes with `context.proxy(ClassDef)`
2. 🔍 Find and proxy existing classes with `BytecodeContext.findClass(Predicate)`
3. 🏃‍ Easily access referenced methods recursively by index with `BytecodeContext.toMethodWalker(Method)`
1. 👹 Mutate classes with `classDex.proxy()`
2. 🔍 Find and proxy existing classes with `classBy(Predicate)`
3. 🏃‍ Easily access referenced methods recursively by index with `method.navigate()`
4. 🔨 Make use of extension functions from `BytecodeUtils` and `ResourceUtils` with certain applications (Available in ReVanced Patches)
5. 💾 Read and write (decoded) resources with `ResourceContext.get(Path, Boolean) `
6. 📃 Read and write DOM files using `ResourceContext.document`
7. 🔧 Equip patches with configurable options using `Patch.options`
5. 💾 Read and write (decoded) resources with `ResourcePatchContext.get(Path, Boolean)`
6. 📃 Read and write DOM files using `ResourcePatchContext.document`
### 🧰 APIs

View File

@ -22,4 +22,4 @@ xpp3 = { module = "xpp3:xpp3", version.ref = "xpp3" }
[plugins]
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

View File

@ -12,14 +12,12 @@ import java.util.function.Supplier
import java.util.logging.Logger
@FunctionalInterface
interface IntegrationsConsumer {
fun acceptIntegrations(integrations: Set<File>)
interface PatchesConsumer {
fun accept(patches: PatchSet, integrations: Set<File> = emptySet())
}
@FunctionalInterface
interface PatchesConsumer {
fun acceptPatches(patches: PatchSet)
}
interface PatcherResultSupplier : Supplier<PatcherResult>, Closeable
@FunctionalInterface
interface PatchExecutorFunction : Function<Boolean, Flow<PatchResult>>
@ -31,7 +29,7 @@ interface PatchExecutorFunction : Function<Boolean, Flow<PatchResult>>
*/
class Patcher(
private val config: PatcherConfig,
) : PatchExecutorFunction, PatchesConsumer, IntegrationsConsumer, Supplier<PatcherResult>, Closeable {
) : PatchExecutorFunction, PatchesConsumer, PatcherResultSupplier {
private val logger = Logger.getLogger(Patcher::class.java.name)
/**
@ -40,16 +38,19 @@ class Patcher(
val context = PatcherContext(config)
init {
context.resourceContext.decodeResources(ResourcePatchContext.ResourceMode.NONE)
context.resourcePatchContext.decodeResources(ResourcePatchContext.ResourceMode.NONE)
}
/**
* Add [Patch]es to [Patcher].
* Add [Patch]es and integrations to the [Patcher].
*
* @param patches The [Patch]es to add.
* @param integrations The integrations to add. Must be a DEX file or container of DEX files.
*/
@Suppress("NAME_SHADOWING")
override fun acceptPatches(patches: PatchSet) {
override fun accept(patches: PatchSet, integrations: Set<File>) {
// region Add patches
// Add all patches to the executablePatches set.
context.executablePatches.addAll(patches)
@ -84,19 +85,18 @@ class Patcher(
// Check, if integrations need to be merged.
for (patch in patches)
if (patch.anyRecursively { it.requiresIntegrations }) {
context.bytecodeContext.integrations.merge = true
context.bytecodePatchContext.integrations.merge = true
break
}
}
}
/**
* Add integrations to the [Patcher].
*
* @param integrations The integrations to add. Must be a DEX file or container of DEX files.
*/
override fun acceptIntegrations(integrations: Set<File>) {
context.bytecodeContext.integrations.addAll(integrations)
// endregion
// region Add integrations
context.bytecodePatchContext.integrations.addAll(integrations)
// endregion
}
/**
@ -140,13 +140,13 @@ class Patcher(
}.also { executedPatches[this] = it }
}
if (context.bytecodeContext.integrations.merge) context.bytecodeContext.integrations.flush()
if (context.bytecodePatchContext.integrations.merge) context.bytecodePatchContext.integrations.flush()
LookupMap.initializeLookupMaps(context.bytecodeContext)
LookupMap.initializeLookupMaps(context.bytecodePatchContext)
// Prevent from decoding the app manifest twice if it is not needed.
if (config.resourceMode != ResourcePatchContext.ResourceMode.NONE) {
context.resourceContext.decodeResources(config.resourceMode)
context.resourcePatchContext.decodeResources(config.resourceMode)
}
logger.info("Executing patches")
@ -212,8 +212,8 @@ class Patcher(
@OptIn(InternalApi::class)
override fun get() =
PatcherResult(
context.bytecodeContext.get(),
context.resourceContext.get(),
context.bytecodePatchContext.get(),
context.resourcePatchContext.get(),
)
}

View File

@ -29,12 +29,12 @@ class PatcherContext internal constructor(config: PatcherConfig) {
internal val allPatches = mutableSetOf<Patch<*>>()
/**
* A context for the patcher containing the current state of the resources.
* A context for patches containing the current state of the resources.
*/
internal val resourceContext = ResourcePatchContext(packageMetadata, config)
internal val resourcePatchContext = ResourcePatchContext(packageMetadata, config)
/**
* A context for the patcher containing the current state of the bytecode.
* A context for patches containing the current state of the bytecode.
*/
internal val bytecodeContext = BytecodePatchContext(config)
internal val bytecodePatchContext = BytecodePatchContext(config)
}

View File

@ -1,7 +1,6 @@
package app.revanced.patcher.extensions
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import com.android.tools.smali.dexlib2.AccessFlags
/**
* Create a label for the instruction at given index.
@ -10,24 +9,3 @@ import com.android.tools.smali.dexlib2.AccessFlags
* @return The label.
*/
fun MutableMethod.newLabel(index: Int) = implementation!!.newLabelForIndex(index)
/**
* Perform a bitwise OR operation between an [AccessFlags] and an [Int].
*
* @param other The [Int] to perform the operation with.
*/
infix fun Int.or(other: AccessFlags) = this or other.value
/**
* Perform a bitwise OR operation between two [AccessFlags].
*
* @param other The other [AccessFlags] to perform the operation with.
*/
infix fun AccessFlags.or(other: AccessFlags) = value or other.value
/**
* Perform a bitwise OR operation between an [Int] and an [AccessFlags].
*
* @param other The [AccessFlags] to perform the operation with.
*/
infix fun AccessFlags.or(other: Int) = value or other

View File

@ -178,8 +178,8 @@ object InstructionExtensions {
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
/**
* Creates a new label for the instruction
* and replaces it with the label of the [compiledInstruction] at [compiledInstructionIndex].
* Create a new label for the instruction
* and replace it with the label of the [compiledInstruction] at [compiledInstructionIndex].
*/
fun Instruction.makeNewLabel() {
fun replaceOffset(

View File

@ -1,3 +1,5 @@
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package app.revanced.patcher.fingerprint
import app.revanced.patcher.fingerprint.LookupMap.Maps.appendParameters
@ -15,16 +17,6 @@ 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.StringReference
import com.android.tools.smali.dexlib2.util.MethodUtil
import kotlin.reflect.full.findAnnotations
/**
* Annotations to scan a pattern [MethodFingerprint] with fuzzy algorithm.
* @param threshold if [threshold] or more of the opcodes do not match, skip.
*/
@Target(AnnotationTarget.CLASS)
annotation class FuzzyPatternScanMethod(
val threshold: Int = 1,
)
/**
* A fingerprint to resolve methods.
@ -35,15 +27,17 @@ annotation class FuzzyPatternScanMethod(
* @param opcodes An opcode pattern of the instructions. Wildcard or unknown opcodes can be specified by `null`.
* @param strings A list of the strings compared each using [String.contains].
* @param custom A custom condition for this fingerprint.
* @param fuzzyPatternScanThreshold The threshold for fuzzy pattern scanning.
*/
@Suppress("MemberVisibilityCanBePrivate")
class MethodFingerprint(
class MethodFingerprint internal constructor(
internal val returnType: String? = null,
internal val accessFlags: Int? = null,
internal val parameters: List<String>? = null,
internal val opcodes: List<Opcode?>? = null,
internal val strings: List<String>? = null,
internal val custom: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null,
private val fuzzyPatternScanThreshold: Int = 0,
) {
/**
* The result of the [MethodFingerprint].
@ -51,13 +45,6 @@ class MethodFingerprint(
var result: MethodFingerprintResult? = null
private set
/**
* The [FuzzyPatternScanMethod] annotation of the [MethodFingerprint].
*
* If the annotation is not present, this property is null.
*/
val fuzzyPatternScanMethod = this::class.findAnnotations(FuzzyPatternScanMethod::class).singleOrNull()
/**
* Resolve a [MethodFingerprint] using the lookup map built by [initializeLookupMaps].
*
@ -244,7 +231,7 @@ class MethodFingerprint(
fingerprint: MethodFingerprint,
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
val instructions = this.implementation!!.instructions
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanThreshold
val pattern = fingerprint.opcodes!!
val instructionLength = instructions.count()
@ -341,7 +328,6 @@ fun Iterable<MethodFingerprint>.resolve(
* @param context The [BytecodePatchContext] this [MethodFingerprintResult] is attached to, to create proxies.
*/
@Suppress("MemberVisibilityCanBePrivate")
class MethodFingerprintResult(
val method: Method,
val classDef: ClassDef,
@ -354,8 +340,11 @@ class MethodFingerprintResult(
* Please note, this method allocates a [ClassProxy].
* Use [classDef] where possible.
*/
@Suppress("MemberVisibilityCanBePrivate")
val mutableClass by lazy { context.proxy(classDef).mutableClass }
val mutableClass by lazy {
with(context) {
classDef.proxy().mutableClass
}
}
/**
* Returns a mutable clone of [method]
@ -412,10 +401,13 @@ class MethodFingerprintResult(
* @property opcodes An opcode pattern of the instructions. Wildcard or unknown opcodes can be specified by `null`.
* @property strings A list of the strings compared each using [String.contains].
* @property customBlock A custom condition for this fingerprint.
* @property fuzzyPatternScanThreshold The threshold for fuzzy pattern scanning.
*
* @constructor Creates a new [MethodFingerprintBuilder].
* @constructor Create a new [MethodFingerprintBuilder].
*/
class MethodFingerprintBuilder internal constructor() {
class MethodFingerprintBuilder internal constructor(
private val fuzzyPatternScanThreshold: Int = 0,
) {
private var returnType: String? = null
private var accessFlags: Int? = null
private var parameters: List<String>? = null
@ -446,8 +438,8 @@ class MethodFingerprintBuilder internal constructor() {
*
* @param accessFlags The exact access flags using values of [AccessFlags].
*/
fun accessFlags(accessFlags: AccessFlags) {
this.accessFlags = accessFlags.value
fun accessFlags(vararg accessFlags: AccessFlags) {
this.accessFlags = accessFlags.fold(0) { acc, it -> acc or it.value }
}
/**
@ -520,19 +512,28 @@ class MethodFingerprintBuilder internal constructor() {
/**
* Create a [MethodFingerprint].
*
* @param fuzzyPatternScanThreshold The threshold for fuzzy pattern scanning. Default is 0.
* @param block The block to build the [MethodFingerprint].
*
* @return The created [MethodFingerprint].
*/
fun methodFingerprint(block: MethodFingerprintBuilder.() -> Unit) =
MethodFingerprintBuilder().apply(block).build()
fun methodFingerprint(
fuzzyPatternScanThreshold: Int = 0,
block: MethodFingerprintBuilder.() -> Unit,
) = MethodFingerprintBuilder(fuzzyPatternScanThreshold).apply(block).build()
/**
* Create a [MethodFingerprint] and add it to the set of fingerprints.
*
* @param fuzzyPatternScanThreshold The threshold for fuzzy pattern scanning. Default is 0.
* @param block The block to build the [MethodFingerprint].
*
* @return The created [MethodFingerprint].
*/
fun BytecodePatchBuilder.methodFingerprint(block: MethodFingerprintBuilder.() -> Unit) =
app.revanced.patcher.fingerprint.methodFingerprint(block)() // Invoke to add to its set of fingerprints.
fun BytecodePatchBuilder.methodFingerprint(
fuzzyPatternScanThreshold: Int = 0,
block: MethodFingerprintBuilder.() -> Unit,
) = app.revanced.patcher.fingerprint.methodFingerprint(
fuzzyPatternScanThreshold,
block,
)() // Invoke to add to its set of fingerprints.

View File

@ -5,8 +5,8 @@ import app.revanced.patcher.PatcherConfig
import app.revanced.patcher.PatcherContext
import app.revanced.patcher.PatcherResult
import app.revanced.patcher.util.ClassMerger.merge
import app.revanced.patcher.util.MethodNavigator
import app.revanced.patcher.util.ProxyClassList
import app.revanced.patcher.util.method.MethodWalker
import app.revanced.patcher.util.proxy.ClassProxy
import com.android.tools.smali.dexlib2.Opcodes
import com.android.tools.smali.dexlib2.iface.ClassDef
@ -46,7 +46,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
BasicDexFileNamer(),
null,
null,
).also { opcodes = it.opcodes }.classes.toMutableSet(),
).also { opcodes = it.opcodes }.classes.toMutableList(),
)
}
@ -56,44 +56,36 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
internal val integrations = Integrations()
/**
* Find a class by a given class name.
* Find a class by its type using a contains check.
*
* @param className The name of the class.
* @return A proxy for the first class that matches the class name.
* @param type The type of the class.
* @return A proxy for the first class that matches the type.
*/
fun findClass(className: String) = findClass { it.type.contains(className) }
fun classByType(type: String) = classBy { type in it.type }
/**
* Find a class by a given predicate.
* Find a class with a predicate.
*
* @param predicate A predicate to match the class.
* @return A proxy for the first class that matches the predicate.
*/
fun findClass(predicate: (ClassDef) -> Boolean) =
// if we already proxied the class matching the predicate...
classes.proxies.firstOrNull { predicate(it.immutableClass) }
?: // else resolve the class to a proxy and return it, if the predicate is matching a class
classes.find(predicate)?.let { proxy(it) }
fun classBy(predicate: (ClassDef) -> Boolean) =
classes.proxyPool.find { predicate(it.immutableClass) } ?: classes.find(predicate)?.proxy()
/**
* Proxy a class.
* This will allow the class to be modified.
* Proxy the class to allow mutation.
*
* @param classDef The class to proxy.
* @return A proxy for the class.
*/
fun proxy(classDef: ClassDef) =
this.classes.proxies.find { it.immutableClass.type == classDef.type } ?: let {
ClassProxy(classDef).also { this.classes.add(it) }
}
fun ClassDef.proxy() = this@BytecodePatchContext.classes.proxyPool.find { it.immutableClass.type == type }
?: ClassProxy(this).also { this@BytecodePatchContext.classes.proxyPool.add(it) }
/**
* Create a [MethodWalker] instance for the current [BytecodePatchContext].
* Navigate a method.
*
* @param startMethod The method to start at.
* @return A [MethodWalker] instance.
* @return A [MethodNavigator] for the method.
*/
fun toMethodWalker(startMethod: Method) = MethodWalker(this, startMethod)
fun Method.navigate() = MethodNavigator(this@BytecodePatchContext, this)
/**
* Compile bytecode from the [BytecodePatchContext].
@ -116,7 +108,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
BasicDexFileNamer(),
object : DexFile {
override fun getClasses() =
this@BytecodePatchContext.classes.also(ProxyClassList::replaceClasses)
this@BytecodePatchContext.classes.also(ProxyClassList::replaceClasses).toSet()
override fun getOpcodes() = this@BytecodePatchContext.opcodes
},

View File

@ -1,44 +1,46 @@
package app.revanced.patcher.patch
import kotlin.reflect.KProperty
import kotlin.reflect.KType
import kotlin.reflect.typeOf
/**
* A patch option.
* An option.
*
* @param T The value type of the option.
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param valueType The type of the option value (to handle type erasure).
* @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 [PatchOption].
* @constructor Create a new [Option].
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
open class PatchOption<T>(
class Option<T> @PublishedApi internal constructor(
val key: String,
val default: T?,
val values: Map<String, T?>?,
val title: String?,
val description: String?,
val required: Boolean,
val valueType: String,
val validator: PatchOption<T>.(T?) -> Boolean,
val default: T? = null,
val values: Map<String, T?>? = null,
val title: String? = null,
val description: String? = null,
val required: Boolean = false,
val type: KType,
val validator: Option<T>.(T?) -> Boolean = { true },
) {
/**
* The value of the [PatchOption].
* The value of the [Option].
*/
var value: T?
/**
* Set the value of the [PatchOption].
* Set the value of the [Option].
*
* @param value The value to set.
*
* @throws PatchOptionException.ValueRequiredException If the value is required but null.
* @throws PatchOptionException.ValueValidationException If the value is invalid.
* @throws OptionException.ValueRequiredException If the value is required but null.
* @throws OptionException.ValueValidationException If the value is invalid.
*/
set(value) {
assertRequiredButNotNull(value)
@ -48,12 +50,12 @@ open class PatchOption<T>(
}
/**
* Get the value of the [PatchOption].
* Get the value of the [Option].
*
* @return The value.
*
* @throws PatchOptionException.ValueRequiredException If the value is required but null.
* @throws PatchOptionException.ValueValidationException If the value is invalid.
* @throws OptionException.ValueRequiredException If the value is required but null.
* @throws OptionException.ValueValidationException If the value is invalid.
*/
get() {
assertRequiredButNotNull(uncheckedValue)
@ -66,19 +68,19 @@ open class PatchOption<T>(
private var uncheckedValue = default
/**
* Reset the [PatchOption] to its default value.
* Reset the [Option] to its default value.
* Override this method if you need to mutate the value instead of replacing it.
*/
open fun reset() {
fun reset() {
uncheckedValue = default
}
private fun assertRequiredButNotNull(value: T?) {
if (required && value == null) throw PatchOptionException.ValueRequiredException(this)
if (required && value == null) throw OptionException.ValueRequiredException(this)
}
private fun assertValid(value: T?) {
if (!validator(value)) throw PatchOptionException.ValueValidationException(value, this)
if (!validator(value)) throw OptionException.ValueValidationException(value, this)
}
override fun toString() = value.toString()
@ -98,33 +100,33 @@ open class PatchOption<T>(
}
/**
* A collection of [PatchOption]s where patch options can be set and retrieved by key.
* A collection of [Option]s where options can be set and retrieved by key.
*
* @param options The patch options.
* @param options The options.
*
* @constructor Create a new [PatchOptions].
* @constructor Create a new [Options].
*/
class PatchOptions(
private val options: Map<String, PatchOption<*>>,
) : Map<String, PatchOption<*>> by options {
constructor(options: Set<PatchOption<*>>) : this(options.associateBy { it.key })
class Options(
private val options: Map<String, Option<*>>,
) : Map<String, Option<*>> by options {
constructor(options: Set<Option<*>>) : this(options.associateBy { it.key })
/**
* Set a patch option's value.
* Set an option's value.
*
* @param key The key.
* @param value The value.
*
* @throws PatchOptionException.PatchOptionNotFoundException If the patch option does not exist.
* @throws OptionException.OptionNotFoundException If the option does not exist.
*/
operator fun <T : Any> set(key: String, value: T?) {
val option = this[key]
try {
@Suppress("UNCHECKED_CAST")
(option as PatchOption<T>).value = value
(option as Option<T>).value = value
} catch (e: ClassCastException) {
throw PatchOptionException.InvalidValueTypeException(
throw OptionException.InvalidValueTypeException(
value?.let { it::class.java.name } ?: "null",
option.value?.let { it::class.java.name } ?: "null",
)
@ -132,427 +134,415 @@ class PatchOptions(
}
/**
* Get a patch option.
* Get an option.
*
* @param key The key.
*
* @return The patch option.
* @return The option.
*/
override fun get(key: String) = options[key] ?: throw PatchOptionException.PatchOptionNotFoundException(key)
override fun get(key: String) = options[key] ?: throw OptionException.OptionNotFoundException(key)
}
/**
* Create a new [PatchOption] with a string value and add it to the current [PatchBuilder].
* Create a new [Option] with a string value and add it to the current [PatchBuilder].
*
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
* @return The created [Option].
*
* @see PatchOption
* @see Option
*/
fun <P : PatchBuilder<*>> P.stringPatchOption(
fun PatchBuilder<*>.stringOption(
key: String,
default: String? = null,
values: Map<String, String?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<String>.(String?) -> Boolean = { true },
) = addNewPatchOption(
validator: Option<String>.(String?) -> Boolean = { true },
) = option(
key,
default,
values,
title,
description,
required,
"String",
validator,
)
/**
* Create a new [PatchOption] with an integer value and add it to the current [PatchBuilder].
* Create a new [Option] with an integer value and add it to the current [PatchBuilder].
*
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
* @return The created [Option].
*
* @see PatchOption
* @see Option
*/
fun <P : PatchBuilder<*>> P.intPatchOption(
fun PatchBuilder<*>.intOption(
key: String,
default: Int? = null,
values: Map<String, Int?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Int?>.(Int?) -> Boolean = { true },
) = addNewPatchOption(
validator: Option<Int?>.(Int?) -> Boolean = { true },
) = option(
key,
default,
values,
title,
description,
required,
"Int",
validator,
)
/**
* Create a new [PatchOption] with a boolean value and add it to the current [PatchBuilder].
* Create a new [Option] with a boolean value and add it to the current [PatchBuilder].
*
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
* @return The created [Option].
*
* @see PatchOption
* @see Option
*/
fun <P : PatchBuilder<*>> P.booleanPatchOption(
fun PatchBuilder<*>.booleanOption(
key: String,
default: Boolean? = null,
values: Map<String, Boolean?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Boolean?>.(Boolean?) -> Boolean = { true },
) = addNewPatchOption(
validator: Option<Boolean?>.(Boolean?) -> Boolean = { true },
) = option(
key,
default,
values,
title,
description,
required,
"Boolean",
validator,
)
/**
* Create a new [PatchOption] with a float value and add it to the current [PatchBuilder].
* Create a new [Option] with a float value and add it to the current [PatchBuilder].
*
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
* @return The created [Option].
*
* @see PatchOption
* @see Option
*/
fun <P : PatchBuilder<*>> P.floatPatchOption(
fun PatchBuilder<*>.floatOption(
key: String,
default: Float? = null,
values: Map<String, Float?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Float?>.(Float?) -> Boolean = { true },
) = addNewPatchOption(
validator: Option<Float?>.(Float?) -> Boolean = { true },
) = option(
key,
default,
values,
title,
description,
required,
"Float",
validator,
)
/**
* Create a new [PatchOption] with a long value and add it to the current [PatchBuilder].
* Create a new [Option] with a long value and add it to the current [PatchBuilder].
*
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
* @return The created [Option].
*
* @see PatchOption
* @see Option
*/
fun <P : PatchBuilder<*>> P.longPatchOption(
fun PatchBuilder<*>.longOption(
key: String,
default: Long? = null,
values: Map<String, Long?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Long?>.(Long?) -> Boolean = { true },
) = addNewPatchOption(
validator: Option<Long?>.(Long?) -> Boolean = { true },
) = option(
key,
default,
values,
title,
description,
required,
"Long",
validator,
)
/**
* Create a new [PatchOption] with a string array value and add it to the current [PatchBuilder].
* Create a new [Option] with a string list value and add it to the current [PatchBuilder].
*
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
* @return The created [Option].
*
* @see PatchOption
* @see Option
*/
fun <P : PatchBuilder<*>> P.stringArrayPatchOption(
fun PatchBuilder<*>.stringsOption(
key: String,
default: Array<String>? = null,
values: Map<String, Array<String>?>? = null,
default: List<String>? = null,
values: Map<String, List<String>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<String>?>.(Array<String>?) -> Boolean = { true },
) = addNewPatchOption(
validator: Option<List<String>>.(List<String>?) -> Boolean = { true },
) = option(
key,
default,
values,
title,
description,
required,
"StringArray",
validator,
)
/**
* Create a new [PatchOption] with an integer array value and add it to the current [PatchBuilder].
* Create a new [Option] with an integer list value and add it to the current [PatchBuilder].
*
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
* @return The created [Option].
*
* @see PatchOption
* @see Option
*/
fun <P : PatchBuilder<*>> P.intArrayPatchOption(
fun PatchBuilder<*>.intsOption(
key: String,
default: Array<Int>? = null,
values: Map<String, Array<Int>?>? = null,
default: List<Int>? = null,
values: Map<String, List<Int>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Int>?>.(Array<Int>?) -> Boolean = { true },
) = addNewPatchOption(
validator: Option<List<Int>>.(List<Int>?) -> Boolean = { true },
) = option(
key,
default,
values,
title,
description,
required,
"IntArray",
validator,
)
/**
* Create a new [PatchOption] with a boolean array value and add it to the current [PatchBuilder].
* Create a new [Option] with a boolean list value and add it to the current [PatchBuilder].
*
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
* @return The created [Option].
*
* @see PatchOption
* @see Option
*/
fun <P : PatchBuilder<*>> P.booleanArrayPatchOption(
fun PatchBuilder<*>.booleansOption(
key: String,
default: Array<Boolean>? = null,
values: Map<String, Array<Boolean>?>? = null,
default: List<Boolean>? = null,
values: Map<String, List<Boolean>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Boolean>?>.(Array<Boolean>?) -> Boolean = { true },
) = addNewPatchOption(
validator: Option<List<Boolean>>.(List<Boolean>?) -> Boolean = { true },
) = option(
key,
default,
values,
title,
description,
required,
"BooleanArray",
validator,
)
/**
* Create a new [PatchOption] with a float array value and add it to the current [PatchBuilder].
* Create a new [Option] with a float list value and add it to the current [PatchBuilder].
*
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
* @return The created [Option].
*
* @see PatchOption
* @see Option
*/
fun <P : PatchBuilder<*>> P.floatArrayPatchOption(
fun PatchBuilder<*>.floatsOption(
key: String,
default: Array<Float>? = null,
values: Map<String, Array<Float>?>? = null,
default: List<Float>? = null,
values: Map<String, List<Float>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Float>?>.(Array<Float>?) -> Boolean = { true },
) = addNewPatchOption(
validator: Option<List<Float>>.(List<Float>?) -> Boolean = { true },
) = option(
key,
default,
values,
title,
description,
required,
"FloatArray",
validator,
)
/**
* Create a new [PatchOption] with a long array value and add it to the current [PatchBuilder].
* Create a new [Option] with a long list value and add it to the current [PatchBuilder].
*
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
* @return The created [Option].
*
* @see PatchOption
* @see Option
*/
fun <P : PatchBuilder<*>> P.longArrayPatchOption(
fun PatchBuilder<*>.longsOption(
key: String,
default: Array<Long>? = null,
values: Map<String, Array<Long>?>? = null,
default: List<Long>? = null,
values: Map<String, List<Long>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Long>?>.(Array<Long>?) -> Boolean = { true },
) = addNewPatchOption(
validator: Option<List<Long>>.(List<Long>?) -> Boolean = { true },
) = option(
key,
default,
values,
title,
description,
required,
"LongArray",
validator,
)
/**
* Create a new [PatchOption] and add it to the current [PatchBuilder].
* Create a new [Option] and add it to the current [PatchBuilder].
*
* @param key The key.
* @param default The default value.
* @param values Eligible patch option values mapped to a human-readable name.
* @param values Eligible option values mapped to a human-readable name.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param valueType The type of the option value (to handle type erasure).
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
* @return The created [Option].
*
* @see PatchOption
* @see Option
*/
fun <P : PatchBuilder<*>, T> P.addNewPatchOption(
inline fun <reified T> PatchBuilder<*>.option(
key: String,
default: T? = null,
values: Map<String, T?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
valueType: String,
validator: PatchOption<T>.(T?) -> Boolean = { true },
) = PatchOption(
noinline validator: Option<T>.(T?) -> Boolean = { true },
) = Option(
key,
default,
values,
title,
description,
required,
valueType,
typeOf<T>(),
validator,
).also { option(it) }
).also { it() }
/**
* An exception thrown when using [PatchOption]s.
* An exception thrown when using [Option]s.
*
* @param errorMessage The exception message.
*/
sealed class PatchOptionException(errorMessage: String) : Exception(errorMessage, null) {
sealed class OptionException(errorMessage: String) : Exception(errorMessage, null) {
/**
* An exception thrown when a [PatchOption] is set to an invalid value.
* An exception thrown when a [Option] is set to an invalid value.
*
* @param invalidType The type of the value that was passed.
* @param expectedType The type of the value that was expected.
*/
class InvalidValueTypeException(invalidType: String, expectedType: String) :
PatchOptionException("Type $expectedType was expected but received type $invalidType")
OptionException("Type $expectedType was expected but received type $invalidType")
/**
* An exception thrown when a value did not satisfy the value conditions specified by the [PatchOption].
* An exception thrown when a value did not satisfy the value conditions specified by the [Option].
*
* @param value The value that failed validation.
*/
class ValueValidationException(value: Any?, option: PatchOption<*>) :
PatchOptionException("The option value \"$value\" failed validation for ${option.key}")
class ValueValidationException(value: Any?, option: Option<*>) :
OptionException("The option value \"$value\" failed validation for ${option.key}")
/**
* An exception thrown when a value is required but null was passed.
*
* @param option The [PatchOption] that requires a value.
* @param option The [Option] that requires a value.
*/
class ValueRequiredException(option: PatchOption<*>) :
PatchOptionException("The option ${option.key} requires a value, but null was passed")
class ValueRequiredException(option: Option<*>) :
OptionException("The option ${option.key} requires a value, but null was passed")
/**
* An exception thrown when a [PatchOption] is not found.
* An exception thrown when a [Option] is not found.
*
* @param key The key of the [PatchOption].
* @param key The key of the [Option].
*/
class PatchOptionNotFoundException(key: String) :
PatchOptionException("No option with key $key")
class OptionNotFoundException(key: String) :
OptionException("No option with key $key")
}

View File

@ -21,7 +21,7 @@ typealias PatchSet = Set<Patch<*>>
typealias PackageName = String
typealias VersionName = String
typealias Package = Pair<PackageName, Set<VersionName>>
typealias Package = Pair<PackageName, Set<VersionName>?>
/**
* A patch.
@ -40,7 +40,7 @@ typealias Package = Pair<PackageName, Set<VersionName>>
* @property finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
* in reverse order of execution.
*
* @constructor Creates a new patch.
* @constructor Create a new patch.
*/
sealed class Patch<C : PatchContext<*>>(
val name: String?,
@ -49,11 +49,11 @@ sealed class Patch<C : PatchContext<*>>(
val requiresIntegrations: Boolean,
val compatiblePackages: Set<Package>?,
val dependencies: Set<Patch<*>>,
options: Set<PatchOption<*>>,
options: Set<Option<*>>,
private val executeBlock: ((C) -> Unit),
private val finalizeBlock: ((C) -> Unit),
) {
val options = PatchOptions(options)
val options = Options(options)
/**
* Runs the execution block of the patch.
@ -74,14 +74,14 @@ sealed class Patch<C : PatchContext<*>>(
* Runs the finalizing block of the patch.
* Called by [Patcher].
*
* @param context The [PatcherContext] to get the [PatchContext] from to close the patch with.
* @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.
*
* @param context The [PatchContext] to close the patch with.
* @param context The [PatchContext] to finalize the patch with.
*/
fun finalize(context: C) = finalizeBlock(context)
@ -105,7 +105,7 @@ sealed class Patch<C : PatchContext<*>>(
* @param finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
* in reverse order of execution.
*
* @constructor Creates a new bytecode patch.
* @constructor Create a new bytecode patch.
*/
class BytecodePatch internal constructor(
name: String?,
@ -114,7 +114,7 @@ class BytecodePatch internal constructor(
requiresIntegrations: Boolean,
compatiblePackages: Set<Package>?,
dependencies: Set<Patch<*>>,
options: Set<PatchOption<*>>,
options: Set<Option<*>>,
internal val fingerprints: Set<MethodFingerprint>,
executeBlock: ((BytecodePatchContext) -> Unit),
finalizeBlock: ((BytecodePatchContext) -> Unit),
@ -130,12 +130,12 @@ class BytecodePatch internal constructor(
finalizeBlock,
) {
override fun execute(context: PatcherContext) {
fingerprints.resolveUsingLookupMap(context.bytecodeContext)
fingerprints.resolveUsingLookupMap(context.bytecodePatchContext)
execute(context.bytecodeContext)
execute(context.bytecodePatchContext)
}
override fun finalize(context: PatcherContext) = finalize(context.bytecodeContext)
override fun finalize(context: PatcherContext) = finalize(context.bytecodePatchContext)
}
/**
@ -154,7 +154,7 @@ class BytecodePatch internal constructor(
* @param finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
* in reverse order of execution.
*
* @constructor Creates a new raw resource patch.
* @constructor Create a new raw resource patch.
*/
class RawResourcePatch internal constructor(
name: String?,
@ -163,7 +163,7 @@ class RawResourcePatch internal constructor(
requiresIntegrations: Boolean,
compatiblePackages: Set<Package>?,
dependencies: Set<Patch<*>>,
options: Set<PatchOption<*>>,
options: Set<Option<*>>,
executeBlock: ((ResourcePatchContext) -> Unit),
finalizeBlock: ((ResourcePatchContext) -> Unit),
) : Patch<ResourcePatchContext>(
@ -177,8 +177,8 @@ class RawResourcePatch internal constructor(
executeBlock,
finalizeBlock,
) {
override fun execute(context: PatcherContext) = execute(context.resourceContext)
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
override fun execute(context: PatcherContext) = execute(context.resourcePatchContext)
override fun finalize(context: PatcherContext) = finalize(context.resourcePatchContext)
}
/**
@ -197,7 +197,7 @@ class RawResourcePatch internal constructor(
* @param finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
* in reverse order of execution.
*
* @constructor Creates a new resource patch.
* @constructor Create a new resource patch.
*/
class ResourcePatch internal constructor(
name: String?,
@ -206,7 +206,7 @@ class ResourcePatch internal constructor(
requiresIntegrations: Boolean,
compatiblePackages: Set<Package>?,
dependencies: Set<Patch<*>>,
options: Set<PatchOption<*>>,
options: Set<Option<*>>,
executeBlock: ((ResourcePatchContext) -> Unit),
finalizeBlock: ((ResourcePatchContext) -> Unit),
) : Patch<ResourcePatchContext>(
@ -220,8 +220,8 @@ class ResourcePatch internal constructor(
executeBlock,
finalizeBlock,
) {
override fun execute(context: PatcherContext) = execute(context.resourceContext)
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
override fun execute(context: PatcherContext) = execute(context.resourcePatchContext)
override fun finalize(context: PatcherContext) = finalize(context.resourcePatchContext)
}
/**
@ -236,12 +236,12 @@ class ResourcePatch internal constructor(
* @property compatiblePackages The packages the patch is compatible with.
* If null, the patch is compatible with all packages.
* @property dependencies Other patches this patch depends on.
* @property option The options of the patch.
* @property options The options of the patch.
* @property executionBlock The execution block of the patch.
* @property finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
* in reverse order of execution.
*
* @constructor Creates a new [Patch] builder.
* @constructor Create a new [Patch] builder.
*/
sealed class PatchBuilder<C : PatchContext<*>>(
protected val name: String?,
@ -249,39 +249,36 @@ sealed class PatchBuilder<C : PatchContext<*>>(
protected val use: Boolean,
protected val requiresIntegrations: Boolean,
) {
protected var compatiblePackages: MutableSet<Package>? = null
protected val dependencies = mutableSetOf<Patch<*>>()
protected val options = mutableSetOf<PatchOption<*>>()
protected var compatiblePackages: Set<Package>? = null
protected var dependencies: Set<Patch<*>> = emptySet()
protected val options = mutableSetOf<Option<*>>()
protected var executionBlock: ((C) -> Unit) = { }
protected var finalizeBlock: ((C) -> Unit) = { }
/**
* Add a compatible package to the patch.
*
* @param versions The versions of the package.
* Add an option to the patch.
*/
operator fun String.invoke(vararg versions: String) {
if (compatiblePackages == null) compatiblePackages = mutableSetOf()
compatiblePackages!! += this to versions.toSet()
operator fun Option<*>.invoke() = apply {
options += this
}
/**
* Add the patch as a dependency.
* Sets the compatible packages of the patch.
*
* @return The added patch.
* @param block The block to set the compatible packages with.
*/
operator fun Patch<*>.invoke() = apply {
this@PatchBuilder.dependencies.add(this)
fun compatibleWith(block: CompatibleWithBuilder.() -> Unit) {
compatiblePackages = CompatibleWithBuilder().apply(block).build()
}
/**
* Add options to the patch.
* Add dependencies to the patch.
*
* @param option The options to add.
* @param block The block to add the dependencies with.
*/
fun option(vararg option: PatchOption<*>) {
options.addAll(option)
fun dependsOn(block: DependenciesBuilder.() -> Unit) {
dependencies = DependenciesBuilder().apply(block).build()
}
/**
@ -296,7 +293,7 @@ sealed class PatchBuilder<C : PatchContext<*>>(
/**
* Set the finalizing block of the patch.
*
* @param block The closing block of the patch.
* @param block The finalizing block of the patch.
*/
fun finalize(block: C.() -> Unit) {
finalizeBlock = block
@ -308,6 +305,53 @@ sealed class PatchBuilder<C : PatchContext<*>>(
* @return The built patch.
*/
internal abstract fun build(): Patch<C>
/**
* A builder for dependencies.
*
* @constructor Create a new [DependenciesBuilder].
*/
class DependenciesBuilder internal constructor() {
private val dependencies = mutableSetOf<Patch<*>>()
/**
* Add the patch as a dependency.
*
* @return The added patch.
*/
operator fun Patch<*>.invoke() = apply {
this@DependenciesBuilder.dependencies.add(this)
}
internal fun build(): Set<Patch<*>> = dependencies
}
/**
* A builder for compatible packages.
*
* @constructor Create a new [CompatibleWithBuilder].
*/
class CompatibleWithBuilder internal constructor() {
private val compatiblePackages = mutableSetOf<Package>()
/**
* Add a package the patch is compatible with.
*
* @param versions The versions of the package.
*/
operator fun String.invoke(vararg versions: String) {
compatiblePackages += this to versions.toSet()
}
/**
* Add a package the patch is compatible with.
*/
operator fun String.invoke() {
compatiblePackages += this to null as Set<String>?
}
internal fun build(): Set<Package> = compatiblePackages
}
}
/**
@ -321,7 +365,7 @@ sealed class PatchBuilder<C : PatchContext<*>>(
*
* @property fingerprints The fingerprints that are resolved before the patch is executed.
*
* @constructor Creates a new [BytecodePatchBuilder] builder.
* @constructor Create a new [BytecodePatchBuilder] builder.
*/
class BytecodePatchBuilder internal constructor(
name: String?,
@ -364,7 +408,7 @@ class BytecodePatchBuilder internal constructor(
* @param use Weather or not the patch should be used.
* @param requiresIntegrations Weather or not the patch requires integrations.
*
* @constructor Creates a new [RawResourcePatch] builder.
* @constructor Create a new [RawResourcePatch] builder.
*/
class RawResourcePatchBuilder internal constructor(
name: String?,
@ -394,7 +438,7 @@ class RawResourcePatchBuilder internal constructor(
* @param use Weather or not the patch should be used.
* @param requiresIntegrations Weather or not the patch requires integrations.
*
* @constructor Creates a new [ResourcePatch] builder.
* @constructor Create a new [ResourcePatch] builder.
*/
class ResourcePatchBuilder internal constructor(
name: String?,
@ -426,7 +470,7 @@ class ResourcePatchBuilder internal constructor(
private fun <B : PatchBuilder<*>> B.buildPatch(block: B.() -> Unit = {}) = apply(block).build()
/**
* Creates a new [BytecodePatch].
* Create a new [BytecodePatch].
*
* @param name The name of the patch.
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
@ -457,7 +501,7 @@ fun bytecodePatch(
*
* @return The created [BytecodePatch].
*/
fun PatchBuilder<*>.bytecodePatch(
fun PatchBuilder.DependenciesBuilder.bytecodePatch(
name: String? = null,
description: String? = null,
use: Boolean = true,
@ -496,7 +540,7 @@ fun rawResourcePatch(
*
* @return The created [RawResourcePatch].
*/
fun PatchBuilder<*>.rawResourcePatch(
fun PatchBuilder.DependenciesBuilder.rawResourcePatch(
name: String? = null,
description: String? = null,
use: Boolean = true,
@ -536,7 +580,7 @@ fun resourcePatch(
*
* @return The created [ResourcePatch].
*/
fun PatchBuilder<*>.resourcePatch(
fun PatchBuilder.DependenciesBuilder.resourcePatch(
name: String? = null,
description: String? = null,
use: Boolean = true,
@ -565,7 +609,8 @@ class PatchResult internal constructor(val patch: Patch<*>, val exception: Patch
/**
* A loader for [Patch].
* Loads patches from JAR or DEX files declared as public static fields or returned by public static methods.
* Loads patches from JAR or DEX files declared as public static fields
* or returned by public static and non-parametrized methods.
* Patches with no name are not loaded.
*
* @param patchesFiles A set of JAR or DEX files to load the patches from.
@ -621,7 +666,8 @@ sealed class PatchLoader private constructor(
// Companion object required for unit tests.
private companion object {
/**
* Loads named patches declared as public static fields or returned by public static methods from classes.
* Loads named patches declared as public static fields
* or returned by public static and non-parametrized methods.
*
* @param binaryClassNames The binary class name of the classes to load the patches from.
*
@ -639,7 +685,7 @@ sealed class PatchLoader private constructor(
}
val patchesFromMethods = it.methods.filter { method ->
isPatch(method.returnType) && method.canAccess(null)
isPatch(method.returnType) && method.parameterCount == 0 && method.canAccess(null)
}.map { method ->
method.invoke(null) as Patch<*>
}
@ -652,7 +698,8 @@ sealed class PatchLoader private constructor(
}
/**
* Loads patches from JAR files declared as public static fields or returned by public static methods.
* Loads patches from JAR files declared as public static fields
* or returned by public static and non-parametrized methods.
* Patches with no name are not loaded.
*
* @param patchesFiles The JAR files to load the patches from.
@ -663,7 +710,8 @@ fun loadPatchesFromJar(patchesFiles: Set<File>): PatchSet =
PatchLoader.Jar(patchesFiles)
/**
* Loads patches from DEX files declared as public static fields or returned by public static methods.
* Loads patches from DEX files declared as public static fields
* or returned by public static and non-parametrized methods.
* Patches with no name are not loaded.
*
* @param patchesFiles The DEX files to load the patches from.

View File

@ -149,7 +149,7 @@ class ResourcePatchContext internal constructor(
// Excluded because present in resources.other.
// TODO: We are reusing config.apkFiles as a temporarily directory for extracting resources.
// This is not ideal as it could conflict with files such as the ones that we filter here.
// The problem is that ResourceContext#get returns a File relative to config.apkFiles,
// The problem is that ResourcePatchContext#get returns a File relative to config.apkFiles,
// and we need to extract files to that directory.
// A solution would be to use config.apkFiles as the working directory for the patching process.
// Once all patches have been executed, we can move the decoded resources to a new directory.

View File

@ -1,6 +1,5 @@
package app.revanced.patcher.util
import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass
import app.revanced.patcher.util.ClassMerger.Utils.filterAny
@ -180,7 +179,7 @@ internal object ClassMerger {
callback: MutableClass.() -> Unit,
) {
callback(targetClass)
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
this.classByType(targetClass.superclass ?: return)?.mutableClass?.let {
traverseClassHierarchy(it, callback)
}
}
@ -199,7 +198,7 @@ internal object ClassMerger {
*
* @return The new [AccessFlags].
*/
fun Int.toPublic() = this.or(AccessFlags.PUBLIC).and(AccessFlags.PRIVATE.value.inv())
fun Int.toPublic() = or(AccessFlags.PUBLIC.value).and(AccessFlags.PRIVATE.value.inv())
/**
* Filter [this] on [needles] matching the given [predicate].

View File

@ -0,0 +1,78 @@
@file:Suppress("unused")
package app.revanced.patcher.util
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import com.android.tools.smali.dexlib2.iface.ClassDef
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.util.MethodUtil
/**
* A navigator for methods.
*
* @param context The [BytecodePatchContext] to use.
* @param method The [Method] to navigate.
*
* @constructor Creates a new [MethodNavigator].
*
* @throws NavigateException If the method does not have an implementation.
* @throws NavigateException If the instruction at the specified index is not a method reference.
*/
class MethodNavigator internal constructor(private val context: BytecodePatchContext, private var method: Method) {
var reference: MethodReference = method
/**
* Navigate to the method at the specified index.
*
* @param index The index of the method to navigate to.
*
* @return This [MethodNavigator].
*/
fun at(vararg index: Int): MethodNavigator {
index.forEach {
val currentMethod = immutable()
val instructions = currentMethod.implementation?.instructions
?: throw NavigateException(
"Method ${currentMethod.definingClass}.${currentMethod.name} does not have an implementation.",
)
val instruction = instructions.elementAt(it) as? ReferenceInstruction
val newMethod = instruction?.reference as? MethodReference
?: throw NavigateException("Instruction at index $it is not a method reference.")
reference = newMethod
}
return this
}
/**
* Get the last navigated method mutably.
*
* @return The last navigated method mutably.
*/
fun mutable() = context.classBy { classDef ->
classDef.type == reference.definingClass
}!!.mutableClass.methodBySignature() as MutableMethod
/**
* Get the last navigated method immutably.
*
* @return The last navigated method immutably.
*/
fun immutable() = context.classes.first { classDef ->
classDef.type == reference.definingClass
}.methodBySignature()
private fun ClassDef.methodBySignature() = methods.first { MethodUtil.methodSignaturesMatch(it, method) }
/**
* An exception thrown when navigating fails.
*
* @param message The message of the exception.
*/
internal class NavigateException internal constructor(message: String) : Exception(message)
}

View File

@ -4,23 +4,18 @@ import app.revanced.patcher.util.proxy.ClassProxy
import com.android.tools.smali.dexlib2.iface.ClassDef
/**
* A class that represents a set of classes and proxies.
* A list of classes and proxies.
*
* @param classes The classes to be backed by proxies.
*/
class ProxyClassList internal constructor(classes: MutableSet<ClassDef>) : MutableSet<ClassDef> by classes {
internal val proxies = mutableListOf<ClassProxy>()
/**
* Add a [ClassProxy].
*/
fun add(classProxy: ClassProxy) = proxies.add(classProxy)
class ProxyClassList internal constructor(classes: MutableList<ClassDef>) : MutableList<ClassDef> by classes {
internal val proxyPool = mutableListOf<ClassProxy>()
/**
* Replace all classes with their mutated versions.
*/
internal fun replaceClasses() =
proxies.removeIf { proxy ->
proxyPool.removeIf { proxy ->
// If the proxy is unused, return false to keep it in the proxies list.
if (!proxy.resolved) return@removeIf false

View File

@ -1,60 +0,0 @@
package app.revanced.patcher.util.method
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
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.util.MethodUtil
/**
* Find a method from another method via instruction offsets.
* @param bytecodeContext The context to use when resolving the next method reference.
* @param currentMethod The method to start from.
*/
class MethodWalker internal constructor(
private val bytecodeContext: BytecodePatchContext,
private var currentMethod: Method,
) {
/**
* Get the method which was walked last.
*
* It is possible to cast this method to a [MutableMethod], if the method has been walked mutably.
*
* @return The method which was walked last.
*/
fun getMethod(): Method {
return currentMethod
}
/**
* Walk to a method defined at the offset in the instruction list of the current method.
*
* The current method will be mutable.
*
* @param offset The offset of the instruction. This instruction must be of format 35c.
* @param walkMutable If this is true, the class of the method will be resolved mutably.
* @return The same [MethodWalker] instance with the method at [offset].
*/
fun nextMethod(
offset: Int,
walkMutable: Boolean = false,
): MethodWalker {
currentMethod.implementation?.instructions?.let { instructions ->
val instruction = instructions.elementAt(offset)
val newMethod = (instruction as ReferenceInstruction).reference as MethodReference
val proxy = bytecodeContext.findClass(newMethod.definingClass)!!
val methods = if (walkMutable) proxy.mutableClass.methods else proxy.immutableClass.methods
currentMethod =
methods.first {
return@first MethodUtil.methodSignaturesMatch(it, newMethod)
}
return this
}
throw MethodNotFoundException("This method can not be walked at offset $offset inside the method ${currentMethod.name}")
}
internal class MethodNotFoundException(exception: String) : Exception(exception)
}

View File

@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
*
* A class proxy simply holds a reference to the original class
* and allocates a mutable clone for the original class if needed.
*
* @param immutableClass The class to proxy.
*/
class ClassProxy internal constructor(

View File

@ -1,10 +1,7 @@
package app.revanced.patcher
import app.revanced.patcher.fingerprint.methodFingerprint
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchSet
import app.revanced.patcher.patch.ResourcePatchContext
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.*
import app.revanced.patcher.util.ProxyClassList
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
@ -38,8 +35,8 @@ internal object PatcherTest {
Logger.getAnonymousLogger(),
)
every { context.bytecodeContext.classes } returns mockk(relaxed = true)
every { context.bytecodeContext.integrations } returns mockk(relaxed = true)
every { context.bytecodePatchContext.classes } returns mockk(relaxed = true)
every { context.bytecodePatchContext.integrations } returns mockk(relaxed = true)
every { apply(false) } answers { callOriginal() }
}
}
@ -51,14 +48,16 @@ internal object PatcherTest {
val patches = setOf(
bytecodePatch { execute { executed += "1" } },
bytecodePatch {
// Directly depend on this patch.
bytecodePatch {
execute { executed += "2" }
finalize { executed += "-2" }
}
dependsOn {
// Directly depend on this patch.
bytecodePatch {
execute { executed += "2" }
finalize { executed += "-2" }
}
// Manually depend on this patch.
app.revanced.patcher.patch.bytecodePatch { execute { executed += "3" } }()
// Manually depend on this patch.
app.revanced.patcher.patch.bytecodePatch { execute { executed += "3" } }()
}
execute { executed += "4" }
finalize { executed += "-1" }
@ -78,26 +77,22 @@ internal object PatcherTest {
@Test
fun `throws if unresolved fingerprint result is delegated`() {
val patches = setOf(
bytecodePatch {
// Fingerprint can never be resolved.
val result by methodFingerprint {}
// Manually add the fingerprint.
app.revanced.patcher.fingerprint.methodFingerprint { }()
val patch = bytecodePatch {
// Fingerprint can never be resolved.
val result by methodFingerprint { }
// Manually add the fingerprint.
app.revanced.patcher.fingerprint.methodFingerprint { }()
execute {
// Throws, because the fingerprint can't be resolved.
result.scanResult
}
},
)
execute {
// Throws, because the fingerprint can't be resolved.
result.scanResult
}
}
assertEquals(2, patches.first().fingerprints.size)
val results = patches()
assertEquals(2, patch.fingerprints.size)
assertTrue(
results.any { it.exception != null },
patch().exception != null,
"Expected an exception because the fingerprint can't be resolved.",
)
}
@ -129,6 +124,8 @@ internal object PatcherTest {
return runBlocking { patcher.apply(false).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
@ -137,8 +134,8 @@ internal object PatcherTest {
}
private fun mockClassWithMethod() {
every { patcher.context.bytecodeContext.classes } returns ProxyClassList(
mutableSetOf(
every { patcher.context.bytecodePatchContext.classes } returns ProxyClassList(
mutableListOf(
ImmutableClassDef(
"class",
0,

View File

@ -5,24 +5,41 @@ package app.revanced.patcher.patch
import org.junit.jupiter.api.Test
import kotlin.reflect.KFunction
import kotlin.reflect.full.*
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaMethod
import kotlin.test.assertEquals
// region Test patches.
// Not loaded, because it's unnamed.
val publicUnnamedPatch = bytecodePatch {
}
// Loaded, because it's named.
val publicPatch = bytecodePatch("Public") {
}
// Not loaded, because it's private.
private val privateUnnamedPatch = bytecodePatch {
}
// Not loaded, because it's private.
private val privatePatch = bytecodePatch("Private") {
}
// Not loaded, because it's unnamed.
fun publicUnnamedPatchFunction() = publicUnnamedPatch
// Loaded, because it's named.
fun publicNamedPatchFunction() = bytecodePatch("Public") { }
// Not loaded, because it's parameterized.
fun parameterizedFunction(@Suppress("UNUSED_PARAMETER") param: Any) = publicNamedPatchFunction()
// Not loaded, because it's private.
private fun privateUnnamedPatchFunction() = privateUnnamedPatch
// Not loaded, because it's private.
private fun privateNamedPatchFunction() = privatePatch
// endregion
@ -35,19 +52,17 @@ internal object PatchLoaderTest {
@Test
fun `loads patches correctly`() {
// Get instance of private PatchLoader.Companion class.
val patchLoaderCompanionObject = PatchLoader::class.java.declaredFields.first {
it.type == PatchLoader::class.companionObject!!.javaObjectType
}.apply {
isAccessible = true
}.get(null)
val patchLoaderCompanionObject = getPrivateFieldByType(
PatchLoader::class.java,
PatchLoader::class.companionObject!!.javaObjectType,
)
// Get private PatchLoader.Companion.loadPatches function from PatchLoader.Companion.
@Suppress("UNCHECKED_CAST")
val loadPatchesFunction = patchLoaderCompanionObject::class.declaredFunctions.first {
it.name == LOAD_PATCHES_FUNCTION_NAME
}.apply {
javaMethod!!.isAccessible = true
} as KFunction<Set<Patch<*>>>
val loadPatchesFunction = getPrivateFunctionByName(
patchLoaderCompanionObject,
LOAD_PATCHES_FUNCTION_NAME,
) as KFunction<Set<Patch<*>>>
// Call private PatchLoader.Companion.loadPatches function.
val patches = loadPatchesFunction.call(
@ -60,7 +75,14 @@ internal object PatchLoaderTest {
2,
patches.size,
"Expected 2 patches to be loaded, " +
"because there's only two named patches declared as a public static field or method.",
"because there's only two named patches declared as public static fields " +
"or returned by public static and non-parametrized methods.",
)
}
private fun getPrivateFieldByType(cls: Class<*>, fieldType: Class<*>) =
cls.declaredFields.first { it.type == fieldType }.apply { isAccessible = true }.get(null)
private fun getPrivateFunctionByName(obj: Any, @Suppress("SameParameterValue") methodName: String) =
obj::class.declaredFunctions.first { it.name == methodName }.apply { isAccessible = true }
}

View File

@ -15,7 +15,9 @@ internal object PatchTest {
@Test
fun `can create patch with compatible packages`() {
val patch = bytecodePatch(name = "Test") {
"compatible.package"("1.0.0")
compatibleWith {
"compatible.package"("1.0.0")
}
}
assertEquals(1, patch.compatiblePackages!!.size)
@ -44,8 +46,11 @@ internal object PatchTest {
val externalPatch = resourcePatch {}
val patch = bytecodePatch(name = "Test") {
externalPatch()
resourcePatch {}
dependsOn {
externalPatch()
resourcePatch {}
}
}
assertEquals(2, patch.dependencies.size)
@ -55,13 +60,15 @@ internal object PatchTest {
@Test
fun `can create patch with options`() {
val patch = bytecodePatch(name = "Test") {
val print by stringPatchOption("print")
val print by stringOption("print")
val custom = option<String>("custom")()
execute {
println(print)
println(custom.value)
}
}
assertEquals(1, patch.options.size)
assertEquals(2, patch.options.size)
}
}

View File

@ -3,23 +3,22 @@ package app.revanced.patcher.patch.options
import app.revanced.patcher.patch.*
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import kotlin.test.Test
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlin.reflect.typeOf
import kotlin.test.*
internal object PatchOptionsTest {
internal object OptionsTest {
private val optionsTestPatch = bytecodePatch {
booleanPatchOption("bool", true)
booleanOption("bool", true)
stringPatchOption("required", "default", required = true)
stringOption("required", "default", required = true)
stringArrayPatchOption("array", arrayOf("1", "2"))
stringsOption("list", listOf("1", "2"))
stringPatchOption("choices", "value", values = mapOf("Valid option value" to "valid"))
stringOption("choices", "value", values = mapOf("Valid option value" to "valid"))
stringPatchOption("validated", "default") { it == "valid" }
stringOption("validated", "default") { it == "valid" }
stringPatchOption("resettable", null, required = true)
stringOption("resettable", null, required = true)
}
@Test
@ -30,12 +29,12 @@ internal object PatchOptionsTest {
@Test
fun `should not allow setting custom value with validation`() = options {
// Getter validation on incorrect value.
assertThrows<PatchOptionException.ValueValidationException> {
assertThrows<OptionException.ValueValidationException> {
set("validated", get("validated"))
}
// Setter validation on incorrect value.
assertThrows<PatchOptionException.ValueValidationException> {
assertThrows<OptionException.ValueValidationException> {
set("validated", "invalid")
}
@ -47,7 +46,7 @@ internal object PatchOptionsTest {
@Test
fun `should throw due to incorrect type`() = options {
assertThrows<PatchOptionException.InvalidValueTypeException> {
assertThrows<OptionException.InvalidValueTypeException> {
set("bool", "not a boolean")
}
}
@ -61,7 +60,7 @@ internal object PatchOptionsTest {
@Test
fun `option should not be found`() = options {
assertThrows<PatchOptionException.PatchOptionNotFoundException> {
assertThrows<OptionException.OptionNotFoundException> {
set("this option does not exist", 1)
}
}
@ -70,15 +69,15 @@ internal object PatchOptionsTest {
fun `should be able to add options manually`() = options {
assertDoesNotThrow {
bytecodePatch {
option(get("array"))
}.options["array"]
get("list")()
}.options["list"]
}
}
@Test
fun `should allow setting value from values`() = options {
@Suppress("UNCHECKED_CAST")
val option = get("choices") as PatchOption<String>
val option = get("choices") as Option<String>
option.value = option.values!!.values.last()
@ -108,14 +107,14 @@ internal object PatchOptionsTest {
get("resettable").reset()
}
assertThrows<PatchOptionException.ValueRequiredException> {
assertThrows<OptionException.ValueRequiredException> {
get("resettable").value
}
}
@Test
fun `option types should be known`() = options {
assertTrue(get("array").valueType == "StringArray")
assertEquals(typeOf<List<String>>(), get("list").type)
}
@Test
@ -125,5 +124,5 @@ internal object PatchOptionsTest {
}
}
private fun options(block: PatchOptions.() -> Unit) = optionsTestPatch.options.let(block)
private fun options(block: Options.() -> Unit) = optionsTestPatch.options.let(block)
}