mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2024-11-10 01:02:22 +01:00
feat: patch dependencies annotation and PatcherOptions
This commit is contained in:
parent
26f3e7336b
commit
6c65952d80
15 changed files with 181 additions and 222 deletions
|
@ -1,16 +1,17 @@
|
|||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.data.PatcherData
|
||||
import app.revanced.patcher.data.base.Data
|
||||
import app.revanced.patcher.data.implementation.findIndexed
|
||||
import app.revanced.patcher.extensions.findAnnotationRecursively
|
||||
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.extensions.nullOutputStream
|
||||
import app.revanced.patcher.patch.base.Patch
|
||||
import app.revanced.patcher.patch.implementation.BytecodePatch
|
||||
import app.revanced.patcher.patch.implementation.ResourcePatch
|
||||
import app.revanced.patcher.patch.implementation.misc.PatchResult
|
||||
import app.revanced.patcher.patch.implementation.misc.PatchResultError
|
||||
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
|
||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
||||
import app.revanced.patcher.signature.implementation.method.resolver.MethodSignatureResolver
|
||||
import app.revanced.patcher.util.ListBackedSet
|
||||
import brut.androlib.Androlib
|
||||
|
@ -34,15 +35,10 @@ val NAMER = BasicDexFileNamer()
|
|||
|
||||
/**
|
||||
* The ReVanced Patcher.
|
||||
* @param inputFile The input file (usually an apk file).
|
||||
* @param resourceCacheDirectory Directory to cache resources.
|
||||
* @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
|
||||
* @param options The options for the patcher.
|
||||
*/
|
||||
class Patcher(
|
||||
inputFile: File,
|
||||
// TODO: maybe a file system in memory is better. Could cause high memory usage.
|
||||
private val resourceCacheDirectory: String,
|
||||
private val patchResources: Boolean = false
|
||||
private val options: PatcherOptions
|
||||
) {
|
||||
val packageVersion: String
|
||||
val packageName: String
|
||||
|
@ -50,11 +46,10 @@ class Patcher(
|
|||
private lateinit var usesFramework: UsesFramework
|
||||
private val patcherData: PatcherData
|
||||
private val opcodes: Opcodes
|
||||
private var signaturesResolved = false
|
||||
|
||||
init {
|
||||
val extFileInput = ExtFile(inputFile)
|
||||
val outDir = File(resourceCacheDirectory)
|
||||
val extFileInput = ExtFile(options.inputFile)
|
||||
val outDir = File(options.resourceCacheDirectory)
|
||||
|
||||
if (outDir.exists()) outDir.deleteRecursively()
|
||||
outDir.mkdir()
|
||||
|
@ -63,7 +58,7 @@ class Patcher(
|
|||
val androlib = Androlib()
|
||||
val resourceTable = androlib.getResTable(extFileInput, true)
|
||||
|
||||
if (patchResources) {
|
||||
if (options.patchResources) {
|
||||
// 1. decode resources to cache directory
|
||||
androlib.decodeManifestWithResources(extFileInput, outDir, resourceTable)
|
||||
androlib.decodeResourcesFull(extFileInput, outDir, resourceTable)
|
||||
|
@ -93,11 +88,11 @@ class Patcher(
|
|||
packageVersion = resourceTable.versionInfo.versionName
|
||||
packageName = resourceTable.currentResPackage.name
|
||||
// read dex files
|
||||
val dexFile = MultiDexIO.readDexFile(true, inputFile, NAMER, null, null)
|
||||
val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
|
||||
opcodes = dexFile.opcodes
|
||||
|
||||
// save to patcher data
|
||||
patcherData = PatcherData(dexFile.classes.toMutableList(), resourceCacheDirectory)
|
||||
patcherData = PatcherData(dexFile.classes.toMutableList(), options.resourceCacheDirectory)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,8 +139,8 @@ class Patcher(
|
|||
}
|
||||
|
||||
// build modified resources
|
||||
if (patchResources) {
|
||||
val extDir = ExtFile(resourceCacheDirectory)
|
||||
if (options.patchResources) {
|
||||
val extDir = ExtFile(options.resourceCacheDirectory)
|
||||
|
||||
// TODO: figure out why a new instance of Androlib is necessary here
|
||||
Androlib().buildResources(extDir, usesFramework)
|
||||
|
@ -161,33 +156,62 @@ class Patcher(
|
|||
}
|
||||
|
||||
/**
|
||||
* Add a patch to the patcher.
|
||||
* @param patches The patches to add.
|
||||
* Add [Patch]es to the patcher.
|
||||
* @param patches [Patch]es The patches to add.
|
||||
*/
|
||||
fun addPatches(patches: Iterable<Patch<Data>>) {
|
||||
fun addPatches(patches: Iterable<Class<out Patch<Data>>>) {
|
||||
patcherData.patches.addAll(patches)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves all signatures.
|
||||
* Apply a [patch] and its dependencies recursively.
|
||||
* @param patch The [patch] to apply.
|
||||
* @param appliedPatches A list of [patch] names, to prevent applying [patch]es twice.
|
||||
* @return The result of executing the [patch].
|
||||
*/
|
||||
fun resolveSignatures(): List<MethodSignature> {
|
||||
val signatures = buildList {
|
||||
for (patch in patcherData.patches) {
|
||||
if (patch !is BytecodePatch) continue
|
||||
this.addAll(patch.signatures)
|
||||
}
|
||||
}
|
||||
if (signatures.isEmpty()) {
|
||||
return emptyList()
|
||||
private fun applyPatch(
|
||||
patch: Class<out Patch<Data>>, appliedPatches: MutableList<String>
|
||||
): PatchResult {
|
||||
val patchName = patch.patchName
|
||||
|
||||
// if the patch has already applied silently skip it
|
||||
if (appliedPatches.contains(patchName)) return PatchResultSuccess()
|
||||
appliedPatches.add(patchName)
|
||||
|
||||
// recursively apply all dependency patches
|
||||
patch.dependencies?.forEach {
|
||||
val patchDependency = it.java
|
||||
|
||||
val result = applyPatch(patchDependency, appliedPatches)
|
||||
if (result.isSuccess()) return@forEach
|
||||
|
||||
val errorMessage = result.error()!!.message
|
||||
return PatchResultError("$patchName depends on ${patchDependency.patchName} but the following error was raised: $errorMessage")
|
||||
}
|
||||
|
||||
MethodSignatureResolver(patcherData.bytecodeData.classes.internalClasses, signatures).resolve(patcherData)
|
||||
signaturesResolved = true
|
||||
return signatures
|
||||
val patchInstance = patch.getDeclaredConstructor().newInstance()
|
||||
|
||||
// if the current patch is a resource patch but resource patching is disabled, return an error
|
||||
val isResourcePatch = patchInstance is ResourcePatch
|
||||
if (!options.patchResources && isResourcePatch) return PatchResultError("$patchName is a resource patch, but resource patching is disabled.")
|
||||
|
||||
// TODO: find a solution for this
|
||||
val data = if (isResourcePatch) {
|
||||
patcherData.resourceData
|
||||
} else {
|
||||
MethodSignatureResolver(
|
||||
patcherData.bytecodeData.classes.internalClasses, (patchInstance as BytecodePatch).signatures
|
||||
).resolve(patcherData)
|
||||
patcherData.bytecodeData
|
||||
}
|
||||
|
||||
return try {
|
||||
patchInstance.execute(data)
|
||||
} catch (e: Exception) {
|
||||
PatchResultError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Apply patches loaded into the patcher.
|
||||
* @param stopOnError If true, the patches will stop on the first error.
|
||||
|
@ -198,43 +222,22 @@ class Patcher(
|
|||
fun applyPatches(
|
||||
stopOnError: Boolean = false, callback: (String) -> Unit = {}
|
||||
): Map<String, Result<PatchResultSuccess>> {
|
||||
if (!signaturesResolved) {
|
||||
resolveSignatures()
|
||||
}
|
||||
val appliedPatches = mutableListOf<String>()
|
||||
|
||||
return buildMap {
|
||||
for (patch in patcherData.patches) {
|
||||
val resourcePatch = patch is ResourcePatch
|
||||
if (!patchResources && resourcePatch) continue
|
||||
val result = applyPatch(patch, appliedPatches)
|
||||
|
||||
val patchNameAnnotation = patch::class.java.findAnnotationRecursively(Name::class.java)
|
||||
val name = patch.patchName
|
||||
callback(name)
|
||||
|
||||
patchNameAnnotation?.let {
|
||||
callback(it.name)
|
||||
this[name] = if (result.isSuccess()) {
|
||||
Result.success(result.success()!!)
|
||||
} else {
|
||||
Result.failure(result.error()!!)
|
||||
}
|
||||
|
||||
val result: Result<PatchResultSuccess> = try {
|
||||
val data = if (resourcePatch) {
|
||||
patcherData.resourceData
|
||||
} else {
|
||||
patcherData.bytecodeData
|
||||
}
|
||||
|
||||
val pr = patch.execute(data)
|
||||
|
||||
if (pr.isSuccess()) {
|
||||
Result.success(pr.success()!!)
|
||||
} else {
|
||||
Result.failure(Exception(pr.error()?.errorMessage() ?: "Unknown error"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
|
||||
patchNameAnnotation?.let {
|
||||
this[patchNameAnnotation.name] = result
|
||||
}
|
||||
|
||||
if (result.isFailure && stopOnError) break
|
||||
if (stopOnError && result.isError()) break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
src/main/kotlin/app/revanced/patcher/PatcherOptions.kt
Normal file
15
src/main/kotlin/app/revanced/patcher/PatcherOptions.kt
Normal file
|
@ -0,0 +1,15 @@
|
|||
package app.revanced.patcher
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* @param inputFile The input file (usually an apk file).
|
||||
* @param resourceCacheDirectory Directory to cache resources.
|
||||
* @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
|
||||
*/
|
||||
data class PatcherOptions(
|
||||
internal val inputFile: File,
|
||||
// TODO: maybe a file system in memory is better. Could cause high memory usage.
|
||||
internal val resourceCacheDirectory: String,
|
||||
internal val patchResources: Boolean = false
|
||||
)
|
|
@ -11,8 +11,8 @@ internal data class PatcherData(
|
|||
val internalClasses: MutableList<ClassDef>,
|
||||
val resourceCacheDirectory: String
|
||||
) {
|
||||
internal val patches = mutableListOf<Patch<Data>>()
|
||||
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
||||
|
||||
internal val bytecodeData = BytecodeData(patches, internalClasses)
|
||||
internal val bytecodeData = BytecodeData(internalClasses)
|
||||
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
||||
}
|
|
@ -1,55 +1,33 @@
|
|||
package app.revanced.patcher.data.implementation
|
||||
|
||||
import app.revanced.patcher.data.base.Data
|
||||
import app.revanced.patcher.patch.base.Patch
|
||||
import app.revanced.patcher.patch.implementation.BytecodePatch
|
||||
import app.revanced.patcher.signature.implementation.method.resolver.SignatureResolverResult
|
||||
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(
|
||||
// FIXME: ugly solution due to design.
|
||||
// It does not make sense for a BytecodeData instance to have access to the patches
|
||||
private val patches: List<Patch<Data>>,
|
||||
internalClasses: MutableList<ClassDef>
|
||||
) : Data {
|
||||
val classes = ProxyBackedClassList(internalClasses)
|
||||
|
||||
/**
|
||||
* Find a class by a given class name
|
||||
* @return A proxy for the first class that matches the class name
|
||||
* 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
|
||||
* @return A proxy for the first class that matches the predicate
|
||||
* 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): app.revanced.patcher.util.proxy.ClassProxy? {
|
||||
fun findClass(predicate: (ClassDef) -> Boolean) =
|
||||
// if we already proxied the class matching the predicate...
|
||||
for (patch in patches) {
|
||||
if (patch !is BytecodePatch) continue
|
||||
for (signature in patch.signatures) {
|
||||
val result = signature.result
|
||||
result ?: continue
|
||||
|
||||
if (predicate(result.definingClassProxy.immutableClass)) return result.definingClassProxy // ...then return that proxy
|
||||
}
|
||||
}
|
||||
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
||||
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||
return classes.find(predicate)?.let {
|
||||
proxy(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MethodMap : LinkedHashMap<String, SignatureResolverResult>() {
|
||||
override fun get(key: String): SignatureResolverResult {
|
||||
return super.get(key) ?: throw MethodNotFoundException("Method $key was not found in the method cache")
|
||||
}
|
||||
classes.find(predicate)?.let { proxy(it) }
|
||||
}
|
||||
|
||||
internal class MethodNotFoundException(s: String) : Exception(s)
|
||||
|
@ -63,6 +41,11 @@ internal inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T?
|
|||
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)
|
||||
}
|
||||
|
@ -80,7 +63,7 @@ fun BytecodeData.proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.Clas
|
|||
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
||||
if (proxy == null) {
|
||||
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
||||
this.classes.proxies.add(proxy)
|
||||
this.classes.add(proxy)
|
||||
}
|
||||
return proxy
|
||||
}
|
|
@ -4,29 +4,54 @@ import app.revanced.patcher.annotation.Compatibility
|
|||
import app.revanced.patcher.annotation.Description
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.data.base.Data
|
||||
import app.revanced.patcher.patch.base.Patch
|
||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
||||
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.signature.implementation.method.annotation.MatchingMethod
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
private inline fun <reified T : Annotation> Any.firstAnnotation() =
|
||||
this::class.annotations.first { it is T } as T
|
||||
/**
|
||||
* Recursively find a given annotation on a class.
|
||||
* @param targetAnnotation The annotation to find.
|
||||
* @return The annotation.
|
||||
*/
|
||||
private fun <T : Annotation> Class<*>.recursiveAnnotation(targetAnnotation: KClass<T>) =
|
||||
this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
||||
|
||||
private inline fun <reified T : Annotation> Any.recursiveAnnotation() =
|
||||
this::class.java.findAnnotationRecursively(T::class.java)!!
|
||||
|
||||
private fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
||||
targetAnnotation: Class<T>, traversed: MutableSet<Annotation>
|
||||
): T? {
|
||||
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
|
||||
|
||||
@Suppress("UNCHECKED_CAST") if (found != null) return found as T
|
||||
|
||||
for (annotation in this.annotations) {
|
||||
if (traversed.contains(annotation)) continue
|
||||
traversed.add(annotation)
|
||||
|
||||
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed)) ?: continue
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
object PatchExtensions {
|
||||
val Patch<*>.name get() = firstAnnotation<Name>().name
|
||||
val Patch<*>.version get() = firstAnnotation<Version>().version
|
||||
val Patch<*>.description get() = firstAnnotation<Description>().description
|
||||
val Patch<*>.compatiblePackages get() = recursiveAnnotation<Compatibility>().compatiblePackages
|
||||
val Class<out Patch<Data>>.patchName: String
|
||||
get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||
val Class<out Patch<Data>>.version get() = recursiveAnnotation(Version::class)?.version
|
||||
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
|
||||
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Patch::class)?.dependencies
|
||||
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||
}
|
||||
|
||||
object MethodSignatureExtensions {
|
||||
val MethodSignature.name get() = firstAnnotation<Name>().name
|
||||
val MethodSignature.version get() = firstAnnotation<Version>().version
|
||||
val MethodSignature.description get() = firstAnnotation<Description>().description
|
||||
val MethodSignature.compatiblePackages get() = recursiveAnnotation<Compatibility>().compatiblePackages
|
||||
val MethodSignature.matchingMethod get() = firstAnnotation<MatchingMethod>()
|
||||
val MethodSignature.fuzzyThreshold get() = firstAnnotation<FuzzyPatternScanMethod>().threshold
|
||||
val MethodSignature.name get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||
val MethodSignature.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
||||
val MethodSignature.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
||||
val MethodSignature.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||
val MethodSignature.matchingMethod get() = javaClass.recursiveAnnotation(MatchingMethod::class)
|
||||
val MethodSignature.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(FuzzyPatternScanMethod::class)
|
||||
val MethodSignature.fuzzyThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
|
||||
}
|
|
@ -11,33 +11,6 @@ import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
|||
import org.jf.dexlib2.util.MethodUtil
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* Recursively find a given annotation on a class
|
||||
* @param targetAnnotation The annotation to find
|
||||
* @return The annotation
|
||||
*/
|
||||
fun <T : Annotation> Class<*>.findAnnotationRecursively(targetAnnotation: Class<T>) =
|
||||
this.findAnnotationRecursively(targetAnnotation, mutableSetOf())
|
||||
|
||||
private fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
||||
targetAnnotation: Class<T>,
|
||||
traversed: MutableSet<Annotation>
|
||||
): T? {
|
||||
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
if (found != null) return found as T
|
||||
|
||||
for (annotation in this.annotations) {
|
||||
if (traversed.contains(annotation)) continue
|
||||
traversed.add(annotation)
|
||||
|
||||
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed)) ?: continue
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value
|
||||
infix fun Int.or(other: AccessFlags) = this or other.value
|
||||
|
||||
|
@ -47,6 +20,19 @@ fun MutableMethodImplementation.addInstructions(index: Int, instructions: List<B
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare a method to another, considering constructors and parameters.
|
||||
* @param otherMethod The method to compare against.
|
||||
* @return True if the methods match given the conditions.
|
||||
*/
|
||||
fun Method.softCompareTo(
|
||||
otherMethod: MethodReference
|
||||
): Boolean {
|
||||
if (MethodUtil.isConstructor(this) && !parametersEqual(this.parameterTypes, otherMethod.parameterTypes))
|
||||
return false
|
||||
return this.name == otherMethod.name
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the method.
|
||||
* @param registerCount This parameter allows you to change the register count of the method.
|
||||
|
@ -86,14 +72,6 @@ internal fun Method.cloneMutable(
|
|||
registerCount: Int = 0,
|
||||
) = clone(registerCount).toMutable()
|
||||
|
||||
internal fun Method.softCompareTo(
|
||||
otherMethod: MethodReference
|
||||
): Boolean {
|
||||
if (MethodUtil.isConstructor(this) && !parametersEqual(this.parameterTypes, otherMethod.parameterTypes))
|
||||
return false
|
||||
return this.name == otherMethod.name
|
||||
}
|
||||
|
||||
// FIXME: also check the order of parameters as different order equals different method overload
|
||||
internal fun parametersEqual(
|
||||
parameters1: Iterable<CharSequence>,
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package app.revanced.patcher.patch.annotations
|
||||
|
||||
import app.revanced.patcher.data.base.Data
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Annotation to mark a Class as a patch.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Patch
|
||||
annotation class Patch(
|
||||
val dependencies: Array<KClass<out app.revanced.patcher.patch.base.Patch<Data>>> = []
|
||||
)
|
||||
|
|
|
@ -11,8 +11,9 @@ import app.revanced.patcher.patch.implementation.misc.PatchResult
|
|||
* Can either be a [ResourcePatch] or a [BytecodePatch].
|
||||
*/
|
||||
abstract class Patch<out T : Data> {
|
||||
|
||||
/**
|
||||
* The main function of the [Patch] which the patcher will call.
|
||||
*/
|
||||
abstract fun execute(data: @UnsafeVariance T): PatchResult // FIXME: remove the UnsafeVariance annotation
|
||||
abstract fun execute(data: @UnsafeVariance T): PatchResult
|
||||
}
|
|
@ -24,10 +24,12 @@ interface PatchResult {
|
|||
}
|
||||
}
|
||||
|
||||
class PatchResultError(private val errorMessage: String) : PatchResult {
|
||||
fun errorMessage(): String {
|
||||
return errorMessage
|
||||
}
|
||||
class PatchResultError(
|
||||
errorMessage: String?, cause: Exception?
|
||||
) : Exception(errorMessage, cause), PatchResult {
|
||||
constructor(errorMessage: String) : this(errorMessage, null)
|
||||
constructor(cause: Exception) : this(cause.message, cause)
|
||||
|
||||
}
|
||||
|
||||
class PatchResultSuccess : PatchResult
|
|
@ -1,7 +1,7 @@
|
|||
package app.revanced.patcher.signature.implementation.method
|
||||
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.data.implementation.MethodNotFoundException
|
||||
import app.revanced.patcher.extensions.MethodSignatureExtensions.name
|
||||
import app.revanced.patcher.signature.base.Signature
|
||||
import app.revanced.patcher.signature.implementation.method.resolver.SignatureResolverResult
|
||||
import org.jf.dexlib2.Opcode
|
||||
|
@ -26,22 +26,8 @@ abstract class MethodSignature(
|
|||
* The result of the signature
|
||||
*/
|
||||
var result: SignatureResolverResult? = null
|
||||
@Throws(MethodNotFoundException::class)
|
||||
get() {
|
||||
return field ?: throw MethodNotFoundException(
|
||||
"Could not resolve required signature ${
|
||||
(this::class.annotations.find { it is Name }?.let {
|
||||
(it as Name).name
|
||||
})
|
||||
}"
|
||||
)
|
||||
}
|
||||
val resolved: Boolean
|
||||
get() {
|
||||
var resolved = false
|
||||
try {
|
||||
resolved = result != null
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
return resolved
|
||||
return field ?: throw MethodNotFoundException("Could not resolve required signature ${this.name}")
|
||||
}
|
||||
}
|
|
@ -2,10 +2,9 @@ package app.revanced.patcher.signature.implementation.method.resolver
|
|||
|
||||
import app.revanced.patcher.data.PatcherData
|
||||
import app.revanced.patcher.data.implementation.proxy
|
||||
import app.revanced.patcher.extensions.findAnnotationRecursively
|
||||
import app.revanced.patcher.extensions.MethodSignatureExtensions.fuzzyThreshold
|
||||
import app.revanced.patcher.extensions.parametersEqual
|
||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
||||
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
|
@ -109,9 +108,7 @@ internal class MethodSignatureResolver(
|
|||
val pattern = signature.opcodes!!
|
||||
val size = pattern.count()
|
||||
|
||||
val threshold =
|
||||
signature::class.java.findAnnotationRecursively(FuzzyPatternScanMethod::class.java)?.threshold
|
||||
?: 0
|
||||
val threshold = signature.fuzzyThreshold
|
||||
|
||||
for (instructionIndex in 0 until count) {
|
||||
var patternIndex = 0
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
|
||||
class ProxyBackedClassList(internal val internalClasses: MutableList<ClassDef>) : List<ClassDef> {
|
||||
internal val proxies = mutableListOf<app.revanced.patcher.util.proxy.ClassProxy>()
|
||||
private val internalProxies = mutableListOf<ClassProxy>()
|
||||
internal val proxies: List<ClassProxy> = internalProxies
|
||||
|
||||
fun add(classDef: ClassDef) {
|
||||
internalClasses.add(classDef)
|
||||
}
|
||||
|
||||
fun add(classProxy: app.revanced.patcher.util.proxy.ClassProxy) {
|
||||
proxies.add(classProxy)
|
||||
}
|
||||
fun add(classDef: ClassDef) = internalClasses.add(classDef)
|
||||
fun add(classProxy: ClassProxy) = internalProxies.add(classProxy)
|
||||
|
||||
/**
|
||||
* Apply all resolved classes into [internalClasses] and clean the [proxies] list.
|
||||
*/
|
||||
internal fun applyProxies() {
|
||||
// FIXME: check if this could cause issues when multiple patches use the same proxy
|
||||
proxies.removeIf { proxy ->
|
||||
internalProxies.removeIf { proxy ->
|
||||
// if the proxy is unused, keep it in the list
|
||||
if (!proxy.proxyUsed) return@removeIf false
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ class MethodWalker internal constructor(
|
|||
* @param walkMutable If this is true, the class of the method will be resolved mutably.
|
||||
* The current method will be mutable.
|
||||
*/
|
||||
fun walk(offset: Int, walkMutable: Boolean = false): MethodWalker {
|
||||
fun nextMethod(offset: Int, walkMutable: Boolean = false): MethodWalker {
|
||||
currentMethod.implementation?.instructions?.let { instructions ->
|
||||
val instruction = instructions.elementAt(offset)
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
package app.revanced.patcher.util.patch
|
||||
|
||||
import app.revanced.patcher.patch.base.Patch
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarFile
|
||||
|
||||
object PatchLoader {
|
||||
/**
|
||||
* This method loads patches from a given jar file containing [Patch]es
|
||||
* @return the loaded patches represented as a list of [Patch] classes
|
||||
*/
|
||||
fun loadFromFile(patchesJar: File) = buildList {
|
||||
val jarFile = JarFile(patchesJar)
|
||||
val classLoader = URLClassLoader(arrayOf(patchesJar.toURI().toURL()))
|
||||
|
||||
val entries = jarFile.entries()
|
||||
while (entries.hasMoreElements()) {
|
||||
val entry = entries.nextElement()
|
||||
if (!entry.name.endsWith(".class") || entry.name.contains("$")) continue
|
||||
|
||||
val clazz = classLoader.loadClass(entry.realName.replace('/', '.').replace(".class", ""))
|
||||
|
||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) continue
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val patch = clazz as Class<Patch<*>>
|
||||
|
||||
// TODO: include declared classes from patch
|
||||
|
||||
this.add(patch)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package app.revanced.patcher.util.patch.base
|
||||
|
||||
import app.revanced.patcher.data.base.Data
|
||||
import app.revanced.patcher.patch.base.Patch
|
||||
import java.io.File
|
||||
|
||||
|
@ -11,7 +12,7 @@ abstract class PatchBundle(patchBundlePath: String) : File(patchBundlePath) {
|
|||
classNames.forEach { className ->
|
||||
val clazz = classLoader.loadClass(className)
|
||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
|
||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<Patch<*>>)
|
||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Data>>)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue