1
mirror of https://github.com/revanced/revanced-patcher synced 2025-09-06 16:38:50 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
semantic-release-bot
540d7d5286 chore(release): 12.0.0-dev.2 [skip ci]
# [12.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v12.0.0-dev.1...v12.0.0-dev.2) (2023-07-28)

### Features

* Deprecate `Version` annotation ([400442f](400442f70e))
2023-07-28 18:15:22 +00:00
oSumAtrIX
400442f70e feat: Deprecate Version annotation 2023-07-28 20:13:45 +02:00
129 changed files with 1841 additions and 4171 deletions

View File

@@ -1,4 +1,4 @@
name: Open a PR to main
name: PR to main
on:
push:
@@ -7,7 +7,7 @@ on:
workflow_dispatch:
env:
MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main`
MESSAGE: merge branch `${{ github.head_ref || github.ref_name }}` to `main`
jobs:
pull-request:
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Open pull request
uses: repo-sync/pull-request@v2
with:

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
# Make sure the release step uses its own credentials:
# https://github.com/cycjimmy/semantic-release-action#private-packages
@@ -36,7 +36,7 @@ jobs:
- name: Build with Gradle
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build clean --no-daemon
run: ./gradlew clean --no-daemon
- name: Setup semantic-release
run: npm install
- name: Release

View File

@@ -10,7 +10,7 @@
[
"@semantic-release/commit-analyzer", {
"releaseRules": [
{ "type": "build", "scope": "Needs bump", "release": "patch" }
{ "type": "build", "scope": "needs-bump", "release": "patch" }
]
}
],

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +1,71 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
plugins {
kotlin("jvm") version "1.9.0"
alias(libs.plugins.binary.compatibility.validator)
kotlin("jvm") version "1.8.20"
`maven-publish`
signing
java
}
val publicationVersion = project.version.toString()
group = "app.revanced"
subprojects {
apply(plugin = "maven-publish")
apply(plugin = "signing")
apply(plugin = "java")
apply(plugin ="kotlin")
val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
group = "app.revanced"
version = publicationVersion
repositories {
mavenCentral()
mavenLocal()
maven { url = uri("https://jitpack.io") }
google()
}
java {
withJavadocJar()
withSourcesJar()
}
configure<KotlinJvmProjectExtension> {
kotlin { jvmToolchain(11) }
}
tasks {
test {
useJUnitPlatform()
testLogging {
events("PASSED", "SKIPPED", "FAILED")
}
repositories {
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/revanced/multidexlib2")
credentials {
username = githubUsername
password = githubPassword
}
}
}
dependencies {
implementation("xpp3:xpp3:1.1.4c")
implementation("app.revanced:smali:2.5.3-a3836654")
implementation("app.revanced:multidexlib2:2.5.3-a3836654")
implementation("app.revanced:apktool-lib:2.8.2")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20-RC")
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
}
tasks {
test {
useJUnitPlatform()
testLogging {
events("PASSED", "SKIPPED", "FAILED")
}
}
processResources {
expand("projectVersion" to project.version)
}
}
java {
withSourcesJar()
}
kotlin {
jvmToolchain(11)
}
publishing {
repositories {
if (System.getenv("GITHUB_ACTOR") != null)
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
else
mavenLocal()
}
publications {
register<MavenPublication>("gpr") {
from(components["java"])
}
}
}

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
version = 17.0.0
version = 12.0.0-dev.2

View File

@@ -1,31 +0,0 @@
[versions]
android = "4.1.1.4"
kotlin-reflect = "1.9.0"
apktool-lib = "2.9.1"
kotlin-test = "1.8.20-RC"
kotlinx-coroutines-core = "1.7.3"
multidexlib2 = "3.0.3.r3"
smali = "3.0.3"
symbol-processing-api = "1.9.10-1.0.13"
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" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
apktool-lib = { module = "app.revanced:apktool", version.ref = "apktool-lib" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
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" }

View File

@@ -1,24 +0,0 @@
public abstract interface annotation class app/revanced/patcher/patch/annotation/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/annotation/Patch : java/lang/annotation/Annotation {
public abstract fun compatiblePackages ()[Lapp/revanced/patcher/patch/annotation/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/annotation/processor/PatchProcessor : com/google/devtools/ksp/processing/SymbolProcessor {
public fun process (Lcom/google/devtools/ksp/processing/Resolver;)Ljava/util/List;
}
public final class app/revanced/patcher/patch/annotation/processor/PatchProcessorProvider : com/google/devtools/ksp/processing/SymbolProcessorProvider {
public fun <init> ()V
public fun create (Lcom/google/devtools/ksp/processing/SymbolProcessorEnvironment;)Lapp/revanced/patcher/patch/annotation/processor/PatchProcessor;
public synthetic fun create (Lcom/google/devtools/ksp/processing/SymbolProcessorEnvironment;)Lcom/google/devtools/ksp/processing/SymbolProcessor;
}

View File

@@ -1,45 +0,0 @@
plugins {
alias(libs.plugins.ksp)
}
dependencies {
implementation(libs.symbol.processing.api)
implementation(libs.kotlinpoet.ksp)
implementation(project(":revanced-patcher"))
testImplementation(libs.kotlin.test)
testImplementation(libs.kotlin.compile.testing)
}
publishing {
publications {
create<MavenPublication>("revanced-patch-annotation-processor-publication") {
from(components["java"])
pom {
name = "ReVanced patch annotation processor"
description = "Annotation processor for patches."
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-patcher.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-patcher.git"
url = "https://github.com/revanced/revanced-patcher"
}
}
}
}
}

View File

@@ -1,2 +0,0 @@
rootProject.name = "revanced-patch-annotation-processor"

View File

@@ -1,38 +0,0 @@
package app.revanced.patcher.patch.annotation
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<KClass<out app.revanced.patcher.patch.Patch<*>>> = [],
val compatiblePackages: Array<CompatiblePackage> = [],
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<String> = [],
)

View File

@@ -1,207 +0,0 @@
package app.revanced.patcher.patch.annotation.processor
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
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 internal constructor(
private val codeGenerator: CodeGenerator,
) : 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<KSAnnotated> {
val patches = 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<KSType>).ifEmpty { null }
val compatiblePackages =
(annotation.property("compatiblePackages") as List<KSAnnotation>).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<ClassName>?,
val compatiblePackages: List<CodeBlock>?,
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<String>).ifEmpty { null }
?.joinToString(", ") { version -> "\"$version\"" }
CodeBlock.of(
"%T(%S, %L)",
app.revanced.patcher.patch.Patch.CompatiblePackage::class,
packageName,
packageVersions?.let { "setOf($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 with,
// unlike the annotated patch.
val dependencyResolutionMap = buildMap {
patches.values.filter { it.dependencies != null }.flatMap {
it.dependencies!!
}.distinct().forEach { dependency ->
patches.keys.find { it.qualifiedName?.asString() == dependency.toString() }
?.let { patch ->
this[dependency] = ClassName(
patch.packageName.asString(),
patch.simpleName.asString() + "Generated"
)
}
}
}
patches.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(", ")
)
}
// The generated patch always depends on the source patch.
addSuperclassConstructorParameter(
"dependencies = setOf(%L)",
buildList {
patchAnnotation.dependencies?.forEach { dependency ->
add("${(dependencyResolutionMap[dependency] ?: dependency)}::class")
}
add("${patchDeclaration.toClassName()}::class")
}.joinToString(", "),
)
addSuperclassConstructorParameter(
"use = %L", patchAnnotation.use
)
addSuperclassConstructorParameter(
"requiresIntegrations = %L",
patchAnnotation.requiresIntegrations
)
}
.addFunction(
FunSpec.builder("execute")
.addModifiers(KModifier.OVERRIDE)
.addParameter("context", contextClass)
.build()
)
.addInitializerBlock(
CodeBlock.builder()
.add(
"%T.options.forEach { (_, option) ->",
patchDeclaration.toClassName()
)
.addStatement(
"options.register(option)"
)
.add(
"}"
)
.build()
)
.build()
).build().writeTo(
codeGenerator,
Dependencies(false, patchDeclaration.containingFile!!)
)
}
return emptyList()
}
}

View File

@@ -1,9 +0,0 @@
package app.revanced.patcher.patch.annotation.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)
}

View File

@@ -1 +0,0 @@
app.revanced.patcher.patch.annotation.processor.PatchProcessorProvider

View File

@@ -1,147 +0,0 @@
package app.revanced.patcher.patch.annotation.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.assertNull
class TestPatchAnnotationProcessor {
// region Processing
@Test
fun testProcessing() = assertEquals(
"Processable patch", compile(
getSourceFile(
"processing", "ProcessablePatch"
)
).loadPatch("$SAMPLE_PACKAGE.processing.ProcessablePatchGenerated").name
)
@Test
fun generateNullProperties() = compile(
getSourceFile(
"null", "NullPropertiesPatch"
)
).loadPatch("$SAMPLE_PACKAGE.null.NullPropertiesPatchGenerated").let {
assertNull(it.description) // Because no description was provided.
assertNull(it.compatiblePackages!!.first().versions) // Because no versions were provided.
}
// 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")
assert(patch.options.isNotEmpty())
assertEquals(patch.options["print"].title, "Print message")
}
// endregion
// region Limitations
@Test
fun failingManualDependency() = assertEquals(
1, // Generated patch is always dependent on source class.
compile(
getSourceFile(
"limitations/manualdependency", "DependentPatch"
), getSourceFile(
"limitations/manualdependency", "DependencyPatch"
)
).loadPatch("$SAMPLE_PACKAGE.limitations.manualdependency.DependentPatchGenerated").dependencies!!.size
)
// endregion
private companion object Utils {
const val SAMPLE_PACKAGE = "app.revanced.patcher.patch.annotation.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 {
val resourceName = "app/revanced/patcher/patch/annotation/processor/samples/$sample/$className.kt"
return SourceFile.kotlin(
"$className.kt",
TestPatchAnnotationProcessor::class.java.classLoader.getResourceAsStream(resourceName)
?.readAllBytes()
?.toString(Charsets.UTF_8)
?: error("Could not find resource $resourceName")
)
}
/**
* 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
}
}

View File

@@ -1,10 +0,0 @@
package app.revanced.patcher.patch.annotation.processor.samples.dependencies
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
@Patch(name = "Dependency patch")
object DependencyPatch : ResourcePatch() {
override fun execute(context: ResourceContext) {}
}

View File

@@ -1,12 +0,0 @@
package app.revanced.patcher.patch.annotation.processor.samples.dependencies
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.Patch
@Patch(
name = "Dependent patch",
dependencies = [DependencyPatch::class],
)
object DependentPatch : BytecodePatch() {
override fun execute(context: BytecodeContext) {}
}

View File

@@ -1,10 +0,0 @@
package app.revanced.patcher.patch.annotation.processor.samples.limitations.manualdependency
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
@Patch(name = "Dependency patch")
object DependencyPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { }
}

View File

@@ -1,17 +0,0 @@
package app.revanced.patcher.patch.annotation.processor.samples.limitations.manualdependency
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.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) {}
}

View File

@@ -1,14 +0,0 @@
package app.revanced.patcher.patch.annotation.processor.samples.`null`
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
@Patch(
"Patch with null properties",
compatiblePackages = [CompatiblePackage("com.google.android.youtube")],
)
object NullPropertiesPatch : BytecodePatch() {
override fun execute(context: BytecodeContext) {}
}

View File

@@ -1,19 +0,0 @@
package app.revanced.patcher.patch.annotation.processor.samples.options
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.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 stringPatchOption(
"print",
null,
"Print message",
"The message to print."
)
}

View File

@@ -1,10 +0,0 @@
package app.revanced.patcher.patch.annotation.processor.samples.processing
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.Patch
@Patch("Processable patch")
object ProcessablePatch : BytecodePatch() {
override fun execute(context: BytecodeContext) {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +0,0 @@
dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.xpp3)
implementation(libs.smali)
implementation(libs.multidexlib2)
implementation(libs.apktool.lib)
implementation(libs.kotlin.reflect)
compileOnly(libs.android)
testImplementation(project(":revanced-patch-annotation-processor"))
testImplementation(libs.kotlin.test)
}
tasks {
processResources {
expand("projectVersion" to project.version)
}
}
publishing {
publications {
create<MavenPublication>("revanced-patcher-publication") {
from(components["java"])
pom {
name = "ReVanced Patcher"
description = "Patcher used by ReVanced."
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-patcher.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-patcher.git"
url = "https://github.com/revanced/revanced-patcher"
}
}
}
}
}

View File

@@ -1 +0,0 @@
rootProject.name = "revanced-patcher"

View File

@@ -1,8 +0,0 @@
package app.revanced.patcher
import java.io.File
@FunctionalInterface
interface IntegrationsConsumer {
fun acceptIntegrations(integrations: List<File>)
}

View File

@@ -1,127 +0,0 @@
@file:Suppress("unused")
package app.revanced.patcher
import app.revanced.patcher.patch.Patch
import dalvik.system.DexClassLoader
import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.MultiDexIO
import java.io.File
import java.net.URLClassLoader
import java.util.jar.JarFile
import java.util.logging.Logger
import kotlin.reflect.KClass
/**
* A set of [Patch]es.
*/
typealias PatchSet = Set<Patch<*>>
/**
* A [Patch] class.
*/
typealias PatchClass = KClass<out Patch<*>>
/**
* A loader of [Patch]es from 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.
* @param patchBundles A set of patches to initialize this instance with.
*/
sealed class PatchBundleLoader private constructor(
classLoader: ClassLoader,
patchBundles: Array<out File>,
getBinaryClassNames: (patchBundle: File) -> List<String>,
// This constructor parameter is unfortunately necessary,
// so that a reference to the mutable set is present in the constructor to be able to add patches to it.
// because the instance itself is a PatchSet, which is immutable, that is delegated by the parameter.
private val patchSet: MutableSet<Patch<*>> = mutableSetOf()
) : PatchSet by patchSet {
private val logger = Logger.getLogger(PatchBundleLoader::class.java.name)
init {
patchBundles.flatMap(getBinaryClassNames).asSequence().map {
classLoader.loadClass(it)
}.filter {
Patch::class.java.isAssignableFrom(it)
}.mapNotNull { patchClass ->
patchClass.getInstance(logger, silent = true)
}.filter {
it.name != null
}.let { patches ->
patchSet.addAll(patches)
}
}
internal companion object Utils {
/**
* Instantiates a [Patch]. If the class is a singleton, the INSTANCE field will be used.
*
* @param logger The [Logger] to use for logging.
* @param silent Whether to suppress logging.
* @return The instantiated [Patch] or `null` if the [Patch] could not be instantiated.
*/
internal fun Class<*>.getInstance(logger: Logger, silent: Boolean = false): Patch<*>? {
return try {
getField("INSTANCE").get(null)
} catch (exception: NoSuchFieldException) {
if (!silent) logger.fine(
"Patch class '${name}' has no INSTANCE field, therefor not a singleton. " +
"Will try to instantiate it."
)
try {
getDeclaredConstructor().newInstance()
} catch (exception: Exception) {
if (!silent) logger.severe(
"Patch class '${name}' is not singleton and has no suitable constructor, " +
"therefor cannot be instantiated and will be ignored."
)
return null
}
} as Patch<*>
}
}
/**
* A [PatchBundleLoader] for JAR files.
*
* @param patchBundles The path to patch bundles of JAR format.
*/
class Jar(vararg patchBundles: File) : PatchBundleLoader(
URLClassLoader(patchBundles.map { it.toURI().toURL() }.toTypedArray()),
patchBundles,
{ patchBundle ->
JarFile(patchBundle).entries().toList().filter { it.name.endsWith(".class") }
.map { it.name.replace('/', '.').replace(".class", "") }
}
)
/**
* A [PatchBundleLoader] for [Dex] files.
*
* @param patchBundles The path to patch bundles of DEX format.
* @param optimizedDexDirectory The directory to store optimized DEX files in.
* This parameter is deprecated and has no effect since API level 26.
*/
class Dex(vararg patchBundles: File, optimizedDexDirectory: File? = null) : PatchBundleLoader(
DexClassLoader(
patchBundles.joinToString(File.pathSeparator) { it.absolutePath }, optimizedDexDirectory?.absolutePath,
null,
PatchBundleLoader::class.java.classLoader
),
patchBundles,
{ patchBundle ->
MultiDexIO.readDexFile(true, patchBundle, BasicDexFileNamer(), null, null).classes
.map { classDef ->
classDef.type.substring(1, classDef.length - 1)
}
}
) {
@Deprecated("This constructor is deprecated. Use the constructor with the second parameter instead.")
constructor(vararg patchBundles: File) : this(*patchBundles, optimizedDexDirectory = null)
}
}

View File

@@ -1,8 +0,0 @@
package app.revanced.patcher
import app.revanced.patcher.patch.PatchResult
import kotlinx.coroutines.flow.Flow
import java.util.function.Function
@FunctionalInterface
interface PatchExecutorFunction : Function<Boolean, Flow<PatchResult>>

View File

@@ -1,40 +0,0 @@
package app.revanced.patcher
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.Patch
import brut.androlib.apk.ApkInfo
import brut.directory.ExtFile
/**
* A context for ReVanced [Patcher].
*
* @param options The [PatcherOptions] used to create this context.
*/
class PatcherContext internal constructor(options: PatcherOptions) {
/**
* [PackageMetadata] of the supplied [PatcherOptions.inputFile].
*/
val packageMetadata = PackageMetadata(ApkInfo(ExtFile(options.inputFile)))
/**
* The map of [Patch]es associated by their [PatchClass].
*/
internal val executablePatches = mutableMapOf<PatchClass, Patch<*>>()
/**
* The map of all [Patch]es and their dependencies associated by their [PatchClass].
*/
internal val allPatches = mutableMapOf<PatchClass, Patch<*>>()
/**
* The [ResourceContext] of this [PatcherContext].
* This holds the current state of the resources.
*/
internal val resourceContext = ResourceContext(this, options)
/**
* The [BytecodeContext] of this [PatcherContext].
* This holds the current state of the bytecode.
*/
internal val bytecodeContext = BytecodeContext(options) }

View File

@@ -1,16 +0,0 @@
package app.revanced.patcher
/**
* An exception thrown by ReVanced [Patcher].
*
* @param errorMessage The exception message.
* @param cause The corresponding [Throwable].
*/
sealed class PatcherException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
constructor(errorMessage: String) : this(errorMessage, null)
class CircularDependencyException internal constructor(dependant: String) : PatcherException(
"Patch '$dependant' causes a circular dependency"
)
}

Some files were not shown because too many files have changed in this diff Show More