diff --git a/src/main/kotlin/app/revanced/patcher/Patcher.kt b/src/main/kotlin/app/revanced/patcher/Patcher.kt index b3bc77f9..ab85e08b 100644 --- a/src/main/kotlin/app/revanced/patcher/Patcher.kt +++ b/src/main/kotlin/app/revanced/patcher/Patcher.kt @@ -1,19 +1,13 @@ package app.revanced.patcher -import app.revanced.patcher.data.Data -import app.revanced.patcher.data.impl.findIndexed +import app.revanced.patcher.data.Context +import app.revanced.patcher.data.findIndexed import app.revanced.patcher.extensions.PatchExtensions.dependencies import app.revanced.patcher.extensions.PatchExtensions.deprecated import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.nullOutputStream import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve -import app.revanced.patcher.patch.Patch -import app.revanced.patcher.patch.PatchResult -import app.revanced.patcher.patch.PatchResultError -import app.revanced.patcher.patch.PatchResultSuccess -import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.ResourcePatch -import app.revanced.patcher.util.ListBackedSet +import app.revanced.patcher.patch.* import app.revanced.patcher.util.VersionReader import brut.androlib.Androlib import brut.androlib.meta.UsesFramework @@ -29,10 +23,8 @@ import lanchon.multidexlib2.BasicDexFileNamer import lanchon.multidexlib2.DexIO import lanchon.multidexlib2.MultiDexIO import org.jf.dexlib2.Opcodes -import org.jf.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.DexFile import org.jf.dexlib2.writer.io.MemoryDataStore -import java.io.Closeable import java.io.File import java.nio.file.Files @@ -46,7 +38,7 @@ class Patcher(private val options: PatcherOptions) { private val logger = options.logger private val opcodes: Opcodes private var resourceDecodingMode = ResourceDecodingMode.MANIFEST_ONLY - val data: PatcherData + val context: PatcherContext companion object { @JvmStatic @@ -64,8 +56,8 @@ class Patcher(private val options: PatcherOptions) { val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null) // get the opcodes opcodes = dexFile.opcodes - // finally create patcher data - data = PatcherData(dexFile.classes.toMutableList(), options.resourceCacheDirectory) + // finally create patcher context + context = PatcherContext(dexFile.classes.toMutableList(), File(options.resourceCacheDirectory)) // decode manifest file decodeResources(ResourceDecodingMode.MANIFEST_ONLY) @@ -88,12 +80,12 @@ class Patcher(private val options: PatcherOptions) { for (classDef in MultiDexIO.readDexFile(true, file, NAMER, null, null).classes) { val type = classDef.type - val existingClass = data.bytecodeData.classes.internalClasses.findIndexed { it.type == type } + val existingClass = context.bytecodeContext.classes.classes.findIndexed { it.type == type } if (existingClass == null) { if (throwOnDuplicates) throw Exception("Class $type has already been added to the patcher") logger.trace("Merging $type") - data.bytecodeData.classes.internalClasses.add(classDef) + context.bytecodeContext.classes.classes.add(classDef) modified = true continue @@ -104,7 +96,7 @@ class Patcher(private val options: PatcherOptions) { logger.trace("Overwriting $type") val index = existingClass.second - data.bytecodeData.classes.internalClasses[index] = classDef + context.bytecodeContext.classes.classes[index] = classDef modified = true } if (modified) callback(file) @@ -115,7 +107,7 @@ class Patcher(private val options: PatcherOptions) { * Save the patched dex file. */ fun save(): PatcherResult { - val packageMetadata = data.packageMetadata + val packageMetadata = context.packageMetadata val metaInfo = packageMetadata.metaInfo var resourceFile: File? = null @@ -168,14 +160,8 @@ class Patcher(private val options: PatcherOptions) { logger.trace("Creating new dex file") val newDexFile = object : DexFile { - override fun getClasses(): Set { - data.bytecodeData.classes.applyProxies() - return ListBackedSet(data.bytecodeData.classes.internalClasses) - } - - override fun getOpcodes(): Opcodes { - return this@Patcher.opcodes - } + override fun getClasses() = context.bytecodeContext.classes.also { it.replaceClasses() } + override fun getOpcodes() = this@Patcher.opcodes } // write modified dex files @@ -199,12 +185,12 @@ class Patcher(private val options: PatcherOptions) { * Add [Patch]es to the patcher. * @param patches [Patch]es The patches to add. */ - fun addPatches(patches: Iterable>>) { + fun addPatches(patches: Iterable>>) { /** * Fill the cache with the instances of the [Patch]es for later use. * Note: Dependencies of the [Patch] will be cached as well. */ - fun Class>.isResource() { + fun Class>.isResource() { this.also { if (!ResourcePatch::class.java.isAssignableFrom(it)) return@also // set the mode to decode all resources before running the patches @@ -212,74 +198,7 @@ class Patcher(private val options: PatcherOptions) { }.dependencies?.forEach { it.java.isResource() } } - data.patches.addAll(patches.onEach(Class>::isResource)) - } - - /** - * Apply a [patch] and its dependencies recursively. - * @param patch The [patch] to apply. - * @param appliedPatches A map of [patch]es paired to a boolean indicating their success, to prevent infinite recursion. - * @return The result of executing the [patch]. - */ - private fun applyPatch( - patch: Class>, - appliedPatches: LinkedHashMap - ): PatchResult { - val patchName = patch.patchName - - // if the patch has already applied silently skip it - if (appliedPatches.contains(patchName)) { - if (!appliedPatches[patchName]!!.success) - return PatchResultError("'$patchName' did not succeed previously") - - logger.trace("Skipping '$patchName' because it has already been applied") - - return PatchResultSuccess() - } - - // recursively apply all dependency patches - patch.dependencies?.forEach { dependencyClass -> - val dependency = dependencyClass.java - - val result = applyPatch(dependency, appliedPatches) - if (result.isSuccess()) return@forEach - - val error = result.error()!! - val errorMessage = error.cause ?: error.message - return PatchResultError("'$patchName' depends on '${dependency.patchName}' but the following error was raised: $errorMessage") - } - - patch.deprecated?.let { (reason, replacement) -> - logger.warn("'$patchName' is deprecated, reason: $reason") - if (replacement != null) logger.warn("Use '${replacement.java.patchName}' instead") - } - - val patchInstance = patch.getDeclaredConstructor().newInstance() - - val isResourcePatch = ResourcePatch::class.java.isAssignableFrom(patch) - // TODO: implement this in a more polymorphic way - val data = if (isResourcePatch) { - data.resourceData - } else { - data.bytecodeData.also { data -> - (patchInstance as BytecodePatch).fingerprints?.resolve( - data, - data.classes.internalClasses - ) - } - } - - logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}") - - return try { - patchInstance.execute(data).also { - appliedPatches[patchName] = AppliedPatch(patchInstance, it.isSuccess()) - } - } catch (e: Exception) { - PatchResultError(e).also { - appliedPatches[patchName] = AppliedPatch(patchInstance, false) - } - } + context.patches.addAll(patches.onEach(Class>::isResource)) } /** @@ -310,7 +229,7 @@ class Patcher(private val options: PatcherOptions) { androlib.decodeResourcesFull(extInputFile, outDir, resourceTable) // read additional metadata from the resource table - data.packageMetadata.let { metadata -> + context.packageMetadata.let { metadata -> metadata.metaInfo.usesFramework = UsesFramework().also { framework -> framework.ids = resourceTable.listFramePackages().map { it.id }.sorted() } @@ -344,7 +263,7 @@ class Patcher(private val options: PatcherOptions) { } // read of the resourceTable which is created by reading the manifest file - data.packageMetadata.let { metadata -> + context.packageMetadata.let { metadata -> metadata.packageName = resourceTable.currentResPackage.name metadata.packageVersion = resourceTable.versionInfo.versionName metadata.metaInfo.versionInfo = resourceTable.versionInfo @@ -356,37 +275,105 @@ class Patcher(private val options: PatcherOptions) { } /** - * Apply patches loaded into the patcher. + * Execute patches added the patcher. + * * @param stopOnError If true, the patches will stop on the first error. * @return A pair of the name of the [Patch] and its [PatchResult]. */ - fun applyPatches(stopOnError: Boolean = false) = sequence { - // prevent from decoding the manifest twice if it is not needed - if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL) + fun executePatches(stopOnError: Boolean = false): Sequence>> { + /** + * Execute a [Patch] and its dependencies recursively. + * + * @param patchClass The [Patch] to execute. + * @param executedPatches A map of [Patch]es paired to a boolean indicating their success, to prevent infinite recursion. + * @return The result of executing the [Patch]. + */ + fun executePatch( + patchClass: Class>, + executedPatches: LinkedHashMap + ): PatchResult { + val patchName = patchClass.patchName - logger.trace("Applying all patches") + // if the patch has already applied silently skip it + if (executedPatches.contains(patchName)) { + if (!executedPatches[patchName]!!.success) + return PatchResultError("'$patchName' did not succeed previously") - val appliedPatches = LinkedHashMap() // first is name + logger.trace("Skipping '$patchName' because it has already been applied") - try { - for (patch in data.patches) { - val patchResult = applyPatch(patch, appliedPatches) + return PatchResultSuccess() + } - val result = if (patchResult.isSuccess()) { - Result.success(patchResult.success()!!) - } else { - Result.failure(patchResult.error()!!) + // recursively execute all dependency patches + patchClass.dependencies?.forEach { dependencyClass -> + val dependency = dependencyClass.java + + val result = executePatch(dependency, executedPatches) + if (result.isSuccess()) return@forEach + + val error = result.error()!! + val errorMessage = error.cause ?: error.message + return PatchResultError("'$patchName' depends on '${dependency.patchName}' but the following error was raised: $errorMessage") + } + + patchClass.deprecated?.let { (reason, replacement) -> + logger.warn("'$patchName' is deprecated, reason: $reason") + if (replacement != null) logger.warn("Use '${replacement.java.patchName}' instead") + } + + val isResourcePatch = ResourcePatch::class.java.isAssignableFrom(patchClass) + val patchInstance = patchClass.getDeclaredConstructor().newInstance() + + // TODO: implement this in a more polymorphic way + val patchContext = if (isResourcePatch) { + context.resourceContext + } else { + context.bytecodeContext.also { context -> + (patchInstance as BytecodePatch).fingerprints?.resolve( + context, + context.classes.classes + ) } + } + + logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}") - yield(patch.patchName to result) - if (stopOnError && patchResult.isError()) break + return try { + patchInstance.execute(patchContext).also { + executedPatches[patchName] = ExecutedPatch(patchInstance, it.isSuccess()) + } + } catch (e: Exception) { + PatchResultError(e).also { + executedPatches[patchName] = ExecutedPatch(patchInstance, false) + } } - } finally { - // close all closeable patches in order - for ((patch, _) in appliedPatches.values.reversed()) { - if (patch !is Closeable) continue + } + + return sequence { + // prevent from decoding the manifest twice if it is not needed + if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL) + + logger.trace("Executing all patches") - patch.close() + val executedPatches = LinkedHashMap() // first is name + + try { + context.patches.forEach { patch -> + val patchResult = executePatch(patch, executedPatches) + + val result = if (patchResult.isSuccess()) { + Result.success(patchResult.success()!!) + } else { + Result.failure(patchResult.error()!!) + } + + yield(patch.patchName to result) + if (stopOnError && patchResult.isError()) return@sequence + } + } finally { + executedPatches.values.reversed().forEach { (patch, _) -> + patch.close() + } } } } @@ -408,9 +395,9 @@ class Patcher(private val options: PatcherOptions) { } /** - * A result of applying a [Patch]. + * A result of executing a [Patch]. * * @param patchInstance The instance of the [Patch] that was applied. * @param success The result of the [Patch]. */ -internal data class AppliedPatch(val patchInstance: Patch, val success: Boolean) \ No newline at end of file +internal data class ExecutedPatch(val patchInstance: Patch, val success: Boolean) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/PatcherContext.kt b/src/main/kotlin/app/revanced/patcher/PatcherContext.kt new file mode 100644 index 00000000..9589517a --- /dev/null +++ b/src/main/kotlin/app/revanced/patcher/PatcherContext.kt @@ -0,0 +1,19 @@ +package app.revanced.patcher + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.data.Context +import app.revanced.patcher.data.PackageMetadata +import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.Patch +import org.jf.dexlib2.iface.ClassDef +import java.io.File + +data class PatcherContext( + val classes: MutableList, + val resourceCacheDirectory: File, +) { + val packageMetadata = PackageMetadata() + internal val patches = mutableListOf>>() + internal val bytecodeContext = BytecodeContext(classes) + internal val resourceContext = ResourceContext(resourceCacheDirectory) +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/PatcherData.kt b/src/main/kotlin/app/revanced/patcher/PatcherData.kt deleted file mode 100644 index 4f3d1edf..00000000 --- a/src/main/kotlin/app/revanced/patcher/PatcherData.kt +++ /dev/null @@ -1,19 +0,0 @@ -package app.revanced.patcher - -import app.revanced.patcher.data.Data -import app.revanced.patcher.data.PackageMetadata -import app.revanced.patcher.data.impl.BytecodeData -import app.revanced.patcher.data.impl.ResourceData -import app.revanced.patcher.patch.Patch -import org.jf.dexlib2.iface.ClassDef -import java.io.File - -data class PatcherData( - val internalClasses: MutableList, - val resourceCacheDirectory: String, -) { - val packageMetadata = PackageMetadata() - internal val patches = mutableListOf>>() - internal val bytecodeData = BytecodeData(internalClasses) - internal val resourceData = ResourceData(File(resourceCacheDirectory)) -} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/annotation/Deprecated.kt b/src/main/kotlin/app/revanced/patcher/annotation/Deprecated.kt index bb50d938..c5fbbc57 100644 --- a/src/main/kotlin/app/revanced/patcher/annotation/Deprecated.kt +++ b/src/main/kotlin/app/revanced/patcher/annotation/Deprecated.kt @@ -1,6 +1,6 @@ package app.revanced.patcher.annotation -import app.revanced.patcher.data.Data +import app.revanced.patcher.data.Context import app.revanced.patcher.patch.Patch import kotlin.reflect.KClass @@ -14,6 +14,6 @@ import kotlin.reflect.KClass @MustBeDocumented annotation class PatchDeprecated( val reason: String, - val replacement: KClass> = Patch::class + val replacement: KClass> = Patch::class // Values cannot be nullable in annotations, so this will have to do. ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/data/impl/ResourceData.kt b/src/main/kotlin/app/revanced/patcher/data/Context.kt similarity index 52% rename from src/main/kotlin/app/revanced/patcher/data/impl/ResourceData.kt rename to src/main/kotlin/app/revanced/patcher/data/Context.kt index a03a357b..3aced306 100644 --- a/src/main/kotlin/app/revanced/patcher/data/impl/ResourceData.kt +++ b/src/main/kotlin/app/revanced/patcher/data/Context.kt @@ -1,6 +1,9 @@ -package app.revanced.patcher.data.impl +package app.revanced.patcher.data -import app.revanced.patcher.data.Data +import app.revanced.patcher.util.ProxyBackedClassList +import app.revanced.patcher.util.method.MethodWalker +import org.jf.dexlib2.iface.ClassDef +import org.jf.dexlib2.iface.Method import org.w3c.dom.Document import java.io.Closeable import java.io.File @@ -11,7 +14,79 @@ import javax.xml.transform.TransformerFactory import javax.xml.transform.dom.DOMSource import javax.xml.transform.stream.StreamResult -class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable { +/** + * A common interface to constrain [Context] to [BytecodeContext] and [ResourceContext]. + */ + +sealed interface Context + +class BytecodeContext internal constructor(classes: MutableList) : Context { + /** + * The list of classes. + */ + val classes = ProxyBackedClassList(classes) + + /** + * Find a class by a given class name. + * + * @param className The name of the class. + * @return A proxy for the first class that matches the class name. + */ + fun findClass(className: String) = findClass { it.type.contains(className) } + + /** + * Find a class by a given predicate. + * + * @param predicate A predicate to match the class. + * @return A proxy for the first class that matches the predicate. + */ + fun findClass(predicate: (ClassDef) -> Boolean) = + // if we already proxied the class matching the predicate... + classes.proxies.firstOrNull { predicate(it.immutableClass) } ?: + // else resolve the class to a proxy and return it, if the predicate is matching a class + classes.find(predicate)?.let { proxy(it) } + + fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy { + var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type } + if (proxy == null) { + proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef) + this.classes.add(proxy) + } + return proxy + } + + private companion object { + inline fun Iterable.find(predicate: (T) -> Boolean): T? { + for (element in this) { + if (predicate(element)) { + return element + } + } + return null + } + } +} + +/** + * Create a [MethodWalker] instance for the current [BytecodeContext]. + * + * @param startMethod The method to start at. + * @return A [MethodWalker] instance. + */ +fun BytecodeContext.toMethodWalker(startMethod: Method): MethodWalker { + return MethodWalker(this, startMethod) +} + +internal inline fun Iterable.findIndexed(predicate: (T) -> Boolean): Pair? { + for ((index, element) in this.withIndex()) { + if (predicate(element)) { + return element to index + } + } + return null +} + +class ResourceContext internal constructor(private val resourceCacheDirectory: File) : Context, Iterable { val xmlEditor = XmlFileHolder() operator fun get(path: String) = resourceCacheDirectory.resolve(path) @@ -23,7 +98,7 @@ class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable() } -} +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/data/Data.kt b/src/main/kotlin/app/revanced/patcher/data/Data.kt deleted file mode 100644 index aab90762..00000000 --- a/src/main/kotlin/app/revanced/patcher/data/Data.kt +++ /dev/null @@ -1,9 +0,0 @@ -package app.revanced.patcher.data - -import app.revanced.patcher.data.impl.BytecodeData -import app.revanced.patcher.data.impl.ResourceData - -/** - * Constraint interface for [BytecodeData] and [ResourceData] - */ -interface Data \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/data/impl/BytecodeData.kt b/src/main/kotlin/app/revanced/patcher/data/impl/BytecodeData.kt deleted file mode 100644 index d1825a1b..00000000 --- a/src/main/kotlin/app/revanced/patcher/data/impl/BytecodeData.kt +++ /dev/null @@ -1,69 +0,0 @@ -package app.revanced.patcher.data.impl - -import app.revanced.patcher.data.Data -import app.revanced.patcher.util.ProxyBackedClassList -import app.revanced.patcher.util.method.MethodWalker -import org.jf.dexlib2.iface.ClassDef -import org.jf.dexlib2.iface.Method - -class BytecodeData( - internalClasses: MutableList -) : Data { - val classes = ProxyBackedClassList(internalClasses) - - /** - * Find a class by a given class name. - * @param className The name of the class. - * @return A proxy for the first class that matches the class name. - */ - fun findClass(className: String) = findClass { it.type.contains(className) } - - /** - * Find a class by a given predicate. - * @param predicate A predicate to match the class. - * @return A proxy for the first class that matches the predicate. - */ - fun findClass(predicate: (ClassDef) -> Boolean) = - // if we already proxied the class matching the predicate... - classes.proxies.firstOrNull { predicate(it.immutableClass) } ?: - // else resolve the class to a proxy and return it, if the predicate is matching a class - classes.find(predicate)?.let { proxy(it) } - - fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy { - var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type } - if (proxy == null) { - proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef) - this.classes.add(proxy) - } - return proxy - } -} - -internal class MethodNotFoundException(s: String) : Exception(s) - -internal inline fun Iterable.find(predicate: (T) -> Boolean): T? { - for (element in this) { - if (predicate(element)) { - return element - } - } - return null -} - -/** - * Create a [MethodWalker] instance for the current [BytecodeData]. - * @param startMethod The method to start at. - * @return A [MethodWalker] instance. - */ -fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker { - return MethodWalker(this, startMethod) -} - -internal inline fun Iterable.findIndexed(predicate: (T) -> Boolean): Pair? { - for ((index, element) in this.withIndex()) { - if (predicate(element)) { - return element to index - } - } - return null -} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt index f57b1bee..320b24ce 100644 --- a/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt +++ b/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt @@ -1,11 +1,13 @@ package app.revanced.patcher.extensions import app.revanced.patcher.annotation.* -import app.revanced.patcher.data.Data +import app.revanced.patcher.data.Context +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod 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.PatchOptions +import app.revanced.patcher.patch.annotations.DependsOn import kotlin.reflect.KClass import kotlin.reflect.KVisibility import kotlin.reflect.full.companionObject @@ -16,7 +18,7 @@ import kotlin.reflect.full.companionObjectInstance * @param targetAnnotation The annotation to find. * @return The annotation. */ -private fun Class<*>.recursiveAnnotation(targetAnnotation: KClass) = +private fun Class<*>.findAnnotationRecursively(targetAnnotation: KClass) = this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf()) @@ -38,21 +40,34 @@ private fun Class<*>.findAnnotationRecursively( } object PatchExtensions { - val Class<*>.patchName: String get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName - val Class>.version get() = recursiveAnnotation(Version::class)?.version - val Class>.include get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Patch::class)!!.include - val Class>.description get() = recursiveAnnotation(Description::class)?.description - val Class>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.DependsOn::class)?.dependencies - val Class>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages - val Class>.options: PatchOptions? + val Class>.patchName: String + get() = findAnnotationRecursively(Name::class)?.name ?: this.javaClass.simpleName + + val Class>.version + get() = findAnnotationRecursively(Version::class)?.version + + val Class>.include + get() = findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)!!.include + + val Class>.description + get() = findAnnotationRecursively(Description::class)?.description + + val Class>.dependencies + get() = findAnnotationRecursively(DependsOn::class)?.dependencies + + val Class>.compatiblePackages + get() = findAnnotationRecursively(Compatibility::class)?.compatiblePackages + + val Class>.options: PatchOptions? get() = kotlin.companionObject?.let { cl -> if (cl.visibility != KVisibility.PUBLIC) return null kotlin.companionObjectInstance?.let { (it as? OptionsContainer)?.options } } - val Class>.deprecated: Pair>?>? - get() = recursiveAnnotation(PatchDeprecated::class)?.let { + + val Class>.deprecated: Pair>?>? + get() = findAnnotationRecursively(PatchDeprecated::class)?.let { it.reason to it.replacement.let { cl -> if (cl == Patch::class) null else cl } @@ -61,9 +76,17 @@ object PatchExtensions { object MethodFingerprintExtensions { val MethodFingerprint.name: String - get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName - val MethodFingerprint.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1" - val MethodFingerprint.description get() = javaClass.recursiveAnnotation(Description::class)?.description - val MethodFingerprint.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod::class) - val MethodFingerprint.fuzzyScanThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0 + get() = javaClass.findAnnotationRecursively(Name::class)?.name ?: this.javaClass.simpleName + + val MethodFingerprint.version + get() = javaClass.findAnnotationRecursively(Version::class)?.version ?: "0.0.1" + + val MethodFingerprint.description + get() = javaClass.findAnnotationRecursively(Description::class)?.description + + val MethodFingerprint.fuzzyPatternScanMethod + get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class) + + val MethodFingerprint.fuzzyScanThreshold + get() = fuzzyPatternScanMethod?.threshold ?: 0 } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt b/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt index f95d9787..bdb3aace 100644 --- a/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt +++ b/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt @@ -1,6 +1,6 @@ package app.revanced.patcher.fingerprint.method.impl -import app.revanced.patcher.data.impl.BytecodeData +import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold import app.revanced.patcher.extensions.parametersEqual @@ -41,64 +41,67 @@ abstract class MethodFingerprint( companion object { /** * Resolve a list of [MethodFingerprint] against a list of [ClassDef]. - * @param context The classes on which to resolve the [MethodFingerprint]. - * @param forData The [BytecodeData] to host proxies. + * + * @param classes The classes on which to resolve the [MethodFingerprint] in. + * @param context The [BytecodeContext] to host proxies. * @return True if the resolution was successful, false otherwise. */ - fun Iterable.resolve(forData: BytecodeData, context: Iterable) { + fun Iterable.resolve(context: BytecodeContext, classes: Iterable) { for (fingerprint in this) // For each fingerprint - classes@ for (classDef in context) // search through all classes for the fingerprint - if (fingerprint.resolve(forData, classDef)) + classes@ for (classDef in classes) // search through all classes for the fingerprint + if (fingerprint.resolve(context, classDef)) break@classes // if the resolution succeeded, continue with the next fingerprint } /** * Resolve a [MethodFingerprint] against a [ClassDef]. - * @param context The class on which to resolve the [MethodFingerprint]. - * @param forData The [BytecodeData] to host proxies. + * + * @param forClass The class on which to resolve the [MethodFingerprint] in. + * @param context The [BytecodeContext] to host proxies. * @return True if the resolution was successful, false otherwise. */ - fun MethodFingerprint.resolve(forData: BytecodeData, context: ClassDef): Boolean { - for (method in context.methods) - if (this.resolve(forData, method, context)) + fun MethodFingerprint.resolve(context: BytecodeContext, forClass: ClassDef): Boolean { + for (method in forClass.methods) + if (this.resolve(context, method, forClass)) return true return false } /** * Resolve a [MethodFingerprint] against a [Method]. - * @param context The context on which to resolve the [MethodFingerprint]. - * @param classDef The class of the matching [Method]. - * @param forData The [BytecodeData] to host proxies. + * + * @param method The class on which to resolve the [MethodFingerprint] in. + * @param forClass The class on which to resolve the [MethodFingerprint]. + * @param context The [BytecodeContext] to host proxies. * @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise. */ - fun MethodFingerprint.resolve(forData: BytecodeData, context: Method, classDef: ClassDef): Boolean { + fun MethodFingerprint.resolve(context: BytecodeContext, method: Method, forClass: ClassDef): Boolean { val methodFingerprint = this if (methodFingerprint.result != null) return true - if (methodFingerprint.returnType != null && !context.returnType.startsWith(methodFingerprint.returnType)) + if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType)) return false - if (methodFingerprint.access != null && methodFingerprint.access != context.accessFlags) + if (methodFingerprint.access != null && methodFingerprint.access != method.accessFlags) return false if (methodFingerprint.parameters != null && !parametersEqual( methodFingerprint.parameters, // TODO: parseParameters() - context.parameterTypes + method.parameterTypes ) ) return false @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") - if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(context)) + if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method)) return false val stringsScanResult: StringsScanResult? = if (methodFingerprint.strings != null) { StringsScanResult( buildList { - val implementation = context.implementation ?: return false + val implementation = method.implementation ?: return false val stringsList = methodFingerprint.strings.toMutableList() @@ -124,19 +127,19 @@ abstract class MethodFingerprint( } else null val patternScanResult = if (methodFingerprint.opcodes != null) { - context.implementation?.instructions ?: return false + method.implementation?.instructions ?: return false - context.patternScan(methodFingerprint) ?: return false + method.patternScan(methodFingerprint) ?: return false } else null methodFingerprint.result = MethodFingerprintResult( - context, - classDef, + method, + forClass, MethodFingerprintResult.MethodFingerprintScanResult( patternScanResult, stringsScanResult ), - forData + context ) return true @@ -215,16 +218,17 @@ private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintS /** * Represents the result of a [MethodFingerprintResult]. + * * @param method The matching method. * @param classDef The [ClassDef] that contains the matching [method]. * @param scanResult The result of scanning for the [MethodFingerprint]. - * @param data The [BytecodeData] this [MethodFingerprintResult] is attached to, to create proxies. + * @param context The [BytecodeContext] this [MethodFingerprintResult] is attached to, to create proxies. */ data class MethodFingerprintResult( val method: Method, val classDef: ClassDef, val scanResult: MethodFingerprintScanResult, - internal val data: BytecodeData + internal val context: BytecodeContext ) { /** @@ -283,7 +287,7 @@ data class MethodFingerprintResult( * Use [classDef] where possible. */ @Suppress("MemberVisibilityCanBePrivate") - val mutableClass by lazy { data.proxy(classDef).resolve() } + val mutableClass by lazy { context.proxy(classDef).mutableClass } /** * Returns a mutable clone of [method] diff --git a/src/main/kotlin/app/revanced/patcher/patch/Patch.kt b/src/main/kotlin/app/revanced/patcher/patch/Patch.kt index 7e1b5e8f..8a194ed1 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/Patch.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/Patch.kt @@ -1,34 +1,44 @@ package app.revanced.patcher.patch -import app.revanced.patcher.data.Data -import app.revanced.patcher.data.impl.BytecodeData -import app.revanced.patcher.data.impl.ResourceData +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.data.Context +import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import java.io.Closeable /** * A ReVanced patch. * - * Can either be a [ResourcePatch] or a [BytecodePatch]. * If it implements [Closeable], it will be closed after all patches have been executed. * Closing will be done in reverse execution order. */ -sealed interface Patch { +sealed interface Patch : Closeable { /** * The main function of the [Patch] which the patcher will call. + * + * @param context The [Context] the patch will work on. + * @return The result of executing the patch. */ - fun execute(data: @UnsafeVariance T): PatchResult + fun execute(context: @UnsafeVariance T): PatchResult + + /** + * The closing function for this patch. + * + * This can be treated like popping the patch from the current patch stack. + */ + override fun close() {} } /** * Resource patch for the Patcher. */ -interface ResourcePatch : Patch +interface ResourcePatch : Patch /** * Bytecode patch for the Patcher. + * * @param fingerprints A list of [MethodFingerprint] this patch relies on. */ abstract class BytecodePatch( internal val fingerprints: Iterable? = null -) : Patch \ No newline at end of file +) : Patch \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotation.kt b/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotation.kt index 12bdd208..f836b74b 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotation.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotation.kt @@ -1,6 +1,6 @@ package app.revanced.patcher.patch.annotations -import app.revanced.patcher.data.Data +import app.revanced.patcher.data.Context import app.revanced.patcher.patch.Patch import kotlin.reflect.KClass @@ -20,5 +20,5 @@ annotation class Patch(val include: Boolean = true) @Retention(AnnotationRetention.RUNTIME) @MustBeDocumented annotation class DependsOn( - val dependencies: Array>> = [] + val dependencies: Array>> = [] ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/util/ProxyBackedClassList.kt b/src/main/kotlin/app/revanced/patcher/util/ProxyBackedClassList.kt index 925a7742..ab9f5401 100644 --- a/src/main/kotlin/app/revanced/patcher/util/ProxyBackedClassList.kt +++ b/src/main/kotlin/app/revanced/patcher/util/ProxyBackedClassList.kt @@ -3,40 +3,44 @@ package app.revanced.patcher.util import app.revanced.patcher.util.proxy.ClassProxy import org.jf.dexlib2.iface.ClassDef -class ProxyBackedClassList(internal val internalClasses: MutableList) : List { - private val internalProxies = mutableListOf() - internal val proxies: List = internalProxies +/** + * A class that represents a set of classes and proxies. + * + * @param classes The classes to be backed by proxies. + */ +class ProxyBackedClassList(internal val classes: MutableList) : Set { + internal val proxies = mutableListOf() - fun add(classDef: ClassDef) = internalClasses.add(classDef) - fun add(classProxy: ClassProxy) = internalProxies.add(classProxy) + /** + * Add a [ClassDef]. + */ + fun add(classDef: ClassDef) = classes.add(classDef) /** - * Apply all resolved classes into [internalClasses] and clean the [proxies] list. + * Add a [ClassProxy]. */ - internal fun applyProxies() { - // FIXME: check if this could cause issues when multiple patches use the same proxy - internalProxies.removeIf { proxy -> + fun add(classProxy: ClassProxy) = proxies.add(classProxy) + + /** + * Replace all classes with their mutated versions. + */ + internal fun replaceClasses() = + proxies.removeIf { proxy -> // if the proxy is unused, keep it in the list - if (!proxy.proxyUsed) return@removeIf false + if (!proxy.resolved) return@removeIf false - // if it has been used, replace the internal class which it proxied - val index = internalClasses.indexOfFirst { it.type == proxy.immutableClass.type } - internalClasses[index] = proxy.mutatedClass + // if it has been used, replace the original class with the new class + val index = classes.indexOfFirst { it.type == proxy.immutableClass.type } + classes[index] = proxy.mutableClass // return true to remove it from the proxies list return@removeIf true } - } - - override val size get() = internalClasses.size - override fun contains(element: ClassDef) = internalClasses.contains(element) - override fun containsAll(elements: Collection) = internalClasses.containsAll(elements) - override fun get(index: Int) = internalClasses[index] - override fun indexOf(element: ClassDef) = internalClasses.indexOf(element) - override fun isEmpty() = internalClasses.isEmpty() - override fun iterator() = internalClasses.iterator() - override fun lastIndexOf(element: ClassDef) = internalClasses.lastIndexOf(element) - override fun listIterator() = internalClasses.listIterator() - override fun listIterator(index: Int) = internalClasses.listIterator(index) - override fun subList(fromIndex: Int, toIndex: Int) = internalClasses.subList(fromIndex, toIndex) + + + override val size get() = classes.size + override fun contains(element: ClassDef) = classes.contains(element) + override fun containsAll(elements: Collection) = classes.containsAll(elements) + override fun isEmpty() = classes.isEmpty() + override fun iterator() = classes.iterator() } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/util/method/MethodWalker.kt b/src/main/kotlin/app/revanced/patcher/util/method/MethodWalker.kt index 1df794dc..a9406d36 100644 --- a/src/main/kotlin/app/revanced/patcher/util/method/MethodWalker.kt +++ b/src/main/kotlin/app/revanced/patcher/util/method/MethodWalker.kt @@ -1,7 +1,6 @@ package app.revanced.patcher.util.method -import app.revanced.patcher.data.impl.BytecodeData -import app.revanced.patcher.data.impl.MethodNotFoundException +import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.softCompareTo import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import org.jf.dexlib2.iface.Method @@ -10,16 +9,19 @@ import org.jf.dexlib2.iface.reference.MethodReference /** * Find a method from another method via instruction offsets. - * @param bytecodeData The bytecodeData to use when resolving the next method reference. + * @param bytecodeContext The context to use when resolving the next method reference. * @param currentMethod The method to start from. */ class MethodWalker internal constructor( - private val bytecodeData: BytecodeData, + private val bytecodeContext: BytecodeContext, private var currentMethod: Method ) { /** * Get the method which was walked last. + * * It is possible to cast this method to a [MutableMethod], if the method has been walked mutably. + * + * @return The method which was walked last. */ fun getMethod(): Method { return currentMethod @@ -27,18 +29,21 @@ class MethodWalker internal constructor( /** * Walk to a method defined at the offset in the instruction list of the current method. + * + * The current method will be mutable. + * * @param offset The offset of the instruction. This instruction must be of format 35c. * @param walkMutable If this is true, the class of the method will be resolved mutably. - * The current method will be mutable. + * @return The same [MethodWalker] instance with the method at [offset]. */ fun nextMethod(offset: Int, walkMutable: Boolean = false): MethodWalker { currentMethod.implementation?.instructions?.let { instructions -> val instruction = instructions.elementAt(offset) val newMethod = (instruction as ReferenceInstruction).reference as MethodReference - val proxy = bytecodeData.findClass(newMethod.definingClass)!! + val proxy = bytecodeContext.findClass(newMethod.definingClass)!! - val methods = if (walkMutable) proxy.resolve().methods else proxy.immutableClass.methods + val methods = if (walkMutable) proxy.mutableClass.methods else proxy.immutableClass.methods currentMethod = methods.first { it -> return@first it.softCompareTo(newMethod) } @@ -47,5 +52,5 @@ class MethodWalker internal constructor( throw MethodNotFoundException("This method can not be walked at offset $offset inside the method ${currentMethod.name}") } - + internal class MethodNotFoundException(exception: String) : Exception(exception) } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/util/patch/PatchBundle.kt b/src/main/kotlin/app/revanced/patcher/util/patch/PatchBundle.kt index a33b42d8..0c07255e 100644 --- a/src/main/kotlin/app/revanced/patcher/util/patch/PatchBundle.kt +++ b/src/main/kotlin/app/revanced/patcher/util/patch/PatchBundle.kt @@ -1,18 +1,74 @@ +@file:Suppress("unused") + package app.revanced.patcher.util.patch -import app.revanced.patcher.data.Data +import app.revanced.patcher.data.Context import app.revanced.patcher.patch.Patch +import org.jf.dexlib2.DexFileFactory import java.io.File +import java.net.URLClassLoader +import java.util.jar.JarFile /** + * A patch bundle. + * @param path The path to the patch bundle. */ -abstract class PatchBundle(path: String) : File(path) { +sealed class PatchBundle(path: String) : File(path) { internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator) = buildList { for (className in classNames) { val clazz = classLoader.loadClass(className) if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) continue - @Suppress("UNCHECKED_CAST") this.add(clazz as Class>) + @Suppress("UNCHECKED_CAST") this.add(clazz as Class>) } } + + /** + * A patch bundle of type [Jar]. + * + * @param patchBundlePath The path to the patch bundle. + */ + class Jar(patchBundlePath: String) : PatchBundle(patchBundlePath) { + + /** + * Load patches from the patch bundle. + * + * Patches will be loaded with a new [URLClassLoader]. + */ + fun loadPatches() = loadPatches( + URLClassLoader( + arrayOf(this.toURI().toURL()), + Thread.currentThread().contextClassLoader // TODO: find out why this is required + ), + StringIterator( + JarFile(this) + .entries() + .toList() // TODO: find a cleaner solution than that to filter non class files + .filter { + it.name.endsWith(".class") && !it.name.contains("$") + } + .iterator() + ) { + it.realName.replace('/', '.').replace(".class", "") + } + ) + } + + /** + * A patch bundle of type [Dex] format. + * + * @param patchBundlePath The path to a patch bundle of dex format. + * @param dexClassLoader The dex class loader. + */ + class Dex(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) { + /** + * Load patches from the patch bundle. + * + * Patches will be loaded to the provided [dexClassLoader]. + */ + fun loadPatches() = loadPatches(dexClassLoader, + StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef -> + classDef.type.substring(1, classDef.length - 1).replace('/', '.') + }) + } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/util/patch/impl/DexPatchBundle.kt b/src/main/kotlin/app/revanced/patcher/util/patch/impl/DexPatchBundle.kt deleted file mode 100644 index dce61fdc..00000000 --- a/src/main/kotlin/app/revanced/patcher/util/patch/impl/DexPatchBundle.kt +++ /dev/null @@ -1,17 +0,0 @@ -package app.revanced.patcher.util.patch.impl - -import app.revanced.patcher.util.patch.PatchBundle -import app.revanced.patcher.util.patch.StringIterator -import org.jf.dexlib2.DexFileFactory - -/** - * A patch bundle of the ReVanced [DexPatchBundle] format. - * @param patchBundlePath The path to a patch bundle of dex format. - * @param dexClassLoader The dex class loader. - */ -class DexPatchBundle(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) { - fun loadPatches() = loadPatches(dexClassLoader, - StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef -> - classDef.type.substring(1, classDef.length - 1).replace('/', '.') - }) -} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/util/patch/impl/JarPatchBundle.kt b/src/main/kotlin/app/revanced/patcher/util/patch/impl/JarPatchBundle.kt deleted file mode 100644 index c6fb83ec..00000000 --- a/src/main/kotlin/app/revanced/patcher/util/patch/impl/JarPatchBundle.kt +++ /dev/null @@ -1,30 +0,0 @@ -package app.revanced.patcher.util.patch.impl - -import app.revanced.patcher.util.patch.PatchBundle -import app.revanced.patcher.util.patch.StringIterator -import java.net.URLClassLoader -import java.util.jar.JarFile - -/** - * A patch bundle of the ReVanced [JarPatchBundle] format. - * @param patchBundlePath The path to the patch bundle. - */ -class JarPatchBundle(patchBundlePath: String) : PatchBundle(patchBundlePath) { - fun loadPatches() = loadPatches( - URLClassLoader( - arrayOf(this.toURI().toURL()), - Thread.currentThread().contextClassLoader // TODO: find out why this is required - ), - StringIterator( - JarFile(this) - .entries() - .toList() // TODO: find a cleaner solution than that to filter non class files - .filter { - it.name.endsWith(".class") && !it.name.contains("$") - } - .iterator() - ) { - it.realName.replace('/', '.').replace(".class", "") - } - ) -} diff --git a/src/main/kotlin/app/revanced/patcher/util/proxy/ClassProxy.kt b/src/main/kotlin/app/revanced/patcher/util/proxy/ClassProxy.kt index 6481ad8e..780a2f96 100644 --- a/src/main/kotlin/app/revanced/patcher/util/proxy/ClassProxy.kt +++ b/src/main/kotlin/app/revanced/patcher/util/proxy/ClassProxy.kt @@ -8,34 +8,26 @@ import org.jf.dexlib2.iface.ClassDef * * A class proxy simply holds a reference to the original class * and allocates a mutable clone for the original class if needed. - * @param immutableClass The class to proxy + * @param immutableClass The class to proxy. */ -class ClassProxy( +class ClassProxy internal constructor( val immutableClass: ClassDef, ) { - internal var proxyUsed = false - internal lateinit var mutatedClass: MutableClass - - init { - // in the instance, that a [MutableClass] is being proxied, - // do not create an additional clone and reuse the [MutableClass] instance - if (immutableClass is MutableClass) { - mutatedClass = immutableClass - proxyUsed = true - } - } + /** + * Weather the proxy was actually used. + */ + internal var resolved = false /** - * Allocates and returns a mutable clone of the original class. - * A patch should always use the original immutable class reference - * to avoid unnecessary allocations for the mutable class. - * @return A mutable clone of the original class. + * The mutable clone of the original class. + * + * Note: This is only allocated if the proxy is actually used. */ - fun resolve(): MutableClass { - if (!proxyUsed) { - proxyUsed = true - mutatedClass = MutableClass(immutableClass) - } - return mutatedClass + val mutableClass by lazy { + resolved = true + if (immutableClass is MutableClass) { + immutableClass + } else + MutableClass(immutableClass) } } \ No newline at end of file diff --git a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt index c0ac5a6e..4656b250 100644 --- a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt +++ b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt @@ -3,18 +3,15 @@ package app.revanced.patcher.usage.bytecode import app.revanced.patcher.annotation.Description import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Version -import app.revanced.patcher.data.impl.BytecodeData +import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.replaceInstruction -import app.revanced.patcher.patch.OptionsContainer -import app.revanced.patcher.patch.PatchOption -import app.revanced.patcher.patch.PatchResult -import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.* import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.Patch -import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility +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.google.common.collect.ImmutableList @@ -39,11 +36,11 @@ import kotlin.io.path.Path @Description("Example demonstration of a bytecode patch.") @ExampleResourceCompatibility @Version("0.0.1") -@DependsOn([ExampleBytecodePatch::class]) +@DependsOn([ExampleResourcePatch::class]) class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { // This function will be executed by the patcher. // You can treat it as a constructor - override fun execute(data: BytecodeData): PatchResult { + override fun execute(context: BytecodeContext): PatchResult { // Get the resolved method by its fingerprint from the resolver cache val result = ExampleFingerprint.result!! @@ -63,9 +60,9 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.") // Get the class in which the method matching our fingerprint is defined in. - val mainClass = data.findClass { + val mainClass = context.findClass { it.type == result.classDef.type - }!!.resolve() + }!!.mutableClass // Add a new method returning a string mainClass.methods.add( @@ -169,6 +166,7 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { ) } + @Suppress("unused") companion object : OptionsContainer() { private var key1 by option( PatchOption.StringOption( diff --git a/src/test/kotlin/app/revanced/patcher/usage/resource/patch/ExampleResourcePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/resource/patch/ExampleResourcePatch.kt index 4f9fcf2f..95a44318 100644 --- a/src/test/kotlin/app/revanced/patcher/usage/resource/patch/ExampleResourcePatch.kt +++ b/src/test/kotlin/app/revanced/patcher/usage/resource/patch/ExampleResourcePatch.kt @@ -3,11 +3,11 @@ package app.revanced.patcher.usage.resource.patch import app.revanced.patcher.annotation.Description import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Version -import app.revanced.patcher.data.impl.ResourceData +import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResultSuccess -import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility import org.w3c.dom.Element @@ -17,8 +17,8 @@ import org.w3c.dom.Element @ExampleResourceCompatibility @Version("0.0.1") class ExampleResourcePatch : ResourcePatch { - override fun execute(data: ResourceData): PatchResult { - data.xmlEditor["AndroidManifest.xml"].use { editor -> + override fun execute(context: ResourceContext): PatchResult { + context.xmlEditor["AndroidManifest.xml"].use { editor -> val element = editor // regular DomFileEditor .file .getElementsByTagName("application") diff --git a/src/test/kotlin/app/revanced/patcher/util/VersionReaderTest.kt b/src/test/kotlin/app/revanced/patcher/util/VersionReaderTest.kt index 2ad670cc..8acbb8b5 100644 --- a/src/test/kotlin/app/revanced/patcher/util/VersionReaderTest.kt +++ b/src/test/kotlin/app/revanced/patcher/util/VersionReaderTest.kt @@ -1,8 +1,7 @@ package app.revanced.patcher.util -import org.junit.jupiter.api.Test - import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test internal class VersionReaderTest { @Test