refact: include each signature in its corresponding patch

Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
oSumAtrIX
2022-04-14 08:48:05 +02:00
parent b2dab3fabf
commit 1f08da8b2a
8 changed files with 316 additions and 290 deletions

View File

@@ -1,11 +1,9 @@
package app.revanced.patcher
import app.revanced.patcher.cache.Cache
import app.revanced.patcher.cache.findIndexed
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchMetadata
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.signature.MethodSignature
import app.revanced.patcher.proxy.ClassProxy
import app.revanced.patcher.signature.resolver.SignatureResolver
import app.revanced.patcher.util.ListBackedSet
import lanchon.multidexlib2.BasicDexFileNamer
@@ -22,21 +20,18 @@ val NAMER = BasicDexFileNamer()
/**
* ReVanced Patcher.
* @param input The input file (an apk or any other multi dex container).
* @param signatures A list of method signatures for the patches.
*/
class Patcher(
input: File,
private val signatures: Iterable<MethodSignature>,
) {
private val cache: Cache
private val patches = mutableSetOf<Patch>()
private val patcherData: PatcherData
private val opcodes: Opcodes
private var sigsResolved = false
private var signaturesResolved = false
init {
val dexFile = MultiDexIO.readDexFile(true, input, NAMER, null, null)
opcodes = dexFile.opcodes
cache = Cache(dexFile.classes.toMutableList())
patcherData = PatcherData(dexFile.classes.toMutableList())
}
/**
@@ -53,18 +48,18 @@ class Patcher(
for (file in files) {
val dexFile = MultiDexIO.readDexFile(true, file, NAMER, null, null)
for (classDef in dexFile.classes) {
val e = cache.classes.findIndexed { it.type == classDef.type }
val e = patcherData.classes.findIndexed { it.type == classDef.type }
if (e != null) {
if (throwOnDuplicates) {
throw Exception("Class ${classDef.type} has already been added to the patcher.")
}
val (_, idx) = e
if (allowedOverwrites.contains(classDef.type)) {
cache.classes[idx] = classDef
patcherData.classes[idx] = classDef
}
continue
}
cache.classes.add(classDef)
patcherData.classes.add(classDef)
}
}
}
@@ -74,16 +69,27 @@ class Patcher(
*/
fun save(): Map<String, MemoryDataStore> {
val newDexFile = object : DexFile {
private fun MutableList<ClassDef>.replaceWith(proxy: ClassProxy) {
if (proxy.proxyUsed) return
this[proxy.originalIndex] = proxy.mutatedClass
}
override fun getClasses(): Set<ClassDef> {
cache.methodMap.values.forEach {
if (it.definingClassProxy.proxyUsed) {
cache.classes[it.definingClassProxy.originalIndex] = it.definingClassProxy.mutatedClass
for (proxy in patcherData.classProxies) {
patcherData.classes.replaceWith(proxy)
}
for (patch in patcherData.patches) {
for (signature in patch.signatures) {
val result = signature.result
result ?: continue
val proxy = result.definingClassProxy
if (!proxy.proxyUsed) continue
patcherData.classes.replaceWith(proxy)
}
}
cache.classProxy.filter { it.proxyUsed }.forEach { proxy ->
cache.classes[proxy.originalIndex] = proxy.mutatedClass
}
return ListBackedSet(cache.classes)
return ListBackedSet(patcherData.classes)
}
override fun getOpcodes(): Opcodes {
@@ -106,29 +112,31 @@ class Patcher(
* @param patches The patches to add.
*/
fun addPatches(patches: Iterable<Patch>) {
this.patches.addAll(patches)
patcherData.patches.addAll(patches)
}
/**
* Apply patches loaded into the patcher.
* @param stopOnError If true, the patches will stop on the first error.
* @return A map of results. If the patch was successfully applied,
* PatchResultSuccess will always be returned in the wrapping Result object.
* If the patch failed to apply, an Exception will always be returned in the wrapping Result object.
* @return A map of [PatchResultSuccess]. If the [Patch] was successfully applied,
* [PatchResultSuccess] will always be returned to the wrapping Result object.
* If the [Patch] failed to apply, an Exception will always be returned to the wrapping Result object.
*/
fun applyPatches(
stopOnError: Boolean = false,
callback: (String) -> Unit = {}
): Map<PatchMetadata, Result<PatchResultSuccess>> {
if (!sigsResolved) {
SignatureResolver(cache.classes, signatures).resolve(cache.methodMap)
sigsResolved = true
if (!signaturesResolved) {
val signatures = patcherData.patches.flatMap { it.signatures }
SignatureResolver(patcherData.classes, signatures).resolve()
signaturesResolved = true
}
return buildMap {
for (patch in patches) {
for (patch in patcherData.patches) {
callback(patch.metadata.shortName)
val result: Result<PatchResultSuccess> = try {
val pr = patch.execute(cache)
val pr = patch.execute(patcherData)
if (pr.isSuccess()) {
Result.success(pr.success()!!)
} else {

View File

@@ -1,18 +1,15 @@
package app.revanced.patcher.cache
package app.revanced.patcher
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.proxy.ClassProxy
import app.revanced.patcher.signature.SignatureResolverResult
import org.jf.dexlib2.iface.ClassDef
class Cache(
class PatcherData(
internal val classes: MutableList<ClassDef>,
val methodMap: MethodMap = MethodMap()
) {
// TODO: currently we create ClassProxies at multiple places, which is why we could have merge conflicts
// this can be solved by creating a dedicated method for creating class proxies,
// if the class proxy already exists in the cached proxy list below.
// The to-do in the method findClass is related
internal val classProxy = mutableSetOf<ClassProxy>()
internal val classProxies = mutableSetOf<ClassProxy>()
internal val patches = mutableSetOf<Patch>()
/**
* Find a class by a given class name
@@ -25,23 +22,23 @@ class Cache(
* @return A proxy for the first class that matches the predicate
*/
fun findClass(predicate: (ClassDef) -> Boolean): ClassProxy? {
// TODO: find a cleaner way to store all proxied classes.
// Currently we have to search the method map as well as the class proxy list which is not elegant
// if we already proxied the class matching the predicate...
for (patch in patches) {
for (signature in patch.signatures) {
val result = signature.result
result ?: continue
// if we already proxied the class matching the predicate,
val proxiedClass = classProxy.find { predicate(it.immutableClass) }
// return that proxy
if (proxiedClass != null) return proxiedClass
// if we already have the class matching the predicate in the method map,
val result = methodMap.entries.find { predicate(it.value.definingClassProxy.immutableClass) }?.value
if (result != null) return result.definingClassProxy
if (predicate(result.definingClassProxy.immutableClass))
return result.definingClassProxy // ...then return that proxy
}
}
// else search the original class list
val (foundClass, index) = classes.findIndexed(predicate) ?: return null
// create a class proxy with the index of the class in the classes list
val classProxy = ClassProxy(foundClass, index)
// add it to the cache and
this.classProxy.add(classProxy)
this.classProxies.add(classProxy)
// return the proxy class
return classProxy
}
@@ -55,7 +52,7 @@ class MethodMap : LinkedHashMap<String, SignatureResolverResult>() {
internal class MethodNotFoundException(s: String) : Exception(s)
internal inline fun <T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
internal inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
for (element in this) {
if (predicate(element)) {
return element

View File

@@ -15,4 +15,4 @@ fun MutableMethodImplementation.addInstructions(index: Int, instructions: List<B
for (i in instructions.lastIndex downTo 0) {
this.addInstruction(index, instructions[i])
}
}
}

View File

@@ -1,13 +1,36 @@
package app.revanced.patcher.patch
import app.revanced.patcher.cache.Cache
import app.revanced.patcher.PatcherData
import app.revanced.patcher.signature.MethodSignature
abstract class Patch(val metadata: PatchMetadata) {
abstract fun execute(cache: Cache): PatchResult
/**
* Patch for the Patcher.
* @param metadata [PatchMetadata] for the patch.
* @param signatures A list of [MethodSignature] this patch relies on.
*/
abstract class Patch(
val metadata: PatchMetadata,
val signatures: Iterable<MethodSignature>
) {
/**
* The main function of the [Patch] which the patcher will call.
*/
abstract fun execute(patcherData: PatcherData): PatchResult
}
/**
* Metadata about a [Patch].
* @param shortName A suggestive short name for the [Patch].
* @param name A suggestive name for the [Patch].
* @param description A description for the [Patch].
* @param compatiblePackages A list of packages this [Patch] is compatible with.
* @param version The version of the [Patch].
*/
data class PatchMetadata(
val shortName: String,
val fullName: String,
val name: String,
val description: String,
@Suppress("ArrayInDataClass") val compatiblePackages: Array<String>,
val version: String,
)

View File

@@ -3,65 +3,66 @@ package app.revanced.patcher.signature
import org.jf.dexlib2.Opcode
/**
* Represents a method signature.
* @param name A suggestive name for the method which the signature was created for.
* @param metadata Metadata about this signature.
* Represents the [MethodSignature] for a method.
* @param methodSignatureMetadata Metadata for this [MethodSignature].
* @param returnType The return type of the method.
* @param accessFlags The access flags of the method.
* @param methodParameters The parameters of the method.
* @param opcodes A list of opcodes of the method.
* @param opcodes The list of opcodes of the method.
*/
data class MethodSignature(
class MethodSignature(
val methodSignatureMetadata: MethodSignatureMetadata,
internal val returnType: String?,
internal val accessFlags: Int?,
internal val methodParameters: Iterable<String>?,
internal val opcodes: Iterable<Opcode>?
) {
/**
* The result of the signature
*/
var result: SignatureResolverResult? = null // TODO: figure out how to get rid of nullable
}
/**
* Metadata about a [MethodSignature].
* @param name A suggestive name for the [MethodSignature].
* @param methodMetadata Metadata about the method for the [MethodSignature].
* @param patternScanMethod The pattern scanning method the pattern scanner should rely on.
* Can either be [PatternScanMethod.Fuzzy] or [PatternScanMethod.Direct].
* @param description An optional description of the [MethodSignature].
* @param compatiblePackages The list of packages the [MethodSignature] is compatible with.
* @param version The version of this signature.
*/
data class MethodSignatureMetadata(
val name: String,
val metadata: SignatureMetadata,
val returnType: String?,
val accessFlags: Int?,
val methodParameters: Iterable<String>?,
val opcodes: Iterable<Opcode>?
val methodMetadata: MethodMetadata,
val patternScanMethod: PatternScanMethod,
@Suppress("ArrayInDataClass") val compatiblePackages: Array<String>,
val description: String?,
val version: String
)
/**
* Metadata about the signature.
* @param method Metadata about the method for this signature.
* @param patcher Metadata for the Patcher, this contains things like how the Patcher should interpret this signature.
*/
data class SignatureMetadata(
val method: MethodMetadata,
val patcher: PatcherMetadata
)
/**
* Metadata about the method for this signature.
* @param definingClass The defining class name of the original method.
* @param methodName The name of the original method.
* @param comment A comment about this method and the data above.
* For example, the version this signature was originally made for.
* Metadata about the method for a [MethodSignature].
* @param definingClass The defining class name of the method.
* @param name A suggestive name for the method which the [MethodSignature] was created for.
*/
data class MethodMetadata(
val definingClass: String?,
val methodName: String?,
val comment: String
val name: String?
)
/**
* Metadata for the Patcher, this contains things like how the Patcher should interpret this signature.
* @param resolverMethod The method the resolver should use to resolve the signature.
* The method, the patcher should rely on when scanning the opcode pattern of a [MethodSignature]
*/
data class PatcherMetadata(
val resolverMethod: ResolverMethod
)
/**
* The method the resolver should use to resolve the signature.
*/
interface ResolverMethod {
interface PatternScanMethod {
/**
* When comparing the signature, if one or more of the opcodes do not match, skip.
*/
class Direct : ResolverMethod
class Direct : PatternScanMethod
/**
* When comparing the signature, if [threshold] or more of the opcodes do not match, skip.
*/
class Fuzzy(val threshold: Int) : ResolverMethod
class Fuzzy(internal val threshold: Int) : PatternScanMethod
}

View File

@@ -1,9 +1,8 @@
package app.revanced.patcher.signature.resolver
import app.revanced.patcher.cache.MethodMap
import app.revanced.patcher.proxy.ClassProxy
import app.revanced.patcher.signature.MethodSignature
import app.revanced.patcher.signature.ResolverMethod
import app.revanced.patcher.signature.PatternScanMethod
import app.revanced.patcher.signature.PatternScanResult
import app.revanced.patcher.signature.SignatureResolverResult
import org.jf.dexlib2.iface.ClassDef
@@ -14,19 +13,17 @@ internal class SignatureResolver(
private val classes: List<ClassDef>,
private val methodSignatures: Iterable<MethodSignature>
) {
fun resolve(methodMap: MethodMap) {
fun resolve() {
for ((index, classDef) in classes.withIndex()) {
for (signature in methodSignatures) {
if (methodMap.containsKey(signature.name)) {
continue
}
if (signature.result != null) continue
for (method in classDef.methods) {
val patternScanData = compareSignatureToMethod(signature, method) ?: continue
// create class proxy, in case a patch needs mutability
val classProxy = ClassProxy(classDef, index)
methodMap[signature.name] = SignatureResolverResult(
signature.result = SignatureResolverResult(
classProxy,
patternScanData,
method.name,
@@ -89,8 +86,8 @@ internal class SignatureResolver(
val pattern = signature.opcodes!!
val size = pattern.count()
var threshold = 0
if (signature.metadata.patcher.resolverMethod is ResolverMethod.Fuzzy) {
threshold = signature.metadata.patcher.resolverMethod.threshold
if (signature.methodSignatureMetadata.patternScanMethod is PatternScanMethod.Fuzzy) {
threshold = signature.methodSignatureMetadata.patternScanMethod.threshold
}
for (instructionIndex in 0 until count) {