refactor: Improve Patch Options

It's so much better now. Really happy with the current system.

BREAKING CHANGE: Options has been moved from Patch to a new interface called OptionsContainer and are now handled entirely different. Make sure to check the examples to understand how it works.
This commit is contained in:
Sculas 2022-09-07 20:55:35 +02:00
parent 0e8446516e
commit 6b909c1ee6
No known key found for this signature in database
GPG Key ID: 1530BFF96D1EEB89
5 changed files with 64 additions and 27 deletions

View File

@ -6,8 +6,10 @@ import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.Data import app.revanced.patcher.data.Data
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.patch.OptionsContainer
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.full.companionObjectInstance
/** /**
* Recursively find a given annotation on a class. * Recursively find a given annotation on a class.
@ -43,6 +45,9 @@ object PatchExtensions {
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.DependsOn::class)?.dependencies val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.DependsOn::class)?.dependencies
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
val Class<out Patch<Data>>.options get() = kotlin.companionObjectInstance?.let {
(it as? OptionsContainer)?.options
}
@JvmStatic @JvmStatic
fun Class<out Patch<Data>>.dependsOn(patch: Class<out Patch<Data>>): Boolean { fun Class<out Patch<Data>>.dependsOn(patch: Class<out Patch<Data>>): Boolean {

View File

@ -17,9 +17,18 @@ abstract class Patch<out T : Data> {
* The main function of the [Patch] which the patcher will call. * The main function of the [Patch] which the patcher will call.
*/ */
abstract fun execute(data: @UnsafeVariance T): PatchResult abstract fun execute(data: @UnsafeVariance T): PatchResult
}
abstract class OptionsContainer {
/** /**
* A list of [PatchOption]s. * 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
}
} }

View File

@ -18,13 +18,17 @@ object RequirementNotMetException : Exception("null was passed into an option th
* @param options An array of [PatchOption]s. * @param options An array of [PatchOption]s.
*/ */
class PatchOptions(vararg val options: PatchOption<*>) : Iterable<PatchOption<*>> { class PatchOptions(vararg val options: PatchOption<*>) : Iterable<PatchOption<*>> {
private val register = buildMap { private val register = mutableMapOf<String, PatchOption<*>>()
for (option in options) {
if (containsKey(option.key)) { init {
options.forEach { register(it) }
}
internal fun register(option: PatchOption<*>) {
if (register.containsKey(option.key)) {
throw IllegalStateException("Multiple options found with the same key") throw IllegalStateException("Multiple options found with the same key")
} }
put(option.key, option) register[option.key] = option
}
} }
/** /**
@ -91,7 +95,7 @@ sealed class PatchOption<T>(
* Gets the value of the option. * Gets the value of the option.
* Please note that using the wrong value type results in a runtime error. * Please note that using the wrong value type results in a runtime error.
*/ */
operator fun <T> getValue(thisRef: Nothing?, property: KProperty<*>) = value as T operator fun <T> getValue(thisRef: Any?, property: KProperty<*>) = value as T
/** /**
* Gets the value of the option. * Gets the value of the option.

View File

@ -7,7 +7,7 @@ import java.io.File
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
internal class PatchOptionsTest { internal class PatchOptionsTest {
private val options = ExampleBytecodePatch().options private val options = ExampleBytecodePatch.options
@Test @Test
fun `should not throw an exception`() { fun `should not throw an exception`() {
@ -16,21 +16,25 @@ internal class PatchOptionsTest {
is PatchOption.StringOption -> { is PatchOption.StringOption -> {
option.value = "Hello World" option.value = "Hello World"
} }
is PatchOption.BooleanOption -> { is PatchOption.BooleanOption -> {
option.value = false option.value = false
} }
is PatchOption.StringListOption -> { is PatchOption.StringListOption -> {
option.value = option.options.first() option.value = option.options.first()
for (choice in option.options) { for (choice in option.options) {
println(choice) println(choice)
} }
} }
is PatchOption.IntListOption -> { is PatchOption.IntListOption -> {
option.value = option.options.first() option.value = option.options.first()
for (choice in option.options) { for (choice in option.options) {
println(choice) println(choice)
} }
} }
is PatchOption.PathOption -> { is PatchOption.PathOption -> {
option.value = File("test.txt").toPath() option.value = File("test.txt").toPath()
} }

View File

@ -7,8 +7,8 @@ import app.revanced.patcher.data.impl.BytecodeData
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.extensions.replaceInstruction import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.patch.OptionsContainer
import app.revanced.patcher.patch.PatchOption import app.revanced.patcher.patch.PatchOption
import app.revanced.patcher.patch.PatchOptions
import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.DependsOn 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.immutable.value.ImmutableFieldEncodedValue
import org.jf.dexlib2.util.Preconditions import org.jf.dexlib2.util.Preconditions
import java.io.File import java.io.File
import java.nio.file.Path
@Patch @Patch
@Name("example-bytecode-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 // Get the resolved method by its fingerprint from the resolver cache
val result = ExampleFingerprint.result!! val result = ExampleFingerprint.result!!
// Patch options
println(key1)
key2 = false
// Get the implementation for the resolved method // Get the implementation for the resolved method
val method = result.mutableMethod val method = result.mutableMethod
val implementation = method.implementation!! val implementation = method.implementation!!
@ -165,21 +170,31 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
) )
} }
override val options = PatchOptions( companion object : OptionsContainer() {
private var key1: String by option(
PatchOption.StringOption( PatchOption.StringOption(
"key1", "default", "title", "description", true "key1", "default", "title", "description", true
), )
)
private var key2: Boolean by option(
PatchOption.BooleanOption( PatchOption.BooleanOption(
"key2", true, "title", "description" // required defaults to false "key2", true, "title", "description" // required defaults to false
), )
)
private var key3: List<String> by option(
PatchOption.StringListOption( PatchOption.StringListOption(
"key3", "TEST", listOf("TEST", "TEST1", "TEST2"), "title", "description" "key3", "TEST", listOf("TEST", "TEST1", "TEST2"), "title", "description"
), )
)
private var key4: List<Int> by option(
PatchOption.IntListOption( PatchOption.IntListOption(
"key4", 1, listOf(1, 2, 3), "title", "description" "key4", 1, listOf(1, 2, 3), "title", "description"
), )
)
private var key5: Path by option(
PatchOption.PathOption( PatchOption.PathOption(
"key5", File("test.txt").toPath(), "title", "description" "key5", File("test.txt").toPath(), "title", "description"
),
) )
)
}
} }