diff --git a/revanced-patch-annotations-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/processor/PatchProcessor.kt b/revanced-patch-annotations-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/processor/PatchProcessor.kt index f597e8da..b5b19e96 100644 --- a/revanced-patch-annotations-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/processor/PatchProcessor.kt +++ b/revanced-patch-annotations-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/processor/PatchProcessor.kt @@ -3,7 +3,6 @@ package app.revanced.patcher.patch.annotations.processor import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.PatchOptions import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.annotations.Patch import com.google.devtools.ksp.processing.* @@ -114,7 +113,6 @@ class PatchProcessor( } } - // kotlin poet generate a class for each patch executablePatches.forEach { (patchDeclaration, patchAnnotation) -> val isBytecodePatch = patchDeclaration.isSubclassOf(BytecodePatch::class) @@ -159,7 +157,7 @@ class PatchProcessor( "dependencies = setOf(%L)", buildList { addAll(dependencies) - // Also add the source class of the generated class so that it is also executed + // Also add the source class of the generated class so that it is also executed. add(patchDeclaration.toClassName()) }.joinToString(", ") { dependency -> "${(dependencyResolutionMap[dependency] ?: dependency)}::class" @@ -181,9 +179,18 @@ class PatchProcessor( .addParameter("context", contextClass) .build() ) - .addProperty( - PropertySpec.builder("options", PatchOptions::class, KModifier.OVERRIDE) - .initializer("%T.options", patchDeclaration.toClassName()) + .addInitializerBlock( + CodeBlock.builder() + .add( + "%T.options.forEach { (key, option) ->", + patchDeclaration.toClassName() + ) + .addStatement( + "options.register(option)" + ) + .add( + "}" + ) .build() ) .build() diff --git a/revanced-patch-annotations-processor/src/test/kotlin/app/revanced/patcher/patch/annotations/processor/TestPatchAnnotationProcessor.kt b/revanced-patch-annotations-processor/src/test/kotlin/app/revanced/patcher/patch/annotations/processor/TestPatchAnnotationProcessor.kt index 850f2610..f2f8593b 100644 --- a/revanced-patch-annotations-processor/src/test/kotlin/app/revanced/patcher/patch/annotations/processor/TestPatchAnnotationProcessor.kt +++ b/revanced-patch-annotations-processor/src/test/kotlin/app/revanced/patcher/patch/annotations/processor/TestPatchAnnotationProcessor.kt @@ -7,7 +7,6 @@ import com.tschuchort.compiletesting.kspWithCompilation import com.tschuchort.compiletesting.symbolProcessorProviders import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertNotNull import kotlin.test.assertNull class TestPatchAnnotationProcessor { @@ -64,7 +63,7 @@ class TestPatchAnnotationProcessor { ) ).loadPatch("$SAMPLE_PACKAGE.options.OptionsPatchGenerated") - assertNotNull(patch.options) + assert(patch.options.isNotEmpty()) assertEquals(patch.options["print"].title, "Print message") } diff --git a/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/options/OptionsPatch.kt b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/options/OptionsPatch.kt index 4aaf93e3..f1a8c22a 100644 --- a/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/options/OptionsPatch.kt +++ b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/options/OptionsPatch.kt @@ -1,21 +1,19 @@ package app.revanced.patcher.patch.annotations.processor.samples.options import app.revanced.patcher.data.ResourceContext -import app.revanced.patcher.patch.PatchOption import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.patch.options.types.StringPatchOption.Companion.stringPatchOption @Patch(name = "Options patch") object OptionsPatch : ResourcePatch() { override fun execute(context: ResourceContext) {} @Suppress("unused") - private val printOption by option( - PatchOption.StringOption( - "print", - null, - "Print message", - "The message to print." - ) + private val printOption by stringPatchOption( + "print", + null, + "Print message", + "The message to print." ) } \ No newline at end of file diff --git a/revanced-patcher/api/revanced-patcher.api b/revanced-patcher/api/revanced-patcher.api index 8987c4ec..2b323a25 100644 --- a/revanced-patcher/api/revanced-patcher.api +++ b/revanced-patcher/api/revanced-patcher.api @@ -332,29 +332,7 @@ public abstract class app/revanced/patcher/patch/BytecodePatch : app/revanced/pa public synthetic fun (Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V } -public final class app/revanced/patcher/patch/IllegalValueException : java/lang/Exception { - public fun (Ljava/lang/Object;)V - public final fun getValue ()Ljava/lang/Object; -} - -public final class app/revanced/patcher/patch/InvalidTypeException : java/lang/Exception { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun getExpected ()Ljava/lang/String; - public final fun getGot ()Ljava/lang/String; -} - -public final class app/revanced/patcher/patch/NoSuchOptionException : java/lang/Exception { - public fun (Ljava/lang/String;)V - public final fun getOption ()Ljava/lang/String; -} - -public abstract class app/revanced/patcher/patch/OptionsContainer { - public fun ()V - public fun getOptions ()Lapp/revanced/patcher/patch/PatchOptions; - protected final fun option (Lapp/revanced/patcher/patch/PatchOption;)Lapp/revanced/patcher/patch/PatchOption; -} - -public abstract class app/revanced/patcher/patch/Patch : app/revanced/patcher/patch/OptionsContainer { +public abstract class app/revanced/patcher/patch/Patch { public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZLkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z @@ -363,6 +341,7 @@ public abstract class app/revanced/patcher/patch/Patch : app/revanced/patcher/pa 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/options/PatchOptions; public final fun getRequiresIntegrations ()Z public final fun getUse ()Z public fun hashCode ()I @@ -381,67 +360,176 @@ public final class app/revanced/patcher/patch/PatchException : java/lang/Excepti public fun (Ljava/lang/Throwable;)V } -public abstract class app/revanced/patcher/patch/PatchOption { - public synthetic fun (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +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; +} + +public abstract class app/revanced/patcher/patch/ResourcePatch : app/revanced/patcher/patch/Patch { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZ)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public abstract class app/revanced/patcher/patch/options/PatchOption { + public fun (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V 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/Function1; + public final fun getValidate ()Lkotlin/jvm/functions/Function1; public final fun getValue ()Ljava/lang/Object; public final fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; public final fun setValue (Ljava/lang/Object;)V public final fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V } -public final class app/revanced/patcher/patch/PatchOption$BooleanOption : app/revanced/patcher/patch/PatchOption { - public fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public abstract class app/revanced/patcher/patch/options/PatchOptionException : java/lang/Exception { + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V } -public final class app/revanced/patcher/patch/PatchOption$IntListOption : app/revanced/patcher/patch/PatchOption$ListOption { - public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final class app/revanced/patcher/patch/options/PatchOptionException$InvalidValueTypeException : app/revanced/patcher/patch/options/PatchOptionException { + public fun (Ljava/lang/String;Ljava/lang/String;)V } -public abstract class app/revanced/patcher/patch/PatchOption$ListOption : app/revanced/patcher/patch/PatchOption { - public synthetic fun (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getOptions ()Ljava/lang/Iterable; +public final class app/revanced/patcher/patch/options/PatchOptionException$PatchOptionNotFoundException : java/lang/Exception { + public fun (Ljava/lang/String;)V } -public final class app/revanced/patcher/patch/PatchOption$StringListOption : app/revanced/patcher/patch/PatchOption$ListOption { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final class app/revanced/patcher/patch/options/PatchOptionException$ValueRequiredException : java/lang/Exception { + public fun (Lapp/revanced/patcher/patch/options/PatchOption;)V } -public final class app/revanced/patcher/patch/PatchOption$StringOption : app/revanced/patcher/patch/PatchOption { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final class app/revanced/patcher/patch/options/PatchOptionException$ValueValidationException : java/lang/Exception { + public fun (Ljava/lang/Object;Lapp/revanced/patcher/patch/options/PatchOption;)V } -public final class app/revanced/patcher/patch/PatchOptions : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { - public fun ([Lapp/revanced/patcher/patch/PatchOption;)V - public final fun getUntyped (Ljava/lang/String;)Lapp/revanced/patcher/patch/PatchOption; - public fun iterator ()Ljava/util/Iterator; - public final fun nullify (Ljava/lang/String;)V +public final class app/revanced/patcher/patch/options/PatchOptions : java/util/Map, kotlin/jvm/internal/markers/KMutableMap { + public fun ()V + public fun clear ()V + public final fun containsKey (Ljava/lang/Object;)Z + public fun containsKey (Ljava/lang/String;)Z + public fun containsValue (Lapp/revanced/patcher/patch/options/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/options/PatchOption; + public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; + public fun get (Ljava/lang/String;)Lapp/revanced/patcher/patch/options/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 put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + public fun put (Ljava/lang/String;Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/patcher/patch/options/PatchOption; + public fun putAll (Ljava/util/Map;)V + public final fun register (Lapp/revanced/patcher/patch/options/PatchOption;)V + public final fun remove (Ljava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption; + public final synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object; + public fun remove (Ljava/lang/String;)Lapp/revanced/patcher/patch/options/PatchOption; + public final fun size ()I + public final fun values ()Ljava/util/Collection; } -public final class app/revanced/patcher/patch/PatchResult { - public fun equals (Ljava/lang/Object;)Z - public final fun getException ()Lapp/revanced/patcher/patch/PatchException; - public final fun getPatch ()Lapp/revanced/patcher/patch/Patch; - public fun hashCode ()I +public final class app/revanced/patcher/patch/options/types/BooleanPatchOption : app/revanced/patcher/patch/options/PatchOption { + public static final field Companion Lapp/revanced/patcher/patch/options/types/BooleanPatchOption$Companion; + public synthetic fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V } -public final class app/revanced/patcher/patch/RequirementNotMetException : java/lang/Exception { - public static final field INSTANCE Lapp/revanced/patcher/patch/RequirementNotMetException; +public final class app/revanced/patcher/patch/options/types/BooleanPatchOption$Companion { + public final fun booleanPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/options/types/BooleanPatchOption; + public static synthetic fun booleanPatchOption$default (Lapp/revanced/patcher/patch/options/types/BooleanPatchOption$Companion;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/types/BooleanPatchOption; } -public abstract class app/revanced/patcher/patch/ResourcePatch : app/revanced/patcher/patch/Patch { - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZ)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final class app/revanced/patcher/patch/options/types/FloatPatchOption : app/revanced/patcher/patch/options/PatchOption { + public static final field Companion Lapp/revanced/patcher/patch/options/types/FloatPatchOption$Companion; + public synthetic fun (Ljava/lang/String;Ljava/lang/Float;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class app/revanced/patcher/patch/options/types/FloatPatchOption$Companion { + public final fun floatPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Float;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/options/types/FloatPatchOption; + public static synthetic fun floatPatchOption$default (Lapp/revanced/patcher/patch/options/types/FloatPatchOption$Companion;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Float;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/types/FloatPatchOption; +} + +public final class app/revanced/patcher/patch/options/types/IntPatchOption : app/revanced/patcher/patch/options/PatchOption { + public static final field Companion Lapp/revanced/patcher/patch/options/types/IntPatchOption$Companion; + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class app/revanced/patcher/patch/options/types/IntPatchOption$Companion { + public final fun intPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/options/types/IntPatchOption; + public static synthetic fun intPatchOption$default (Lapp/revanced/patcher/patch/options/types/IntPatchOption$Companion;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/types/IntPatchOption; +} + +public final class app/revanced/patcher/patch/options/types/LongPatchOption : app/revanced/patcher/patch/options/PatchOption { + public static final field Companion Lapp/revanced/patcher/patch/options/types/LongPatchOption$Companion; + public synthetic fun (Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class app/revanced/patcher/patch/options/types/LongPatchOption$Companion { + public final fun longPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/options/types/LongPatchOption; + public static synthetic fun longPatchOption$default (Lapp/revanced/patcher/patch/options/types/LongPatchOption$Companion;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/types/LongPatchOption; +} + +public final class app/revanced/patcher/patch/options/types/StringPatchOption : app/revanced/patcher/patch/options/PatchOption { + public static final field Companion Lapp/revanced/patcher/patch/options/types/StringPatchOption$Companion; + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class app/revanced/patcher/patch/options/types/StringPatchOption$Companion { + public final fun stringPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/options/types/StringPatchOption; + public static synthetic fun stringPatchOption$default (Lapp/revanced/patcher/patch/options/types/StringPatchOption$Companion;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/types/StringPatchOption; +} + +public final class app/revanced/patcher/patch/options/types/array/BooleanArrayPatchOption : app/revanced/patcher/patch/options/PatchOption { + public static final field Companion Lapp/revanced/patcher/patch/options/types/array/BooleanArrayPatchOption$Companion; + public synthetic fun (Ljava/lang/String;[Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class app/revanced/patcher/patch/options/types/array/BooleanArrayPatchOption$Companion { + public final fun booleanArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/options/types/array/BooleanArrayPatchOption; + public static synthetic fun booleanArrayPatchOption$default (Lapp/revanced/patcher/patch/options/types/array/BooleanArrayPatchOption$Companion;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/types/array/BooleanArrayPatchOption; +} + +public final class app/revanced/patcher/patch/options/types/array/FloatArrayPatchOption : app/revanced/patcher/patch/options/PatchOption { + public static final field Companion Lapp/revanced/patcher/patch/options/types/array/FloatArrayPatchOption$Companion; + public synthetic fun (Ljava/lang/String;[Ljava/lang/Float;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class app/revanced/patcher/patch/options/types/array/FloatArrayPatchOption$Companion { + public final fun floatArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Float;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/options/types/array/FloatArrayPatchOption; + public static synthetic fun floatArrayPatchOption$default (Lapp/revanced/patcher/patch/options/types/array/FloatArrayPatchOption$Companion;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Float;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/types/array/FloatArrayPatchOption; +} + +public final class app/revanced/patcher/patch/options/types/array/IntArrayPatchOption : app/revanced/patcher/patch/options/PatchOption { + public static final field Companion Lapp/revanced/patcher/patch/options/types/array/IntArrayPatchOption$Companion; + public synthetic fun (Ljava/lang/String;[Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class app/revanced/patcher/patch/options/types/array/IntArrayPatchOption$Companion { + public final fun intArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/options/types/array/IntArrayPatchOption; + public static synthetic fun intArrayPatchOption$default (Lapp/revanced/patcher/patch/options/types/array/IntArrayPatchOption$Companion;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/types/array/IntArrayPatchOption; +} + +public final class app/revanced/patcher/patch/options/types/array/LongArrayPatchOption : app/revanced/patcher/patch/options/PatchOption { + public static final field Companion Lapp/revanced/patcher/patch/options/types/array/LongArrayPatchOption$Companion; + public synthetic fun (Ljava/lang/String;[Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class app/revanced/patcher/patch/options/types/array/LongArrayPatchOption$Companion { + public final fun longArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/options/types/array/LongArrayPatchOption; + public static synthetic fun longArrayPatchOption$default (Lapp/revanced/patcher/patch/options/types/array/LongArrayPatchOption$Companion;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/types/array/LongArrayPatchOption; +} + +public final class app/revanced/patcher/patch/options/types/array/StringArrayPatchOption : app/revanced/patcher/patch/options/PatchOption { + public static final field Companion Lapp/revanced/patcher/patch/options/types/array/StringArrayPatchOption$Companion; + public synthetic fun (Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class app/revanced/patcher/patch/options/types/array/StringArrayPatchOption$Companion { + public final fun stringArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/options/types/array/StringArrayPatchOption; + public static synthetic fun stringArrayPatchOption$default (Lapp/revanced/patcher/patch/options/types/array/StringArrayPatchOption$Companion;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/types/array/StringArrayPatchOption; } public final class app/revanced/patcher/util/DomFileEditor : java/io/Closeable { diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/BytecodePatch.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/BytecodePatch.kt new file mode 100644 index 00000000..7c154818 --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/BytecodePatch.kt @@ -0,0 +1,27 @@ +package app.revanced.patcher.patch + +import app.revanced.patcher.PatchClass +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +/** + * A ReVanced [Patch] that works on [BytecodeContext]. + * + * @param fingerprints A list of [MethodFingerprint]s which will be resolved before the patch is executed. + * @param name The name of the patch. + * @param description The description of the patch. + * @param compatiblePackages The packages the patch is compatible with. + * @param dependencies The names of patches this patch depends on. + * @param use Weather or not the patch should be used. + * @param requiresIntegrations Weather or not the patch requires integrations. + */ +abstract class BytecodePatch( + internal val fingerprints: Set = emptySet(), + name: String? = null, + description: String? = null, + compatiblePackages: Set? = null, + dependencies: Set? = null, + use: Boolean = true, + // TODO: Remove this property, once integrations are coupled with patches. + requiresIntegrations: Boolean = false, +) : Patch(name, description, compatiblePackages, dependencies, use, requiresIntegrations) \ No newline at end of file diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/OptionsContainer.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/OptionsContainer.kt deleted file mode 100644 index 52320a39..00000000 --- a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/OptionsContainer.kt +++ /dev/null @@ -1,23 +0,0 @@ -package app.revanced.patcher.patch - -/** - * A container for patch options. - */ -abstract class OptionsContainer { - /** - * A list of [PatchOption]s. - * @see PatchOptions - */ - @Suppress("MemberVisibilityCanBePrivate") - open val options = PatchOptions() - - /** - * Registers a [PatchOption]. - * @param opt The [PatchOption] to register. - * @return The registered [PatchOption]. - */ - protected fun option(opt: PatchOption): PatchOption { - options.register(opt) - return opt - } -} \ No newline at end of file diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/Patch.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/Patch.kt index 549180ea..e54d2f0a 100644 --- a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/Patch.kt +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/Patch.kt @@ -4,10 +4,8 @@ package app.revanced.patcher.patch import app.revanced.patcher.PatchClass import app.revanced.patcher.Patcher -import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.Context -import app.revanced.patcher.data.ResourceContext -import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.patch.options.PatchOptions import java.io.Closeable /** @@ -32,7 +30,12 @@ sealed class Patch>( val use: Boolean = true, // TODO: Remove this property, once integrations are coupled with patches. val requiresIntegrations: Boolean = false, -) : OptionsContainer() { +) { + /** + * The options of the patch associated by the options key. + */ + val options = PatchOptions() + /** * The execution function of the patch. * @@ -66,44 +69,3 @@ sealed class Patch>( ) } -/** - * A ReVanced [Patch] that works on [ResourceContext]. - * - * @param name The name of the patch. - * @param description The description of the patch. - * @param compatiblePackages The packages the patch is compatible with. - * @param dependencies The names of patches this patch depends on. - * @param use Weather or not the patch should be used. - * @param requiresIntegrations Weather or not the patch requires integrations. - */ -abstract class ResourcePatch( - name: String? = null, - description: String? = null, - compatiblePackages: Set? = null, - dependencies: Set? = null, - use: Boolean = true, - // TODO: Remove this property, once integrations are coupled with patches. - requiresIntegrations: Boolean = false, -) : Patch(name, description, compatiblePackages, dependencies, use, requiresIntegrations) - -/** - * A ReVanced [Patch] that works on [BytecodeContext]. - * - * @param fingerprints A list of [MethodFingerprint]s which will be resolved before the patch is executed. - * @param name The name of the patch. - * @param description The description of the patch. - * @param compatiblePackages The packages the patch is compatible with. - * @param dependencies The names of patches this patch depends on. - * @param use Weather or not the patch should be used. - * @param requiresIntegrations Weather or not the patch requires integrations. - */ -abstract class BytecodePatch( - internal val fingerprints: Set = emptySet(), - name: String? = null, - description: String? = null, - compatiblePackages: Set? = null, - dependencies: Set? = null, - use: Boolean = true, - // TODO: Remove this property, once integrations are coupled with patches. - requiresIntegrations: Boolean = false, -) : Patch(name, description, compatiblePackages, dependencies, use, requiresIntegrations) \ No newline at end of file diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt deleted file mode 100644 index 328debe6..00000000 --- a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt +++ /dev/null @@ -1,230 +0,0 @@ -@file:Suppress("CanBeParameter", "MemberVisibilityCanBePrivate", "UNCHECKED_CAST") - -package app.revanced.patcher.patch - -import kotlin.reflect.KProperty - -class NoSuchOptionException(val option: String) : Exception("No such option: $option") -class IllegalValueException(val value: Any?) : Exception("Illegal value: $value") -class InvalidTypeException(val got: String, val expected: String) : - Exception("Invalid option value type: $got, expected $expected") - -object RequirementNotMetException : Exception("null was passed into an option that requires a value") - -/** - * A registry for an array of [PatchOption]s. - * @param options An array of [PatchOption]s. - */ -class PatchOptions(vararg options: PatchOption<*>) : Iterable> { - private val register = mutableMapOf>() - - init { - options.forEach { register(it) } - } - - internal fun register(option: PatchOption<*>) { - if (register.containsKey(option.key)) { - throw IllegalStateException("Multiple options found with the same key") - } - register[option.key] = option - } - - /** - * Get a [PatchOption] by its key. - * @param key The key of the [PatchOption]. - */ - @JvmName("getUntyped") - operator fun get(key: String) = register[key] ?: throw NoSuchOptionException(key) - - /** - * Get a [PatchOption] by its key. - * @param key The key of the [PatchOption]. - */ - inline operator fun get(key: String): PatchOption { - val opt = get(key) - if (opt.value !is T) throw InvalidTypeException( - opt.value?.let { it::class.java.canonicalName } ?: "null", - T::class.java.canonicalName - ) - return opt as PatchOption - } - - /** - * Set the value of a [PatchOption]. - * @param key The key of the [PatchOption]. - * @param value The value you want it to be. - * Please note that using the wrong value type results in a runtime error. - */ - inline operator fun set(key: String, value: T) { - val opt = get(key) - if (opt.value !is T) throw InvalidTypeException( - T::class.java.canonicalName, - opt.value?.let { it::class.java.canonicalName } ?: "null" - ) - opt.value = value - } - - /** - * Sets the value of a [PatchOption] to `null`. - * @param key The key of the [PatchOption]. - */ - fun nullify(key: String) { - get(key).value = null - } - - override fun iterator() = register.values.iterator() -} - -/** - * A [Patch] option. - * @param key Unique identifier of the option. Example: _`settings`_ - * @param default The default value of the option. - * @param title A human-readable title of the option. Example: _Patch Settings_ - * @param description A human-readable description of the option. Example: _Settings for the patches._ - * @param required Whether the option is required. - */ -@Suppress("MemberVisibilityCanBePrivate") -sealed class PatchOption( - val key: String, - default: T?, - val title: String, - val description: String, - val required: Boolean, - val validator: (T?) -> Boolean -) { - var value: T? = default - get() { - if (field == null && required) { - throw RequirementNotMetException - } - return field - } - set(value) { - if (value == null && required) { - throw RequirementNotMetException - } - if (!validator(value)) { - throw IllegalValueException(value) - } - field = value - } - - /** - * Gets the value of the option. - * Please note that using the wrong value type results in a runtime error. - */ - @JvmName("getValueTyped") - inline operator fun getValue(thisRef: Nothing?, property: KProperty<*>): V? { - if (value !is V?) throw InvalidTypeException( - V::class.java.canonicalName, - value?.let { it::class.java.canonicalName } ?: "null" - ) - return value as? V? - } - - operator fun getValue(thisRef: Any?, property: KProperty<*>) = value - - /** - * Gets the value of the option. - * Please note that using the wrong value type results in a runtime error. - */ - @JvmName("setValueTyped") - inline operator fun setValue(thisRef: Nothing?, property: KProperty<*>, new: V) { - if (value !is V) throw InvalidTypeException( - V::class.java.canonicalName, - value?.let { it::class.java.canonicalName } ?: "null" - ) - value = new as T - } - - operator fun setValue(thisRef: Any?, property: KProperty<*>, new: T?) { - value = new - } - - /** - * A [PatchOption] representing a [String]. - * @see PatchOption - */ - class StringOption( - key: String, - default: String?, - title: String, - description: String, - required: Boolean = false, - validator: (String?) -> Boolean = { true } - ) : PatchOption( - key, default, title, description, required, validator - ) - - /** - * A [PatchOption] representing a [Boolean]. - * @see PatchOption - */ - class BooleanOption( - key: String, - default: Boolean?, - title: String, - description: String, - required: Boolean = false, - validator: (Boolean?) -> Boolean = { true } - ) : PatchOption( - key, default, title, description, required, validator - ) - - /** - * A [PatchOption] with a list of allowed options. - * @param options A list of allowed options for the [ListOption]. - * @see PatchOption - */ - sealed class ListOption( - key: String, - default: E?, - val options: Iterable, - title: String, - description: String, - required: Boolean = false, - validator: (E?) -> Boolean = { true } - ) : PatchOption( - key, default, title, description, required, { - (it?.let { it in options } ?: true) && validator(it) - } - ) { - init { - if (default != null && default !in options) { - throw IllegalStateException("Default option must be an allowed option") - } - } - } - - /** - * A [ListOption] of type [String]. - * @see ListOption - */ - class StringListOption( - key: String, - default: String?, - options: Iterable, - title: String, - description: String, - required: Boolean = false, - validator: (String?) -> Boolean = { true } - ) : ListOption( - key, default, options, title, description, required, validator - ) - - /** - * A [ListOption] of type [Int]. - * @see ListOption - */ - class IntListOption( - key: String, - default: Int?, - options: Iterable, - title: String, - description: String, - required: Boolean = false, - validator: (Int?) -> Boolean = { true } - ) : ListOption( - key, default, options, title, description, required, validator - ) -} diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/PatchResult.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/PatchResult.kt index 5be69f54..71d40b72 100644 --- a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/PatchResult.kt +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/PatchResult.kt @@ -7,14 +7,4 @@ package app.revanced.patcher.patch * @param exception The [PatchException] thrown, if any. */ @Suppress("MemberVisibilityCanBePrivate") -class PatchResult internal constructor(val patch: Patch<*>, val exception: PatchException? = null) { - override fun hashCode() = patch.hashCode() - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as PatchResult - - return patch == other.patch - } -} \ No newline at end of file +class PatchResult internal constructor(val patch: Patch<*>, val exception: PatchException? = null) \ No newline at end of file diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/ResourcePatch.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/ResourcePatch.kt new file mode 100644 index 00000000..b5d42299 --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/ResourcePatch.kt @@ -0,0 +1,24 @@ +package app.revanced.patcher.patch + +import app.revanced.patcher.PatchClass +import app.revanced.patcher.data.ResourceContext + +/** + * A ReVanced [Patch] that works on [ResourceContext]. + * + * @param name The name of the patch. + * @param description The description of the patch. + * @param compatiblePackages The packages the patch is compatible with. + * @param dependencies The names of patches this patch depends on. + * @param use Weather or not the patch should be used. + * @param requiresIntegrations Weather or not the patch requires integrations. + */ +abstract class ResourcePatch( + name: String? = null, + description: String? = null, + compatiblePackages: Set? = null, + dependencies: Set? = null, + use: Boolean = true, + // TODO: Remove this property, once integrations are coupled with patches. + requiresIntegrations: Boolean = false, +) : Patch(name, description, compatiblePackages, dependencies, use, requiresIntegrations) \ No newline at end of file diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/PatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/PatchOption.kt new file mode 100644 index 00000000..df130bf4 --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/PatchOption.kt @@ -0,0 +1,40 @@ +package app.revanced.patcher.patch.options + +import app.revanced.patcher.patch.Patch +import kotlin.reflect.KProperty + +/** + * A [Patch] option. + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * @param validate The function to validate values of the option. + * @param T The value type of the option. + */ +abstract class PatchOption( + val key: String, + default: T?, + val title: String?, + val description: String?, + val required: Boolean, + val validate: (T?) -> Boolean +) { + /** + * The value of the [PatchOption]. + */ + var value: T? = default + set(value) { + if (required && value == null) throw PatchOptionException.ValueRequiredException(this) + if (!validate(value)) throw PatchOptionException.ValueValidationException(value, this) + + field = value + } + + operator fun getValue(thisRef: Any?, property: KProperty<*>) = value + + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { + this.value = value + } +} \ No newline at end of file diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/PatchOptionException.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/PatchOptionException.kt new file mode 100644 index 00000000..1f84803d --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/PatchOptionException.kt @@ -0,0 +1,41 @@ +package app.revanced.patcher.patch.options + +/** + * An exception thrown when using [PatchOption]s. + * + * @param errorMessage The exception message. + */ +sealed class PatchOptionException(errorMessage: String) : Exception(errorMessage, null) { + /** + * An exception thrown when a [PatchOption] 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") + + /** + * An exception thrown when a value did not satisfy the value conditions specified by the [PatchOption]. + * + * @param value The value that failed validation. + */ + class ValueValidationException(value: Any?, option: PatchOption<*>) : + Exception("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. + */ + class ValueRequiredException(option: PatchOption<*>) : + Exception("The option ${option.key} requires a value, but null was passed") + + /** + * An exception thrown when a [PatchOption] is not found. + * + * @param key The key of the [PatchOption]. + */ + class PatchOptionNotFoundException(key: String) + : Exception("No option with key $key") +} \ No newline at end of file diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/PatchOptions.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/PatchOptions.kt new file mode 100644 index 00000000..93d3f6c9 --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/PatchOptions.kt @@ -0,0 +1,43 @@ +package app.revanced.patcher.patch.options + + +/** + * A map of [PatchOption]s associated by their keys. + * + * @param options The [PatchOption]s to initialize with. + */ +class PatchOptions internal constructor( + private val options: MutableMap> = mutableMapOf() +) : MutableMap> by options { + /** + * Register a [PatchOption]. Acts like [MutableMap.put]. + * @param value The [PatchOption] to register. + */ + fun register(value: PatchOption<*>) { + options[value.key] = value + } + + /** + * Set an option's value. + * @param key The identifier. + * @param value The value. + * @throws PatchOptionException.PatchOptionNotFoundException If the option does not exist. + */ + inline operator fun set(key: String, value: T?) { + val option = this[key] + + if (option.value !is T) throw PatchOptionException.InvalidValueTypeException( + T::class.java.name, + option.value?.let { it::class.java.name } ?: "null", + ) + + @Suppress("UNCHECKED_CAST") + (option as PatchOption).value = value + } + + /** + * Get an option. + */ + override operator fun get(key: String) = + options[key] ?: throw PatchOptionException.PatchOptionNotFoundException(key) +} \ No newline at end of file diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/BooleanPatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/BooleanPatchOption.kt new file mode 100644 index 00000000..471478c4 --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/BooleanPatchOption.kt @@ -0,0 +1,48 @@ +package app.revanced.patcher.patch.options.types + +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.options.PatchOption + +/** + * A [PatchOption] representing a [Boolean]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * + * @see PatchOption + */ +class BooleanPatchOption private constructor( + key: String, + default: Boolean?, + title: String?, + description: String?, + required: Boolean, + validator: (Boolean?) -> Boolean +) : PatchOption(key, default, title, description, required, validator) { + companion object { + /** + * Create a new [BooleanPatchOption] and add it to the current [Patch]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * @return The created [BooleanPatchOption]. + * + * @see BooleanPatchOption + * @see PatchOption + */ + fun > T.booleanPatchOption( + key: String, + default: Boolean? = null, + title: String? = null, + description: String? = null, + required: Boolean = false, + validator: (Boolean?) -> Boolean = { true } + ) = BooleanPatchOption(key, default, title, description, required, validator).also { options.register(it) } + } +} diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/FloatPatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/FloatPatchOption.kt new file mode 100644 index 00000000..beec13ab --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/FloatPatchOption.kt @@ -0,0 +1,48 @@ +package app.revanced.patcher.patch.options.types + +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.options.PatchOption + +/** + * A [PatchOption] representing a [Float]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * + * @see PatchOption + */ +class FloatPatchOption private constructor( + key: String, + default: Float?, + title: String?, + description: String?, + required: Boolean, + validator: (Float?) -> Boolean +) : PatchOption(key, default, title, description, required, validator) { + companion object { + /** + * Create a new [FloatPatchOption] and add it to the current [Patch]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * @return The created [FloatPatchOption]. + * + * @see FloatPatchOption + * @see PatchOption + */ + fun > T.floatPatchOption( + key: String, + default: Float? = null, + title: String? = null, + description: String? = null, + required: Boolean = false, + validator: (Float?) -> Boolean = { true } + ) = FloatPatchOption(key, default, title, description, required, validator).also { options.register(it) } + } +} diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/IntPatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/IntPatchOption.kt new file mode 100644 index 00000000..c815b59c --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/IntPatchOption.kt @@ -0,0 +1,48 @@ +package app.revanced.patcher.patch.options.types + +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.options.PatchOption + +/** + * A [PatchOption] representing an [Integer]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * + * @see PatchOption + */ +class IntPatchOption private constructor( + key: String, + default: Int?, + title: String?, + description: String?, + required: Boolean, + validator: (Int?) -> Boolean +) : PatchOption(key, default, title, description, required, validator) { + companion object { + /** + * Create a new [IntPatchOption] and add it to the current [Patch]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * @return The created [IntPatchOption]. + * + * @see IntPatchOption + * @see PatchOption + */ + fun > T.intPatchOption( + key: String, + default: Int? = null, + title: String? = null, + description: String? = null, + required: Boolean = false, + validator: (Int?) -> Boolean = { true } + ) = IntPatchOption(key, default, title, description, required, validator).also { options.register(it) } + } +} diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/LongPatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/LongPatchOption.kt new file mode 100644 index 00000000..563fa217 --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/LongPatchOption.kt @@ -0,0 +1,48 @@ +package app.revanced.patcher.patch.options.types + +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.options.PatchOption + +/** + * A [PatchOption] representing a [Long]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * + * @see PatchOption + */ +class LongPatchOption private constructor( + key: String, + default: Long?, + title: String?, + description: String?, + required: Boolean, + validator: (Long?) -> Boolean +) : PatchOption(key, default, title, description, required, validator) { + companion object { + /** + * Create a new [LongPatchOption] and add it to the current [Patch]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * @return The created [LongPatchOption]. + * + * @see LongPatchOption + * @see PatchOption + */ + fun > T.longPatchOption( + key: String, + default: Long? = null, + title: String? = null, + description: String? = null, + required: Boolean = false, + validator: (Long?) -> Boolean = { true } + ) = LongPatchOption(key, default, title, description, required, validator).also { options.register(it) } + } +} diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/StringPatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/StringPatchOption.kt new file mode 100644 index 00000000..5e2677f2 --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/StringPatchOption.kt @@ -0,0 +1,48 @@ +package app.revanced.patcher.patch.options.types + +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.options.PatchOption + +/** + * A [PatchOption] representing a [String]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * + * @see PatchOption + */ +class StringPatchOption private constructor( + key: String, + default: String?, + title: String?, + description: String?, + required: Boolean, + validator: (String?) -> Boolean +) : PatchOption(key, default, title, description, required, validator) { + companion object { + /** + * Create a new [StringPatchOption] and add it to the current [Patch]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * @return The created [StringPatchOption]. + * + * @see StringPatchOption + * @see PatchOption + */ + fun > T.stringPatchOption( + key: String, + default: String? = null, + title: String? = null, + description: String? = null, + required: Boolean = false, + validator: (String?) -> Boolean = { true } + ) = StringPatchOption(key, default, title, description, required, validator).also { options.register(it) } + } +} diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/BooleanArrayPatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/BooleanArrayPatchOption.kt new file mode 100644 index 00000000..84bd809f --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/BooleanArrayPatchOption.kt @@ -0,0 +1,48 @@ +package app.revanced.patcher.patch.options.types.array + +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.options.PatchOption + +/** + * A [PatchOption] representing a [Boolean] array. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * + * @see PatchOption + */ +class BooleanArrayPatchOption private constructor( + key: String, + default: Array?, + title: String?, + description: String?, + required: Boolean, + validator: (Array?) -> Boolean +) : PatchOption>(key, default, title, description, required, validator) { + companion object { + /** + * Create a new [BooleanArrayPatchOption] and add it to the current [Patch]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * @return The created [BooleanArrayPatchOption]. + * + * @see BooleanArrayPatchOption + * @see PatchOption + */ + fun > T.booleanArrayPatchOption( + key: String, + default: Array? = null, + title: String? = null, + description: String? = null, + required: Boolean = false, + validator: (Array?) -> Boolean = { true } + ) = BooleanArrayPatchOption(key, default, title, description, required, validator).also { options.register(it) } + } +} diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/FloatArrayPatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/FloatArrayPatchOption.kt new file mode 100644 index 00000000..c9e829da --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/FloatArrayPatchOption.kt @@ -0,0 +1,48 @@ +package app.revanced.patcher.patch.options.types.array + +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.options.PatchOption + +/** + * A [PatchOption] representing a [Float] array. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * + * @see PatchOption + */ +class FloatArrayPatchOption private constructor( + key: String, + default: Array?, + title: String?, + description: String?, + required: Boolean, + validator: (Array?) -> Boolean +) : PatchOption>(key, default, title, description, required, validator) { + companion object { + /** + * Create a new [FloatArrayPatchOption] and add it to the current [Patch]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * @return The created [FloatArrayPatchOption]. + * + * @see FloatArrayPatchOption + * @see PatchOption + */ + fun > T.floatArrayPatchOption( + key: String, + default: Array? = null, + title: String? = null, + description: String? = null, + required: Boolean = false, + validator: (Array?) -> Boolean = { true } + ) = FloatArrayPatchOption(key, default, title, description, required, validator).also { options.register(it) } + } +} diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/IntArrayPatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/IntArrayPatchOption.kt new file mode 100644 index 00000000..28211cf3 --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/IntArrayPatchOption.kt @@ -0,0 +1,48 @@ +package app.revanced.patcher.patch.options.types.array + +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.options.PatchOption + +/** + * A [PatchOption] representing an [Integer] array. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * + * @see PatchOption + */ +class IntArrayPatchOption private constructor( + key: String, + default: Array?, + title: String?, + description: String?, + required: Boolean, + validator: (Array?) -> Boolean +) : PatchOption>(key, default, title, description, required, validator) { + companion object { + /** + * Create a new [IntArrayPatchOption] and add it to the current [Patch]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * @return The created [IntArrayPatchOption]. + * + * @see IntArrayPatchOption + * @see PatchOption + */ + fun > T.intArrayPatchOption( + key: String, + default: Array? = null, + title: String? = null, + description: String? = null, + required: Boolean = false, + validator: (Array?) -> Boolean = { true } + ) = IntArrayPatchOption(key, default, title, description, required, validator).also { options.register(it) } + } +} diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/LongArrayPatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/LongArrayPatchOption.kt new file mode 100644 index 00000000..babb6907 --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/LongArrayPatchOption.kt @@ -0,0 +1,48 @@ +package app.revanced.patcher.patch.options.types.array + +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.options.PatchOption + +/** + * A [PatchOption] representing a [Long] array. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * + * @see PatchOption + */ +class LongArrayPatchOption private constructor( + key: String, + default: Array?, + title: String?, + description: String?, + required: Boolean, + validator: (Array?) -> Boolean +) : PatchOption>(key, default, title, description, required, validator) { + companion object { + /** + * Create a new [LongArrayPatchOption] and add it to the current [Patch]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * @return The created [LongArrayPatchOption]. + * + * @see LongArrayPatchOption + * @see PatchOption + */ + fun > T.longArrayPatchOption( + key: String, + default: Array? = null, + title: String? = null, + description: String? = null, + required: Boolean = false, + validator: (Array?) -> Boolean = { true } + ) = LongArrayPatchOption(key, default, title, description, required, validator).also { options.register(it) } + } +} diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/StringArrayPatchOption.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/StringArrayPatchOption.kt new file mode 100644 index 00000000..a73053c8 --- /dev/null +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/options/types/array/StringArrayPatchOption.kt @@ -0,0 +1,48 @@ +package app.revanced.patcher.patch.options.types.array + +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.options.PatchOption + +/** + * A [PatchOption] representing a [String] array. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * + * @see PatchOption + */ +class StringArrayPatchOption private constructor( + key: String, + default: Array?, + title: String?, + description: String?, + required: Boolean, + validator: (Array?) -> Boolean +) : PatchOption>(key, default, title, description, required, validator) { + companion object { + /** + * Create a new [StringArrayPatchOption] and add it to the current [Patch]. + * + * @param key The identifier. + * @param default The default value. + * @param title The title. + * @param description A description. + * @param required Whether the option is required. + * @return The created [StringArrayPatchOption]. + * + * @see StringArrayPatchOption + * @see PatchOption + */ + fun > T.stringArrayPatchOption( + key: String, + default: Array? = null, + title: String? = null, + description: String? = null, + required: Boolean = false, + validator: (Array?) -> Boolean = { true } + ) = StringArrayPatchOption(key, default, title, description, required, validator).also { options.register(it) } + } +} diff --git a/revanced-patcher/src/test/kotlin/app/revanced/patcher/issues/Issue98.kt b/revanced-patcher/src/test/kotlin/app/revanced/patcher/issues/Issue98.kt deleted file mode 100644 index 9e095b5f..00000000 --- a/revanced-patcher/src/test/kotlin/app/revanced/patcher/issues/Issue98.kt +++ /dev/null @@ -1,18 +0,0 @@ -package app.revanced.patcher.issues - -import app.revanced.patcher.patch.PatchOption -import org.junit.jupiter.api.Test -import kotlin.test.assertNull - -internal class Issue98 { - companion object { - var key1: String? by PatchOption.StringOption( - "key1", null, "title", "description" - ) - } - - @Test - fun `should infer nullable type correctly`() { - assertNull(key1) - } -} \ No newline at end of file diff --git a/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt b/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt deleted file mode 100644 index 778a3f4a..00000000 --- a/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -package app.revanced.patcher.patch - -import app.revanced.patcher.usage.ExampleBytecodePatch -import org.junit.jupiter.api.assertThrows -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals -import kotlin.test.assertNotNull - -internal class PatchOptionsTest { - private val options = ExampleBytecodePatch.options - - @Test - fun `should not throw an exception`() { - for (option in options) { - when (option) { - is PatchOption.StringOption -> { - option.value = "Hello World" - } - - is PatchOption.BooleanOption -> { - option.value = false - } - - is PatchOption.StringListOption -> { - option.value = option.options.first() - for (choice in option.options) { - assertNotNull(choice) - } - } - - is PatchOption.IntListOption -> { - option.value = option.options.first() - for (choice in option.options) { - assertNotNull(choice) - } - } - } - } - val option = options.get("key1") - // or: val option: String? by options["key1"] - // then you won't need `.value` every time - assertEquals("Hello World", option.value) - options["key1"] = "Hello, world!" - assertEquals("Hello, world!", option.value) - } - - @Test - fun `should return a different value when changed`() { - var value: String? by options["key1"] - val current = value + "" // force a copy - value = "Hello, world!" - assertNotEquals(current, value) - } - - @Test - fun `should be able to set value to null`() { - // Sadly, doing: - // > options["key2"] = null - // is not possible because Kotlin - // cannot reify the type "Nothing?". - // So we have to do this instead: - options["key2"] = null as Any? - // This is a cleaner replacement for the above: - options.nullify("key2") - } - - @Test - fun `should fail because the option does not exist`() { - assertThrows { - options["this option does not exist"] = 123 - } - } - - @Test - fun `should fail because of invalid value type when setting an option`() { - assertThrows { - options["key1"] = 123 - } - } - - @Test - fun `should fail because of invalid value type when getting an option`() { - assertThrows { - options.get("key1") - } - } - - @Test - fun `should fail because of an illegal value`() { - assertThrows { - options["key3"] = "this value is not an allowed option" - } - } - - @Test - fun `should fail because the requirement is not met`() { - assertThrows { - options.nullify("key1") - } - } - - @Test - fun `should fail because getting a non-initialized option is illegal`() { - assertThrows { - options["key5"].value - } - } -} \ No newline at end of file diff --git a/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/options/PatchOptionsTest.kt b/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/options/PatchOptionsTest.kt new file mode 100644 index 00000000..95538d1f --- /dev/null +++ b/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/options/PatchOptionsTest.kt @@ -0,0 +1,59 @@ +package app.revanced.patcher.patch.options + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.options.types.BooleanPatchOption.Companion.booleanPatchOption +import app.revanced.patcher.patch.options.types.StringPatchOption.Companion.stringPatchOption +import app.revanced.patcher.patch.options.types.array.StringArrayPatchOption.Companion.stringArrayPatchOption +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import kotlin.test.Test + +internal class PatchOptionsTest { + @Test + fun `should not fail because default value is unvalidated`() { + assertDoesNotThrow { + OptionsTestPatch.options["required"].value + } + } + + @Test + fun `should throw due to incorrect type`() { + assertThrows { + OptionsTestPatch.options["bool"] = 0 + } + } + + @Test + fun `should be nullable`() { + OptionsTestPatch.options["bool"] = null + } + + @Test + fun `option should not be found`() { + assertThrows { + OptionsTestPatch.options["this option does not exist"] = 1 + } + } + + @Test + fun `should be able to add options manually`() { + assertThrows { + OptionsTestPatch.options["array"] = OptionsTestPatch.stringArrayOption + } + assertDoesNotThrow { + OptionsTestPatch.options.register(OptionsTestPatch.stringArrayOption) + } + } + + private object OptionsTestPatch : BytecodePatch() { + private var stringOption by stringPatchOption("string", "default") + private var booleanOption by booleanPatchOption("bool", true) + private var requiredStringOption by stringPatchOption("required", "default", required = true) + private var nullDefaultRequiredOption by stringPatchOption("null", null, required = true) + + val stringArrayOption = stringArrayPatchOption("array", arrayOf("1", "2")) + + override fun execute(context: BytecodeContext) {} + } +} \ No newline at end of file diff --git a/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/usage/ExampleBytecodePatch.kt b/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/usage/ExampleBytecodePatch.kt new file mode 100644 index 00000000..9083674c --- /dev/null +++ b/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/usage/ExampleBytecodePatch.kt @@ -0,0 +1,146 @@ +package app.revanced.patcher.patch.usage + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction +import app.revanced.patcher.extensions.or +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchException +import app.revanced.patcher.patch.annotations.CompatiblePackage +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Format +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction11x +import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c +import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c +import com.android.tools.smali.dexlib2.immutable.ImmutableField +import com.android.tools.smali.dexlib2.immutable.ImmutableMethod +import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation +import com.android.tools.smali.dexlib2.immutable.reference.ImmutableFieldReference +import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference +import com.android.tools.smali.dexlib2.immutable.value.ImmutableFieldEncodedValue +import com.android.tools.smali.dexlib2.util.Preconditions +import com.google.common.collect.ImmutableList + +@Suppress("unused") +@Patch( + name = "Example bytecode patch", + description = "Example demonstration of a bytecode patch.", + dependencies = [ExampleResourcePatch::class], + compatiblePackages = [CompatiblePackage("com.example.examplePackage", arrayOf("0.0.1", "0.0.2"))] +) +object ExampleBytecodePatch : BytecodePatch(setOf(ExampleFingerprint)) { + // Entry point of a patch. Supplied fingerprints are resolved at this point. + override fun execute(context: BytecodeContext) { + ExampleFingerprint.result?.let { result -> + // Let's modify it, so it prints "Hello, ReVanced! Editing bytecode." + // Get the start index of our opcode pattern. + // This will be the index of the instruction with the opcode CONST_STRING. + val startIndex = result.scanResult.patternScanResult!!.startIndex + + result.mutableMethod.apply { + replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.") + + // Store the fields initial value into the first virtual register. + replaceInstruction(0, "sget-object v0, LTestClass;->dummyField:Ljava/io/PrintStream;") + + // Now let's create a new call to our method and print the return value! + // You can also use the smali compiler to create instructions. + // For this sake of example I reuse the TestClass field dummyField inside the virtual register 0. + // + // Control flow instructions are not supported as of now. + addInstructionsWithLabels( + startIndex + 2, + """ + invoke-static { }, LTestClass;->returnHello()Ljava/lang/String; + move-result-object v1 + invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V + """ + ) + } + + // Find the class in which the method matching our fingerprint is defined in. + context.findClass(result.classDef.type)!!.mutableClass.apply { + // Add a new method that returns a string. + methods.add( + ImmutableMethod( + result.classDef.type, + "returnHello", + null, + "Ljava/lang/String;", + AccessFlags.PRIVATE or AccessFlags.STATIC, + null, + null, + ImmutableMethodImplementation( + 1, + ImmutableList.of( + BuilderInstruction21c( + Opcode.CONST_STRING, + 0, + ImmutableStringReference("Hello, ReVanced! Adding bytecode.") + ), + BuilderInstruction11x(Opcode.RETURN_OBJECT, 0) + ), + null, + null + ) + ).toMutable() + ) + + // Add a field in the main class. + // We will use this field in our method below to call println on. + // The field holds the Ljava/io/PrintStream->out; field. + fields.add( + ImmutableField( + type, + "dummyField", + "Ljava/io/PrintStream;", + AccessFlags.PRIVATE or AccessFlags.STATIC, + ImmutableFieldEncodedValue( + ImmutableFieldReference( + "Ljava/lang/System;", + "out", + "Ljava/io/PrintStream;" + ) + ), + null, + null + ).toMutable() + ) + } + } ?: throw PatchException("Fingerprint failed to resolve.") + } + + /** + * Replace an existing instruction with a new one containing a reference to a new string. + * @param index The index of the instruction to replace. + * @param string The replacement string. + */ + private fun MutableMethod.replaceStringAt(index: Int, string: String) { + val instruction = getInstruction(index) + + // Utility method of dexlib2. + Preconditions.checkFormat(instruction.opcode, Format.Format21c) + + // Cast this to an instruction of the format 21c. + // The instruction format can be found in the docs at + // https://source.android.com/devices/tech/dalvik/dalvik-bytecode + val strInstruction = instruction as Instruction21c + + // In our case we want an instruction with the opcode CONST_STRING + // The format is 21c, so we create a new BuilderInstruction21c + // This instruction will hold the string reference constant in the virtual register of the original instruction + // For that a reference to the string is needed. It can be created with an ImmutableStringReference. + // At last, use the method replaceInstruction to replace it at the given index startIndex. + replaceInstruction( + index, + "const-string ${strInstruction.registerA}, ${ImmutableStringReference(string)}" + ) + } +} + diff --git a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleFingerprint.kt b/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/usage/ExampleFingerprint.kt similarity index 82% rename from revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleFingerprint.kt rename to revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/usage/ExampleFingerprint.kt index 4ec21e0c..0958aef3 100644 --- a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleFingerprint.kt +++ b/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/usage/ExampleFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patcher.usage +package app.revanced.patcher.patch.usage import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint @@ -12,8 +12,8 @@ object ExampleFingerprint : MethodFingerprint( listOf("[L"), listOf( Opcode.SGET_OBJECT, - null, // Testing unknown opcodes. - Opcode.INVOKE_STATIC, // This is intentionally wrong to test the Fuzzy resolver. + null, // Matching unknown opcodes. + Opcode.INVOKE_STATIC, // This is intentionally wrong to test fuzzy matching. Opcode.RETURN_VOID ), null diff --git a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleResourcePatch.kt b/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/usage/ExampleResourcePatch.kt similarity index 93% rename from revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleResourcePatch.kt rename to revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/usage/ExampleResourcePatch.kt index 3314e04d..f7a99819 100644 --- a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleResourcePatch.kt +++ b/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/usage/ExampleResourcePatch.kt @@ -1,4 +1,4 @@ -package app.revanced.patcher.usage +package app.revanced.patcher.patch.usage import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.patch.ResourcePatch diff --git a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt b/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt deleted file mode 100644 index e0f66c53..00000000 --- a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt +++ /dev/null @@ -1,193 +0,0 @@ -package app.revanced.patcher.usage - -import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels -import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction -import app.revanced.patcher.extensions.or -import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.PatchOption -import app.revanced.patcher.patch.annotations.CompatiblePackage -import app.revanced.patcher.patch.annotations.Patch -import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable -import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable -import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Format -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation -import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction11x -import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c -import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c -import com.android.tools.smali.dexlib2.immutable.ImmutableField -import com.android.tools.smali.dexlib2.immutable.ImmutableMethod -import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation -import com.android.tools.smali.dexlib2.immutable.reference.ImmutableFieldReference -import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference -import com.android.tools.smali.dexlib2.immutable.value.ImmutableFieldEncodedValue -import com.android.tools.smali.dexlib2.util.Preconditions -import com.google.common.collect.ImmutableList -import kotlin.test.assertNotNull - -@Suppress("unused") -@Patch( - name = "Example bytecode patch", - description = "Example demonstration of a bytecode patch.", - dependencies = [ExampleResourcePatch::class], - compatiblePackages = [CompatiblePackage("com.example.examplePackage", arrayOf("0.0.1", "0.0.2"))] -) -object ExampleBytecodePatch : BytecodePatch( - setOf(ExampleFingerprint) -) { - - // This function will be executed by the patcher. - // You can treat it as a constructor - override fun execute(context: BytecodeContext) { - // Get the resolved method by its fingerprint from the resolver cache - val result = ExampleFingerprint.result!! - - // Patch options - assertNotNull(key1) - key2 = false - - // Get the implementation for the resolved method - val method = result.mutableMethod - val implementation = method.implementation!! - - // Let's modify it, so it prints "Hello, ReVanced! Editing bytecode." - // Get the start index of our opcode pattern. - // This will be the index of the instruction with the opcode CONST_STRING. - val startIndex = result.scanResult.patternScanResult!!.startIndex - - implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.") - - // Get the class in which the method matching our fingerprint is defined in. - val mainClass = context.findClass { - it.type == result.classDef.type - }!!.mutableClass - - // Add a new method returning a string - mainClass.methods.add( - ImmutableMethod( - result.classDef.type, - "returnHello", - null, - "Ljava/lang/String;", - AccessFlags.PRIVATE or AccessFlags.STATIC, - null, - null, - ImmutableMethodImplementation( - 1, - ImmutableList.of( - BuilderInstruction21c( - Opcode.CONST_STRING, - 0, - ImmutableStringReference("Hello, ReVanced! Adding bytecode.") - ), - BuilderInstruction11x(Opcode.RETURN_OBJECT, 0) - ), - null, - null - ) - ).toMutable() - ) - - // Add a field in the main class - // We will use this field in our method below to call println on - // The field holds the Ljava/io/PrintStream->out; field - mainClass.fields.add( - ImmutableField( - mainClass.type, - "dummyField", - "Ljava/io/PrintStream;", - AccessFlags.PRIVATE or AccessFlags.STATIC, - ImmutableFieldEncodedValue( - ImmutableFieldReference( - "Ljava/lang/System;", - "out", - "Ljava/io/PrintStream;" - ) - ), - null, - null - ).toMutable() - ) - - // store the fields initial value into the first virtual register - method.replaceInstruction(0, "sget-object v0, LTestClass;->dummyField:Ljava/io/PrintStream;") - - // Now let's create a new call to our method and print the return value! - // You can also use the smali compiler to create instructions. - // For this sake of example I reuse the TestClass field dummyField inside the virtual register 0. - // - // Control flow instructions are not supported as of now. - method.addInstructionsWithLabels( - startIndex + 2, - """ - invoke-static { }, LTestClass;->returnHello()Ljava/lang/String; - move-result-object v1 - invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V - """ - ) - } - - /** - * Replace the string for an instruction at the given index with a new one. - * @param index The index of the instruction to replace the string for - * @param string The replacing string - */ - private fun MutableMethodImplementation.replaceStringAt(index: Int, string: String) { - val instruction = this.instructions[index] - - // Utility method of dexlib2 - Preconditions.checkFormat(instruction.opcode, Format.Format21c) - - // Cast this to an instruction of the format 21c - // The instruction format can be found in the docs at - // https://source.android.com/devices/tech/dalvik/dalvik-bytecode - val strInstruction = instruction as Instruction21c - - // In our case we want an instruction with the opcode CONST_STRING - // The format is 21c, so we create a new BuilderInstruction21c - // This instruction will hold the string reference constant in the virtual register of the original instruction - // For that a reference to the string is needed. It can be created with an ImmutableStringReference. - // At last, use the method replaceInstruction to replace it at the given index startIndex. - this.replaceInstruction( - index, - BuilderInstruction21c( - Opcode.CONST_STRING, - strInstruction.registerA, - ImmutableStringReference(string) - ) - ) - } - - private var key1 by option( - PatchOption.StringOption( - "key1", "default", "title", "description", true - ) - ) - - private var key2 by option( - PatchOption.BooleanOption( - "key2", true, "title", "description" // required defaults to false - ) - ) - - private var key3 by option( - PatchOption.StringListOption( - "key3", "TEST", listOf("TEST", "TEST1", "TEST2"), "title", "description" - ) - ) - - private var key4 by option( - PatchOption.IntListOption( - "key4", 1, listOf(1, 2, 3), "title", "description" - ) - ) - - private var key5 by option( - PatchOption.StringOption( - "key5", null, "title", "description", true - ) - ) -} -