diff --git a/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt index df7f8f5b..cf5ecf4c 100644 --- a/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt +++ b/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt @@ -6,8 +6,10 @@ import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Version import app.revanced.patcher.data.Data import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.patch.OptionsContainer import app.revanced.patcher.patch.Patch import kotlin.reflect.KClass +import kotlin.reflect.full.companionObjectInstance /** * Recursively find a given annotation on a class. @@ -43,6 +45,9 @@ object PatchExtensions { val Class>.description get() = recursiveAnnotation(Description::class)?.description val Class>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.DependsOn::class)?.dependencies val Class>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages + val Class>.options get() = kotlin.companionObjectInstance?.let { + (it as? OptionsContainer)?.options + } @JvmStatic fun Class>.dependsOn(patch: Class>): Boolean { diff --git a/src/main/kotlin/app/revanced/patcher/patch/Patch.kt b/src/main/kotlin/app/revanced/patcher/patch/Patch.kt index 264cdaab..6daf1cf5 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/Patch.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/Patch.kt @@ -17,9 +17,18 @@ abstract class Patch { * The main function of the [Patch] which the patcher will call. */ abstract fun execute(data: @UnsafeVariance T): PatchResult +} +abstract class OptionsContainer { /** * A list of [PatchOption]s. + * @see PatchOptions */ - open val options = PatchOptions() + @Suppress("MemberVisibilityCanBePrivate") + val options = PatchOptions() + + protected fun option(opt: PatchOption<*>): PatchOption<*> { + options.register(opt) + return opt + } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt b/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt index 3f46829b..239f1ffa 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt @@ -18,13 +18,17 @@ object RequirementNotMetException : Exception("null was passed into an option th * @param options An array of [PatchOption]s. */ class PatchOptions(vararg val options: PatchOption<*>) : Iterable> { - private val register = buildMap { - for (option in options) { - if (containsKey(option.key)) { - throw IllegalStateException("Multiple options found with the same key") - } - put(option.key, option) + 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 } /** @@ -91,7 +95,7 @@ sealed class PatchOption( * Gets the value of the option. * Please note that using the wrong value type results in a runtime error. */ - operator fun getValue(thisRef: Nothing?, property: KProperty<*>) = value as T + operator fun getValue(thisRef: Any?, property: KProperty<*>) = value as T /** * Gets the value of the option. diff --git a/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt b/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt index d983abcc..796d31bd 100644 --- a/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt +++ b/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt @@ -7,7 +7,7 @@ import java.io.File import kotlin.test.assertNotEquals internal class PatchOptionsTest { - private val options = ExampleBytecodePatch().options + private val options = ExampleBytecodePatch.options @Test fun `should not throw an exception`() { @@ -16,21 +16,25 @@ internal class PatchOptionsTest { 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) { println(choice) } } + is PatchOption.IntListOption -> { option.value = option.options.first() for (choice in option.options) { println(choice) } } + is PatchOption.PathOption -> { option.value = File("test.txt").toPath() } diff --git a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt index 7661032b..466818ad 100644 --- a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt +++ b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt @@ -7,8 +7,8 @@ import app.revanced.patcher.data.impl.BytecodeData import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.patch.OptionsContainer import app.revanced.patcher.patch.PatchOption -import app.revanced.patcher.patch.PatchOptions import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResultSuccess import app.revanced.patcher.patch.annotations.DependsOn @@ -33,6 +33,7 @@ import org.jf.dexlib2.immutable.reference.ImmutableStringReference import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue import org.jf.dexlib2.util.Preconditions import java.io.File +import java.nio.file.Path @Patch @Name("example-bytecode-patch") @@ -47,6 +48,10 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { // Get the resolved method by its fingerprint from the resolver cache val result = ExampleFingerprint.result!! + // Patch options + println(key1) + key2 = false + // Get the implementation for the resolved method val method = result.mutableMethod val implementation = method.implementation!! @@ -165,21 +170,31 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { ) } - override val options = PatchOptions( - PatchOption.StringOption( - "key1", "default", "title", "description", true - ), - PatchOption.BooleanOption( - "key2", true, "title", "description" // required defaults to false - ), - PatchOption.StringListOption( - "key3", "TEST", listOf("TEST", "TEST1", "TEST2"), "title", "description" - ), - PatchOption.IntListOption( - "key4", 1, listOf(1, 2, 3), "title", "description" - ), - PatchOption.PathOption( - "key5", File("test.txt").toPath(), "title", "description" - ), - ) + companion object : OptionsContainer() { + private var key1: String by option( + PatchOption.StringOption( + "key1", "default", "title", "description", true + ) + ) + private var key2: Boolean by option( + PatchOption.BooleanOption( + "key2", true, "title", "description" // required defaults to false + ) + ) + private var key3: List by option( + PatchOption.StringListOption( + "key3", "TEST", listOf("TEST", "TEST1", "TEST2"), "title", "description" + ) + ) + private var key4: List by option( + PatchOption.IntListOption( + "key4", 1, listOf(1, 2, 3), "title", "description" + ) + ) + private var key5: Path by option( + PatchOption.PathOption( + "key5", File("test.txt").toPath(), "title", "description" + ) + ) + } }