diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4b214cbc..512c2964 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,8 +6,12 @@ kotlin-test = "1.8.20-RC" kotlinx-coroutines-core = "1.7.1" multidexlib2 = "3.0.3.r2" smali = "3.0.3" +symbol-processing-api = "1.9.0-1.0.11" xpp3 = "1.1.4c" binary-compatibility-validator = "0.13.2" +kotlin-compile-testing-ksp = "1.5.0" +kotlinpoet-ksp = "1.14.2" +ksp = "1.9.0-1.0.11" [libraries] android = { module = "com.google.android:android", version.ref = "android" } @@ -17,7 +21,11 @@ kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotl kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" } multidexlib2 = { module = "app.revanced:multidexlib2", version.ref = "multidexlib2" } smali = { module = "com.android.tools.smali:smali", version.ref = "smali" } +symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "symbol-processing-api" } xpp3 = { module = "xpp3:xpp3", version.ref = "xpp3" } +kotlin-compile-testing = { module = "com.github.tschuchortdev:kotlin-compile-testing-ksp", version.ref = "kotlin-compile-testing-ksp" } +kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinpoet-ksp" } [plugins] binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } \ No newline at end of file diff --git a/revanced-patch-annotations-processor/api/revanced-patch-annotations-processor.api b/revanced-patch-annotations-processor/api/revanced-patch-annotations-processor.api new file mode 100644 index 00000000..87bc05c8 --- /dev/null +++ b/revanced-patch-annotations-processor/api/revanced-patch-annotations-processor.api @@ -0,0 +1,25 @@ +public abstract interface annotation class app/revanced/patcher/patch/annotations/CompatiblePackage : java/lang/annotation/Annotation { + public abstract fun name ()Ljava/lang/String; + public abstract fun versions ()[Ljava/lang/String; +} + +public abstract interface annotation class app/revanced/patcher/patch/annotations/Patch : java/lang/annotation/Annotation { + public abstract fun compatiblePackages ()[Lapp/revanced/patcher/patch/annotations/CompatiblePackage; + public abstract fun dependencies ()[Ljava/lang/Class; + public abstract fun description ()Ljava/lang/String; + public abstract fun name ()Ljava/lang/String; + public abstract fun requiresIntegrations ()Z + public abstract fun use ()Z +} + +public final class app/revanced/patcher/patch/annotations/processor/PatchProcessor : com/google/devtools/ksp/processing/SymbolProcessor { + public fun (Lcom/google/devtools/ksp/processing/CodeGenerator;Lcom/google/devtools/ksp/processing/KSPLogger;)V + public fun process (Lcom/google/devtools/ksp/processing/Resolver;)Ljava/util/List; +} + +public final class app/revanced/patcher/patch/annotations/processor/PatchProcessorProvider : com/google/devtools/ksp/processing/SymbolProcessorProvider { + public fun ()V + public fun create (Lcom/google/devtools/ksp/processing/SymbolProcessorEnvironment;)Lapp/revanced/patcher/patch/annotations/processor/PatchProcessor; + public synthetic fun create (Lcom/google/devtools/ksp/processing/SymbolProcessorEnvironment;)Lcom/google/devtools/ksp/processing/SymbolProcessor; +} + diff --git a/revanced-patch-annotations-processor/build.gradle.kts b/revanced-patch-annotations-processor/build.gradle.kts new file mode 100644 index 00000000..7b0f0bea --- /dev/null +++ b/revanced-patch-annotations-processor/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + kotlin("jvm") version "1.9.0" + `maven-publish` + alias(libs.plugins.ksp) +} + +group = "app.revanced" + +dependencies { + implementation(libs.symbol.processing.api) + implementation(libs.kotlinpoet.ksp) + implementation(project(":revanced-patcher")) + + testImplementation(libs.kotlin.test) + testImplementation(libs.kotlin.compile.testing) +} + +tasks { + test { + useJUnitPlatform() + testLogging { + events("PASSED", "SKIPPED", "FAILED") + } + } +} + +kotlin { jvmToolchain(11) } + +java { + withSourcesJar() +} + +publishing { + repositories { + mavenLocal() + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/revanced/revanced-patch-annotations-processor") + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } + publications { + create("gpr") { + from(components["java"]) + } + } +} \ No newline at end of file diff --git a/revanced-patch-annotations-processor/settings.gradle.kts b/revanced-patch-annotations-processor/settings.gradle.kts new file mode 100644 index 00000000..a376e099 --- /dev/null +++ b/revanced-patch-annotations-processor/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "revanced-patch-annotations-processor" + diff --git a/revanced-patch-annotations-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotations.kt b/revanced-patch-annotations-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotations.kt new file mode 100644 index 00000000..6c28e6ea --- /dev/null +++ b/revanced-patch-annotations-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotations.kt @@ -0,0 +1,38 @@ +package app.revanced.patcher.patch.annotations + +import java.lang.annotation.Inherited +import kotlin.reflect.KClass + +/** + * Annotation for [app.revanced.patcher.patch.Patch] classes. + * + * @param name The name of the patch. If empty, the patch will be unnamed. + * @param description The description of the patch. If empty, no description will be used. + * @param dependencies The patches this patch depends on. + * @param compatiblePackages The packages this patch is compatible with. + * @param use Whether this patch should be used. + * @param requiresIntegrations Whether this patch requires integrations. + */ +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.CLASS) +@Inherited +annotation class Patch( + val name: String = "", + val description: String = "", + val dependencies: Array>> = [], + val compatiblePackages: Array = [], + val use: Boolean = true, + // TODO: Remove this property, once integrations are coupled with patches. + val requiresIntegrations: Boolean = false, +) + +/** + * A package that a [app.revanced.patcher.patch.Patch] is compatible with. + * + * @param name The name of the package. + * @param versions The versions of the package. + */ +annotation class CompatiblePackage( + val name: String, + val versions: Array = [], +) \ No newline at end of file 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 new file mode 100644 index 00000000..f597e8da --- /dev/null +++ b/revanced-patch-annotations-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/processor/PatchProcessor.kt @@ -0,0 +1,198 @@ +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.* +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSType +import com.google.devtools.ksp.validate +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.writeTo +import kotlin.reflect.KClass + +class PatchProcessor( + private val codeGenerator: CodeGenerator, + private val logger: KSPLogger +) : SymbolProcessor { + + private fun KSAnnotated.isSubclassOf(cls: KClass<*>): Boolean { + if (this !is KSClassDeclaration) return false + + if (qualifiedName?.asString() == cls.qualifiedName) return true + + return superTypes.any { it.resolve().declaration.isSubclassOf(cls) } + } + + @Suppress("UNCHECKED_CAST") + override fun process(resolver: Resolver): List { + val executablePatches = buildMap { + resolver.getSymbolsWithAnnotation(Patch::class.qualifiedName!!).filter { + // Do not check here if Patch is super of the class, because it is expensive. + // Check it later when processing. + it.validate() && it.isSubclassOf(app.revanced.patcher.patch.Patch::class) + }.map { + it as KSClassDeclaration + }.forEach { patchDeclaration -> + patchDeclaration.annotations.find { + it.annotationType.resolve().declaration.qualifiedName!!.asString() == Patch::class.qualifiedName!! + }?.let { annotation -> + fun KSAnnotation.property(name: String) = + arguments.find { it.name!!.asString() == name }?.value!! + + val name = + annotation.property("name").toString().ifEmpty { null } + + val description = + annotation.property("description").toString().ifEmpty { null } + + val dependencies = + (annotation.property("dependencies") as List).ifEmpty { null } + + val compatiblePackages = + (annotation.property("compatiblePackages") as List).ifEmpty { null } + + val use = + annotation.property("use") as Boolean + + val requiresIntegrations = + annotation.property("requiresIntegrations") as Boolean + + // Data class for KotlinPoet + data class PatchData( + val name: String?, + val description: String?, + val dependencies: List?, + val compatiblePackages: List?, + val use: Boolean, + val requiresIntegrations: Boolean + ) + + this[patchDeclaration] = PatchData( + name, + description, + dependencies?.map { dependency -> dependency.toClassName() }, + compatiblePackages?.map { + val packageName = it.property("name") + val packageVersions = (it.property("versions") as List) + .joinToString(", ") { version -> "\"$version\"" } + + CodeBlock.of( + "%T(%S, setOf(%L))", + app.revanced.patcher.patch.Patch.CompatiblePackage::class, + packageName, + packageVersions + ) + }, + use, + requiresIntegrations + ) + } + } + } + + // If a patch depends on another, that is annotated, the dependency should be replaced with the generated patch, + // because the generated patch has all the necessary properties to invoke the super constructor, + // unlike the annotated patch. + val dependencyResolutionMap = buildMap { + executablePatches.values.filter { it.dependencies != null }.flatMap { + it.dependencies!! + }.distinct().forEach { dependency -> + executablePatches.keys.find { it.qualifiedName?.asString() == dependency.toString() } + ?.let { patch -> + this[dependency] = ClassName( + patch.packageName.asString(), + patch.simpleName.asString() + "Generated" + ) + } + } + } + + // kotlin poet generate a class for each patch + executablePatches.forEach { (patchDeclaration, patchAnnotation) -> + val isBytecodePatch = patchDeclaration.isSubclassOf(BytecodePatch::class) + + val superClass = if (isBytecodePatch) { + BytecodePatch::class + } else { + ResourcePatch::class + } + + val contextClass = if (isBytecodePatch) { + BytecodeContext::class + } else { + ResourceContext::class + } + + val generatedPatchClassName = ClassName( + patchDeclaration.packageName.asString(), + patchDeclaration.simpleName.asString() + "Generated" + ) + + FileSpec.builder(generatedPatchClassName) + .addType( + TypeSpec.objectBuilder(generatedPatchClassName) + .superclass(superClass).apply { + patchAnnotation.name?.let { name -> + addSuperclassConstructorParameter("name = %S", name) + } + + patchAnnotation.description?.let { description -> + addSuperclassConstructorParameter("description = %S", description) + } + + patchAnnotation.compatiblePackages?.let { compatiblePackages -> + addSuperclassConstructorParameter( + "compatiblePackages = setOf(%L)", + compatiblePackages.joinToString(", ") + ) + } + + patchAnnotation.dependencies?.let { dependencies -> + addSuperclassConstructorParameter( + "dependencies = setOf(%L)", + buildList { + addAll(dependencies) + // Also add the source class of the generated class so that it is also executed + add(patchDeclaration.toClassName()) + }.joinToString(", ") { dependency -> + "${(dependencyResolutionMap[dependency] ?: dependency)}::class" + } + ) + } + addSuperclassConstructorParameter( + "use = %L", patchAnnotation.use + ) + + addSuperclassConstructorParameter( + "requiresIntegrations = %L", + patchAnnotation.requiresIntegrations + ) + } + .addFunction( + FunSpec.builder("execute") + .addModifiers(KModifier.OVERRIDE) + .addParameter("context", contextClass) + .build() + ) + .addProperty( + PropertySpec.builder("options", PatchOptions::class, KModifier.OVERRIDE) + .initializer("%T.options", patchDeclaration.toClassName()) + .build() + ) + .build() + ).build().writeTo( + codeGenerator, + Dependencies(false, patchDeclaration.containingFile!!) + ) + } + + return emptyList() + } +} \ No newline at end of file diff --git a/revanced-patch-annotations-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/processor/PatchProcessorProvider.kt b/revanced-patch-annotations-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/processor/PatchProcessorProvider.kt new file mode 100644 index 00000000..95abc376 --- /dev/null +++ b/revanced-patch-annotations-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/processor/PatchProcessorProvider.kt @@ -0,0 +1,9 @@ +package app.revanced.patcher.patch.annotations.processor + +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class PatchProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment) = + PatchProcessor(environment.codeGenerator, environment.logger) +} \ No newline at end of file diff --git a/revanced-patch-annotations-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/revanced-patch-annotations-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 00000000..0a48684e --- /dev/null +++ b/revanced-patch-annotations-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +app.revanced.patcher.patch.annotations.processor.PatchProcessorProvider \ No newline at end of file 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 new file mode 100644 index 00000000..850f2610 --- /dev/null +++ b/revanced-patch-annotations-processor/src/test/kotlin/app/revanced/patcher/patch/annotations/processor/TestPatchAnnotationProcessor.kt @@ -0,0 +1,131 @@ +package app.revanced.patcher.patch.annotations.processor + +import app.revanced.patcher.patch.Patch +import com.tschuchort.compiletesting.KotlinCompilation +import com.tschuchort.compiletesting.SourceFile +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 { + // region Processing + + @Test + fun testProcessing() = assertEquals( + "Processable patch", compile( + getSourceFile( + "processing", "ProcessablePatch" + ) + ).loadPatch("$SAMPLE_PACKAGE.processing.ProcessablePatchGenerated").name + ) + + // endregion + + // region Dependencies + + @Test + fun testDependencies() { + compile( + getSourceFile( + "dependencies", "DependentPatch" + ), getSourceFile( + "dependencies", "DependencyPatch" + ) + ).let { result -> + result.loadPatch("$SAMPLE_PACKAGE.dependencies.DependentPatchGenerated").let { + // Dependency as well as the source class of the generated class. + assertEquals( + 2, + it.dependencies!!.size + ) + + // The last dependency is always the source class of the generated class to respect + // order of dependencies. + assertEquals( + result.loadPatch("$SAMPLE_PACKAGE.dependencies.DependentPatch")::class, + it.dependencies!!.last() + ) + } + } + } + + // endregion + + // region Options + + @Test + fun testOptions() { + val patch = compile( + getSourceFile( + "options", "OptionsPatch" + ) + ).loadPatch("$SAMPLE_PACKAGE.options.OptionsPatchGenerated") + + assertNotNull(patch.options) + assertEquals(patch.options["print"].title, "Print message") + } + + // endregion + + // region Limitations + @Test + fun failingManualDependency() = assertNull( + compile( + getSourceFile( + "limitations/manualdependency", "DependentPatch" + ), getSourceFile( + "limitations/manualdependency", "DependencyPatch" + ) + ).loadPatch("$SAMPLE_PACKAGE.limitations.manualdependency.DependentPatchGenerated").dependencies + ) + + // endregion + + private companion object Utils { + const val SAMPLE_PACKAGE = "app.revanced.patcher.patch.annotations.processor.samples" + + /** + * Get a source file from the given sample and class name. + * + * @param sample The sample to get the source file from. + * @param className The name of the class to get the source file from. + * @return The source file. + */ + fun getSourceFile(sample: String, className: String) = SourceFile.kotlin( + "$className.kt", TestPatchAnnotationProcessor::class.java.classLoader.getResourceAsStream( + "app/revanced/patcher/patch/annotations/processor/samples/$sample/$className.kt" + )?.readAllBytes()?.toString(Charsets.UTF_8) ?: error("Could not find resource $className") + ) + + /** + * Compile the given source files and return the result. + * + * @param sourceFiles The source files to compile. + * @return The result of the compilation. + */ + fun compile(vararg sourceFiles: SourceFile) = KotlinCompilation().apply { + sources = sourceFiles.asList() + + symbolProcessorProviders = listOf(PatchProcessorProvider()) + + // Required until https://github.com/tschuchortdev/kotlin-compile-testing/issues/312 closed. + kspWithCompilation = true + + inheritClassPath = true + messageOutputStream = System.out + }.compile().also { result -> + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + } + + // region Class loading + + fun KotlinCompilation.Result.loadPatch(name: String) = classLoader.loadClass(name).loadPatch() + + fun Class<*>.loadPatch() = this.getField("INSTANCE").get(null) as Patch<*> + + // endregion + } +} \ No newline at end of file diff --git a/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/dependencies/DependencyPatch.kt b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/dependencies/DependencyPatch.kt new file mode 100644 index 00000000..c03169df --- /dev/null +++ b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/dependencies/DependencyPatch.kt @@ -0,0 +1,10 @@ +package app.revanced.patcher.patch.annotations.processor.samples.dependencies + +import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patcher.patch.annotations.Patch + +@Patch(name = "Dependency patch") +object DependencyPatch : ResourcePatch() { + override fun execute(context: ResourceContext) {} +} \ No newline at end of file diff --git a/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/dependencies/DependentPatch.kt b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/dependencies/DependentPatch.kt new file mode 100644 index 00000000..44d6df08 --- /dev/null +++ b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/dependencies/DependentPatch.kt @@ -0,0 +1,12 @@ +package app.revanced.patcher.patch.annotations.processor.samples.dependencies +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotations.Patch + +@Patch( + name = "Dependent patch", + dependencies = [DependencyPatch::class], +) +object DependentPatch : BytecodePatch() { + override fun execute(context: BytecodeContext) {} +} \ No newline at end of file diff --git a/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/limitations/manualdependency/DependencyPatch.kt b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/limitations/manualdependency/DependencyPatch.kt new file mode 100644 index 00000000..d43bd2c5 --- /dev/null +++ b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/limitations/manualdependency/DependencyPatch.kt @@ -0,0 +1,10 @@ +package app.revanced.patcher.patch.annotations.processor.samples.limitations.manualdependency + +import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patcher.patch.annotations.Patch + +@Patch(name = "Dependency patch") +object DependencyPatch : ResourcePatch() { + override fun execute(context: ResourceContext) { } +} \ No newline at end of file diff --git a/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/limitations/manualdependency/DependentPatch.kt b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/limitations/manualdependency/DependentPatch.kt new file mode 100644 index 00000000..60121ecd --- /dev/null +++ b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/limitations/manualdependency/DependentPatch.kt @@ -0,0 +1,17 @@ +package app.revanced.patcher.patch.annotations.processor.samples.limitations.manualdependency +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotations.Patch + +@Patch(name = "Dependent patch") +object DependentPatch : BytecodePatch( + // Dependency will not be executed correctly if it is manually specified. + // The reason for this is that the dependency patch is annotated too, + // so the processor will generate a new patch class for it embedding the annotated information. + // Because the dependency is manually specified, + // the processor will not be able to change this dependency to the generated class, + // which means that the dependency will lose the annotated information. + dependencies = setOf(DependencyPatch::class) +) { + override fun execute(context: BytecodeContext) {} +} \ No newline at end of file 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 new file mode 100644 index 00000000..4aaf93e3 --- /dev/null +++ b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/options/OptionsPatch.kt @@ -0,0 +1,21 @@ +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 + +@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." + ) + ) +} \ No newline at end of file diff --git a/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/processing/ProcessablePatch.kt b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/processing/ProcessablePatch.kt new file mode 100644 index 00000000..28d04a0d --- /dev/null +++ b/revanced-patch-annotations-processor/src/test/resources/app/revanced/patcher/patch/annotations/processor/samples/processing/ProcessablePatch.kt @@ -0,0 +1,10 @@ +package app.revanced.patcher.patch.annotations.processor.samples.processing + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotations.Patch + +@Patch("Processable patch") +object ProcessablePatch : BytecodePatch() { + override fun execute(context: BytecodeContext) {} +} \ No newline at end of file diff --git a/revanced-patcher/api/revanced-patcher.api b/revanced-patcher/api/revanced-patcher.api index 160d13b9..8987c4ec 100644 --- a/revanced-patcher/api/revanced-patcher.api +++ b/revanced-patcher/api/revanced-patcher.api @@ -327,7 +327,9 @@ public final class app/revanced/patcher/logging/impl/NopLogger : app/revanced/pa } public abstract class app/revanced/patcher/patch/BytecodePatch : app/revanced/patcher/patch/Patch { - public fun (Lapp/revanced/patcher/patch/Patch$Manifest;[Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprint;)V + public fun ()V + public fun (Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZ)V + 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 { @@ -348,38 +350,29 @@ public final class app/revanced/patcher/patch/NoSuchOptionException : java/lang/ public abstract class app/revanced/patcher/patch/OptionsContainer { public fun ()V - public final fun getOptions ()Lapp/revanced/patcher/patch/PatchOptions; + 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 { - public synthetic fun (Lapp/revanced/patcher/patch/Patch$Manifest;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +public abstract class app/revanced/patcher/patch/Patch : app/revanced/patcher/patch/OptionsContainer { + 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 public abstract fun execute (Lapp/revanced/patcher/data/Context;)V - public final fun getManifest ()Lapp/revanced/patcher/patch/Patch$Manifest; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class app/revanced/patcher/patch/Patch$Manifest { - public fun (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Set;Ljava/util/Set;ZLapp/revanced/patcher/patch/PatchOptions;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Set;Ljava/util/Set;ZLapp/revanced/patcher/patch/PatchOptions;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun equals (Ljava/lang/Object;)Z public final fun getCompatiblePackages ()Ljava/util/Set; public final fun getDependencies ()Ljava/util/Set; public final fun getDescription ()Ljava/lang/String; public final fun getName ()Ljava/lang/String; - public final fun getOptions ()Lapp/revanced/patcher/patch/PatchOptions; public final fun getRequiresIntegrations ()Z public final fun getUse ()Z public fun hashCode ()I + public fun toString ()Ljava/lang/String; } -public final class app/revanced/patcher/patch/Patch$Manifest$CompatiblePackage { +public final class app/revanced/patcher/patch/Patch$CompatiblePackage { public fun (Ljava/lang/String;Ljava/util/Set;)V public synthetic fun (Ljava/lang/String;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getName ()Ljava/lang/String; - public final fun getVersions ()Ljava/util/Set; } public final class app/revanced/patcher/patch/PatchException : java/lang/Exception { @@ -446,10 +439,9 @@ public final class app/revanced/patcher/patch/RequirementNotMetException : java/ } public abstract class app/revanced/patcher/patch/ResourcePatch : app/revanced/patcher/patch/Patch { - public fun (Lapp/revanced/patcher/patch/Patch$Manifest;)V -} - -public abstract interface annotation class app/revanced/patcher/patch/annotations/Patch : java/lang/annotation/Annotation { + 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/util/DomFileEditor : java/io/Closeable { diff --git a/revanced-patcher/build.gradle.kts b/revanced-patcher/build.gradle.kts index e9fcb122..f8a97f4c 100644 --- a/revanced-patcher/build.gradle.kts +++ b/revanced-patcher/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { compileOnly(libs.android) + testImplementation(project(":revanced-patch-annotations-processor")) testImplementation(libs.kotlin.test) } diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt index 246b4c93..d58cf415 100644 --- a/revanced-patcher/src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt @@ -2,7 +2,6 @@ package app.revanced.patcher -import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively import app.revanced.patcher.patch.Patch import dalvik.system.DexClassLoader import lanchon.multidexlib2.BasicDexFileNamer @@ -25,7 +24,7 @@ typealias PatchClass = KClass> /** * A loader of [Patch]es from patch bundles. - * This will load all [Patch]es from the given patch bundles. + * This will load all [Patch]es from the given patch bundles that have a name. * * @param getBinaryClassNames A function that returns the binary names of all classes in a patch bundle. * @param classLoader The [ClassLoader] to use for loading the classes. @@ -38,22 +37,22 @@ sealed class PatchBundleLoader private constructor( private val logger = Logger.getLogger(PatchBundleLoader::class.java.name) init { - patchBundles.flatMap(getBinaryClassNames).map { + patchBundles.flatMap(getBinaryClassNames).asSequence().map { classLoader.loadClass(it) }.filter { - if (it.isAnnotation) return@filter false - - it.findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class) != null + it.isInstance(Patch::class.java) }.mapNotNull { patchClass -> patchClass.getInstance(logger) - }.associateBy { it.manifest.name } - let { patches -> + }.filter { + it.name != null + }.associateBy { + it.name!! + }.let { patches -> @Suppress("UNCHECKED_CAST") (this as MutableMap>).putAll(patches) } } - internal companion object Utils { /** * Instantiates a [Patch]. If the class is a singleton, the INSTANCE field will be used. diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/Patcher.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/Patcher.kt index 160859aa..28740400 100644 --- a/revanced-patcher/src/main/kotlin/app/revanced/patcher/Patcher.kt +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/Patcher.kt @@ -2,7 +2,6 @@ package app.revanced.patcher import app.revanced.patcher.PatchBundleLoader.Utils.getInstance import app.revanced.patcher.data.ResourceContext -import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolveUsingLookupMap import app.revanced.patcher.patch.* @@ -46,13 +45,19 @@ class Patcher( context.resourceContext.decodeResources(ResourceContext.ResourceDecodingMode.MANIFEST_ONLY) } + // TODO: Fix circular dependency detection. + // /** + // * Add [Patch]es to ReVanced [Patcher]. + // * It is not guaranteed that all supplied [Patch]es will be accepted, if an exception is thrown. + // * + // * @param patches The [Patch]es to add. + // * @throws PatcherException.CircularDependencyException If a circular dependency is detected. + // */ /** * Add [Patch]es to ReVanced [Patcher]. - * It is not guaranteed that all supplied [Patch]es will be accepted, if an exception is thrown. * * @param patches The [Patch]es to add. - * @throws PatcherException.CircularDependencyException If a circular dependency is detected. - */ + */ @Suppress("NAME_SHADOWING") override fun acceptPatches(patches: List>) { /** @@ -65,14 +70,14 @@ class Patcher( val dependency = this.java.getInstance(logger)!! context.allPatches[this] = dependency - dependency.manifest.dependencies?.forEach { it.putDependenciesRecursively() } + dependency.dependencies?.forEach { it.putDependenciesRecursively() } } // Add all patches and their dependencies to the context. for (patch in patches) context.executablePatches.putIfAbsent(patch::class, patch) ?: { context.allPatches[patch::class] = patch - patch.manifest.dependencies?.forEach { it.putDependenciesRecursively() } + patch.dependencies?.forEach { it.putDependenciesRecursively() } } /* TODO: Fix circular dependency detection. @@ -99,7 +104,7 @@ class Patcher( * @param predicate The predicate to match. */ fun Patch<*>.anyRecursively(predicate: (Patch<*>) -> Boolean): Boolean = - predicate(this) || manifest.dependencies?.any { dependency -> + predicate(this) || dependencies?.any { dependency -> context.allPatches[dependency]!!.anyRecursively(predicate) } ?: false @@ -113,7 +118,7 @@ class Patcher( // Determine, if merging integrations is required. for (patch in patches) - if (!patch.anyRecursively { it.manifest.requiresIntegrations }) { + if (!patch.anyRecursively { it.requiresIntegrations }) { context.bytecodeContext.integrations.merge = true break } @@ -148,7 +153,7 @@ class Patcher( patch: Patch<*>, executedPatches: LinkedHashMap, PatchResult> ): PatchResult { - val patchName = patch.manifest.name + val patchName = patch.name executedPatches[patch]?.let { patchResult -> patchResult.exception ?: return patchResult @@ -159,7 +164,7 @@ class Patcher( } // Recursively execute all dependency patches. - patch.manifest.dependencies?.forEach { dependencyName -> + patch.dependencies?.forEach { dependencyName -> val dependency = context.executablePatches[dependencyName]!! val result = executePatch(dependency, executedPatches) @@ -171,17 +176,17 @@ class Patcher( } } - // TODO: Implement this in a more polymorphic way. - val patchContext = if (patch is BytecodePatch) { - patch.fingerprints.asList().resolveUsingLookupMap(context.bytecodeContext) - - context.bytecodeContext - } else { - context.resourceContext - } - return try { - patch.execute(patchContext) + // TODO: Implement this in a more polymorphic way. + when (patch) { + is BytecodePatch -> { + patch.fingerprints.toList().resolveUsingLookupMap(context.bytecodeContext) + patch.execute(context.bytecodeContext) + } + is ResourcePatch -> { + patch.execute(context.resourceContext) + } + } PatchResult(patch) } catch (exception: PatchException) { @@ -203,11 +208,11 @@ class Patcher( val executedPatches = LinkedHashMap, PatchResult>() // Key is name. - context.executablePatches.map { it.value }.sortedBy { it.manifest.name }.forEach { patch -> + context.executablePatches.map { it.value }.sortedBy { it.name }.forEach { patch -> val patchResult = executePatch(patch, executedPatches) // If the patch failed, emit the result, even if it is closeable. - // Results of successfully executed patches that are closeable will be emitted later. + // Results of executed patches that are closeable will be emitted later. patchResult.exception?.let { // Propagate exception to caller instead of wrapping it in a new exception. emit(patchResult) @@ -240,7 +245,7 @@ class Patcher( PatchResult( patch, PatchException( - "'${patch.manifest.name}' raised an exception while being closed: $it", + "'${patch.name}' raised an exception while being closed: $it", result.exception ) ) @@ -248,10 +253,7 @@ class Patcher( if (returnOnError) return@flow } ?: run { - patch::class - .java - .findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class) - ?: return@run + patch.name ?: return@run emit(result) } 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 index f14ca483..52320a39 100644 --- a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/OptionsContainer.kt +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/OptionsContainer.kt @@ -9,8 +9,13 @@ abstract class OptionsContainer { * @see PatchOptions */ @Suppress("MemberVisibilityCanBePrivate") - val options = PatchOptions() + 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 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 66b8b204..549180ea 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 @@ -1,3 +1,5 @@ +@file:Suppress("MemberVisibilityCanBePrivate", "UNUSED_PARAMETER") + package app.revanced.patcher.patch import app.revanced.patcher.PatchClass @@ -14,10 +16,23 @@ import java.io.Closeable * If an implementation of [Patch] also implements [Closeable] * it will be closed in reverse execution order of patches executed by ReVanced [Patcher]. * - * @param manifest The manifest of the [Patch]. + * @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. * @param T The [Context] type this patch will work on. */ -sealed class Patch>(val manifest: Manifest) { +sealed class Patch>( + val name: String? = null, + val description: String? = null, + val compatiblePackages: Set? = null, + val dependencies: Set? = null, + val use: Boolean = true, + // TODO: Remove this property, once integrations are coupled with patches. + val requiresIntegrations: Boolean = false, +) : OptionsContainer() { /** * The execution function of the patch. * @@ -26,72 +41,69 @@ sealed class Patch>(val manifest: Manifest) { */ abstract fun execute(context: @UnsafeVariance T) - override fun hashCode() = manifest.hashCode() + override fun hashCode() = name.hashCode() + + override fun toString() = name ?: this::class.simpleName ?: "Unnamed patch" - override fun equals(other: Any?) = other is Patch<*> && manifest == other.manifest + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false - override fun toString() = manifest.name + other as Patch<*> + + return name == other.name + } /** - * The manifest of a [Patch]. + * A package a [Patch] is compatible with. * - * @param name The name of the patch. - * @param description The description of the patch. - * @param use Weather or not the patch should be used. - * @param dependencies The names of patches this patch depends on. - * @param compatiblePackages The packages the patch is compatible with. - * @param requiresIntegrations Weather or not the patch requires integrations. - * @param options The options of the patch. + * @param name The name of the package. + * @param versions The versions of the package. */ - class Manifest( + class CompatiblePackage( val name: String, - val description: String, - val use: Boolean = true, - val dependencies: Set? = null, - val compatiblePackages: Set? = null, - // TODO: Remove this property, once integrations are coupled with patches. - val requiresIntegrations: Boolean = false, - val options: PatchOptions? = null, - ) { - override fun hashCode() = name.hashCode() - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Manifest - - return name == other.name - } - - /** - * A package a [Patch] is compatible with. - * - * @param name The name of the package. - * @param versions The versions of the package. - */ - class CompatiblePackage( - val name: String, - val versions: Set? = null, - ) - } + versions: Set? = null, + ) } /** * A ReVanced [Patch] that works on [ResourceContext]. * - * @param metadata The manifest of the [ResourcePatch]. + * @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( - metadata: Manifest, -) : Patch(metadata) + 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 manifest The manifest of the [BytecodePatch]. * @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( - manifest: Manifest, - internal vararg val fingerprints: MethodFingerprint, -) : Patch(manifest) \ No newline at end of file + 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 index ecf3a852..328debe6 100644 --- a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt @@ -2,8 +2,6 @@ package app.revanced.patcher.patch -import java.nio.file.Path -import kotlin.io.path.pathString import kotlin.reflect.KProperty class NoSuchOptionException(val option: String) : Exception("No such option: $option") diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/annotations/Patch.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/annotations/Patch.kt deleted file mode 100644 index 4928d87c..00000000 --- a/revanced-patcher/src/main/kotlin/app/revanced/patcher/patch/annotations/Patch.kt +++ /dev/null @@ -1,7 +0,0 @@ -package app.revanced.patcher.patch.annotations - -/** - * Annotation to mark a class as a patch. - */ -@Target(AnnotationTarget.CLASS) -annotation class Patch \ No newline at end of file diff --git a/revanced-patcher/src/test/kotlin/app/revanced/patcher/extensions/InstructionExtensionsTest.kt b/revanced-patcher/src/test/kotlin/app/revanced/patcher/extensions/InstructionExtensionsTest.kt index feed3aa7..a7746340 100644 --- a/revanced-patcher/src/test/kotlin/app/revanced/patcher/extensions/InstructionExtensionsTest.kt +++ b/revanced-patcher/src/test/kotlin/app/revanced/patcher/extensions/InstructionExtensionsTest.kt @@ -19,7 +19,7 @@ import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21s import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test +import kotlin.test.Test import kotlin.test.assertEquals private object InstructionExtensionsTest { 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 index 8d370111..778a3f4a 100644 --- a/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt +++ b/revanced-patcher/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt @@ -1,8 +1,8 @@ package app.revanced.patcher.patch -import app.revanced.patcher.usage.bytecode.ExampleBytecodePatch -import org.junit.jupiter.api.Test +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 diff --git a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt b/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt similarity index 82% rename from revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt rename to revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt index e6aa88af..e0f66c53 100644 --- a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt +++ b/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt @@ -1,14 +1,13 @@ -package app.revanced.patcher.usage.bytecode +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.OptionsContainer import app.revanced.patcher.patch.PatchOption +import app.revanced.patcher.patch.annotations.CompatiblePackage import app.revanced.patcher.patch.annotations.Patch -import app.revanced.patcher.usage.resource.patch.ExampleResourcePatch 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 @@ -26,19 +25,19 @@ import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringRefere import com.android.tools.smali.dexlib2.immutable.value.ImmutableFieldEncodedValue import com.android.tools.smali.dexlib2.util.Preconditions import com.google.common.collect.ImmutableList - -@Patch -class ExampleBytecodePatch : BytecodePatch( - Manifest( - "Example patch", - "Example demonstration of a bytecode patch.", - dependencies = setOf(ExampleResourcePatch::class), - compatiblePackages = setOf( - Manifest.CompatiblePackage("com.example.examplePackage", setOf("0.0.1", "0.0.2")) - ) - ), - ExampleFingerprint +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) { @@ -46,7 +45,7 @@ class ExampleBytecodePatch : BytecodePatch( val result = ExampleFingerprint.result!! // Patch options - println(key1) + assertNotNull(key1) key2 = false // Get the implementation for the resolved method @@ -161,32 +160,34 @@ class ExampleBytecodePatch : BytecodePatch( ) } - @Suppress("unused") - companion object : OptionsContainer() { - private var key1 by option( - PatchOption.StringOption( - "key1", "default", "title", "description", true - ) + 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 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 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 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 - ) + ) + + private var key5 by option( + PatchOption.StringOption( + "key5", null, "title", "description", true ) - } + ) } + diff --git a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleFingerprint.kt b/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleFingerprint.kt similarity index 93% rename from revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleFingerprint.kt rename to revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleFingerprint.kt index 3cb5b067..4ec21e0c 100644 --- a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleFingerprint.kt +++ b/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patcher.usage.bytecode +package app.revanced.patcher.usage import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint diff --git a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/resource/patch/ExampleResourcePatch.kt b/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleResourcePatch.kt similarity index 79% rename from revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/resource/patch/ExampleResourcePatch.kt rename to revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleResourcePatch.kt index 974d885e..3314e04d 100644 --- a/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/resource/patch/ExampleResourcePatch.kt +++ b/revanced-patcher/src/test/kotlin/app/revanced/patcher/usage/ExampleResourcePatch.kt @@ -1,10 +1,11 @@ -package app.revanced.patcher.usage.resource.patch +package app.revanced.patcher.usage import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.patch.ResourcePatch import org.w3c.dom.Element -class ExampleResourcePatch : ResourcePatch(Manifest("Example name", "Example description")) { + +class ExampleResourcePatch : ResourcePatch() { override fun execute(context: ResourceContext) { context.xmlEditor["AndroidManifest.xml"].use { editor -> val element = editor // regular DomFileEditor diff --git a/settings.gradle.kts b/settings.gradle.kts index 1f9503f4..8fdd3782 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,4 +19,4 @@ dependencyResolutionManagement { } } -include("revanced-patcher") \ No newline at end of file +include("revanced-patch-annotations-processor", "revanced-patcher") \ No newline at end of file