mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2024-11-10 01:02:22 +01:00
fix: Fixed writer & signature resolver, improved tests & speed, minor refactoring
This commit is contained in:
parent
2d3c61113d
commit
bb42fa3c6f
8 changed files with 127 additions and 103 deletions
|
@ -2,7 +2,7 @@ package app.revanced.patcher
|
|||
|
||||
import app.revanced.patcher.cache.Cache
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.resolver.MethodResolver
|
||||
import app.revanced.patcher.resolver.SignatureResolver
|
||||
import app.revanced.patcher.signature.MethodSignature
|
||||
import lanchon.multidexlib2.BasicDexFileNamer
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
|
@ -21,23 +21,20 @@ class Patcher(
|
|||
private val patches = mutableSetOf<Patch>()
|
||||
|
||||
init {
|
||||
// TODO: find a way to load all dex classes, the code below only loads the first .dex file
|
||||
val dexFile = MultiDexIO.readDexFile(true, input, BasicDexFileNamer(), Opcodes.getDefault(), null)
|
||||
cache = Cache(dexFile.classes, MethodResolver(dexFile.classes, signatures).resolve())
|
||||
cache = Cache(dexFile.classes, SignatureResolver(dexFile.classes, signatures).resolve())
|
||||
}
|
||||
|
||||
fun save() {
|
||||
val newDexFile = object : DexFile {
|
||||
override fun getClasses(): MutableSet<out ClassDef> {
|
||||
// TODO: find a way to return a set with a custom iterator
|
||||
// TODO: the iterator would return the proxied class matching the current index of the list
|
||||
// TODO: instead of the original class
|
||||
for (classProxy in cache.classProxy) {
|
||||
if (!classProxy.proxyUsed) continue
|
||||
// TODO: merge this class with cache.classes somehow in an iterator
|
||||
classProxy.mutatedClass
|
||||
}
|
||||
return cache.classes.toMutableSet()
|
||||
override fun getClasses(): Set<ClassDef> {
|
||||
// this is a slow workaround for now
|
||||
val mutableClassList = cache.classes.toMutableList()
|
||||
cache.classProxy
|
||||
.filter { it.proxyUsed }.forEach { proxy ->
|
||||
mutableClassList[proxy.originalIndex] = proxy.mutatedClass
|
||||
}
|
||||
return mutableClassList.toSet()
|
||||
}
|
||||
|
||||
override fun getOpcodes(): Opcodes {
|
||||
|
@ -46,8 +43,8 @@ class Patcher(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: not sure about maxDexPoolSize & we should use the multithreading overload for writeDexFile
|
||||
MultiDexIO.writeDexFile(true, output, BasicDexFileNamer(), newDexFile, 10, null)
|
||||
// TODO: we should use the multithreading capable overload for writeDexFile
|
||||
MultiDexIO.writeDexFile(true, output, BasicDexFileNamer(), newDexFile, 50000, null)
|
||||
}
|
||||
|
||||
fun addPatches(vararg patches: Patch) {
|
||||
|
@ -56,6 +53,7 @@ class Patcher(
|
|||
|
||||
fun applyPatches(stopOnError: Boolean = false): Map<String, Result<Nothing?>> {
|
||||
return buildMap {
|
||||
// TODO: after each patch execution we could clear left overs like proxied classes to safe memory
|
||||
for (patch in patches) {
|
||||
val result: Result<Nothing?> = try {
|
||||
val pr = patch.execute(cache)
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
package app.revanced.patcher.cache
|
||||
|
||||
import app.revanced.patcher.proxy.ClassProxy
|
||||
import app.revanced.patcher.signature.MethodSignatureScanResult
|
||||
import app.revanced.patcher.signature.SignatureResolverResult
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
|
||||
class Cache(
|
||||
internal val classes: Set<ClassDef>,
|
||||
val resolvedMethods: 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
|
||||
internal val classProxy = mutableSetOf<ClassProxy>()
|
||||
|
||||
fun findClass(predicate: (ClassDef) -> Boolean): ClassProxy? {
|
||||
// if we already proxied the class matching the predicate,
|
||||
val proxiedClass = classProxy.singleOrNull{classProxy -> predicate(classProxy.immutableClass)}
|
||||
val proxiedClass = classProxy.singleOrNull { classProxy -> predicate(classProxy.immutableClass) }
|
||||
// return that proxy
|
||||
if (proxiedClass != null) return proxiedClass
|
||||
// else search the original class list
|
||||
val foundClass = classes.singleOrNull(predicate) ?: return null
|
||||
val foundClass = classes.singleOrNull(predicate) ?: return null
|
||||
// create a class proxy with the index of the class in the classes list
|
||||
// TODO: There might be a more elegant way to the comment above
|
||||
val classProxy = ClassProxy(foundClass, classes.indexOf(foundClass))
|
||||
|
@ -27,8 +30,8 @@ class Cache(
|
|||
}
|
||||
}
|
||||
|
||||
class MethodMap : LinkedHashMap<String, MethodSignatureScanResult>() {
|
||||
override fun get(key: String): MethodSignatureScanResult {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,9 @@ package app.revanced.patcher.proxy
|
|||
import app.revanced.patcher.proxy.mutableTypes.MutableClass
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
|
||||
|
||||
class ClassProxy(
|
||||
val immutableClass: ClassDef,
|
||||
val originalClassIndex: Int,
|
||||
val originalIndex: Int,
|
||||
) {
|
||||
internal var proxyUsed = false
|
||||
internal lateinit var mutatedClass: MutableClass
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package app.revanced.patcher.resolver
|
||||
|
||||
internal data class MethodResolverScanResult(
|
||||
internal data class PatternScanData(
|
||||
val found: Boolean,
|
||||
val startIndex: Int? = 0,
|
||||
val endIndex: Int? = 0
|
|
@ -1,43 +1,51 @@
|
|||
package app.revanced.patcher.resolver
|
||||
|
||||
import app.revanced.patcher.cache.MethodMap
|
||||
import app.revanced.patcher.signature.MethodSignatureScanResult
|
||||
import app.revanced.patcher.signature.PatternScanData
|
||||
import app.revanced.patcher.proxy.ClassProxy
|
||||
import app.revanced.patcher.signature.MethodSignature
|
||||
import app.revanced.patcher.signature.PatternScanResult
|
||||
import app.revanced.patcher.signature.SignatureResolverResult
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
|
||||
// TODO: add logger
|
||||
internal class MethodResolver(private val classes: Set<ClassDef>, private val signatures: Array<MethodSignature>) {
|
||||
// TODO: add logger back
|
||||
internal class SignatureResolver(
|
||||
private val classes: Set<ClassDef>,
|
||||
private val methodSignatures: Array<MethodSignature>
|
||||
) {
|
||||
fun resolve(): MethodMap {
|
||||
val methodMap = MethodMap()
|
||||
|
||||
for (classDef in classes) {
|
||||
for (method in classDef.methods) {
|
||||
for (methodSignature in signatures) {
|
||||
if (methodMap.containsKey(methodSignature.name)) { // method already found for this sig
|
||||
for ((index, classDef) in classes.withIndex()) {
|
||||
for (signature in methodSignatures) {
|
||||
if (methodMap.containsKey(signature.name)) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (method in classDef.methods) {
|
||||
val (isMatch, patternScanData) = compareSignatureToMethod(signature, method)
|
||||
|
||||
if (!isMatch || patternScanData == null) {
|
||||
continue
|
||||
}
|
||||
|
||||
val (r, sr) = cmp(method, methodSignature)
|
||||
if (!r || sr == null) {
|
||||
continue
|
||||
}
|
||||
|
||||
methodMap[methodSignature.name] = MethodSignatureScanResult(
|
||||
method,
|
||||
PatternScanData(
|
||||
// sadly we cannot create contracts for a data class, so we must assert
|
||||
sr.startIndex!!,
|
||||
sr.endIndex!!
|
||||
// create class proxy, in case a patch needs mutability
|
||||
val classProxy = ClassProxy(classDef, index)
|
||||
methodMap[signature.name] = SignatureResolverResult(
|
||||
classProxy,
|
||||
method.name,
|
||||
PatternScanResult(
|
||||
patternScanData.startIndex!!,
|
||||
patternScanData.endIndex!!
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (signature in signatures) {
|
||||
// TODO: remove?
|
||||
for (signature in methodSignatures) {
|
||||
if (methodMap.containsKey(signature.name)) continue
|
||||
}
|
||||
|
||||
|
@ -46,45 +54,49 @@ internal class MethodResolver(private val classes: Set<ClassDef>, private val si
|
|||
|
||||
// These functions do not require the constructor values, so they can be static.
|
||||
companion object {
|
||||
fun resolveMethod(classNode: ClassDef, signature: MethodSignature): MethodSignatureScanResult? {
|
||||
for (method in classNode.methods) {
|
||||
val (r, sr) = cmp(method, signature)
|
||||
fun resolveFromProxy(classProxy: ClassProxy, signature: MethodSignature): SignatureResolverResult? {
|
||||
for (method in classProxy.immutableClass.methods) {
|
||||
val (r, sr) = compareSignatureToMethod(signature, method)
|
||||
if (!r || sr == null) continue
|
||||
return MethodSignatureScanResult(
|
||||
method,
|
||||
PatternScanData(0, 0) // opcode list is always ignored.
|
||||
return SignatureResolverResult(
|
||||
classProxy,
|
||||
method.name,
|
||||
null
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun cmp(method: Method, signature: MethodSignature): Pair<Boolean, MethodResolverScanResult?> {
|
||||
private fun compareSignatureToMethod(
|
||||
signature: MethodSignature,
|
||||
method: Method
|
||||
): Pair<Boolean, PatternScanData?> {
|
||||
// TODO: compare as generic object if not primitive
|
||||
signature.returnType?.let { _ ->
|
||||
if (signature.returnType != method.returnType) {
|
||||
return@cmp false to null
|
||||
return@compareSignatureToMethod false to null
|
||||
}
|
||||
}
|
||||
|
||||
signature.accessFlags?.let { _ ->
|
||||
if (signature.accessFlags != method.accessFlags) {
|
||||
return@cmp false to null
|
||||
return@compareSignatureToMethod false to null
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: compare as generic object if the parameter is not primitive
|
||||
signature.methodParameters?.let { _ ->
|
||||
if (signature.methodParameters != method.parameters) {
|
||||
return@cmp false to null
|
||||
return@compareSignatureToMethod false to null
|
||||
}
|
||||
}
|
||||
|
||||
signature.opcodes?.let { _ ->
|
||||
val result = method.implementation?.instructions?.scanFor(signature.opcodes)
|
||||
return@cmp if (result != null && result.found) true to result else false to null
|
||||
return@compareSignatureToMethod if (result != null && result.found) true to result else false to null
|
||||
}
|
||||
|
||||
return true to MethodResolverScanResult(true)
|
||||
return true to PatternScanData(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +104,7 @@ internal class MethodResolver(private val classes: Set<ClassDef>, private val si
|
|||
private operator fun ClassDef.component1() = this
|
||||
private operator fun ClassDef.component2() = this.methods
|
||||
|
||||
private fun <T> MutableIterable<T>.scanFor(pattern: Array<Opcode>): MethodResolverScanResult {
|
||||
private fun <T> MutableIterable<T>.scanFor(pattern: Array<Opcode>): PatternScanData {
|
||||
// TODO: create var for count?
|
||||
for (i in 0 until this.count()) {
|
||||
var occurrence = 0
|
||||
|
@ -101,12 +113,12 @@ private fun <T> MutableIterable<T>.scanFor(pattern: Array<Opcode>): MethodResolv
|
|||
if (!n.shouldSkip() && n != pattern[occurrence]) break
|
||||
if (++occurrence >= pattern.size) {
|
||||
val current = i + occurrence
|
||||
return MethodResolverScanResult(true, current - pattern.size, current)
|
||||
return PatternScanData(true, current - pattern.size, current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MethodResolverScanResult(false)
|
||||
return PatternScanData(false)
|
||||
}
|
||||
|
||||
// TODO: extend Opcode type, not T (requires a cast to Opcode)
|
|
@ -1,23 +0,0 @@
|
|||
package app.revanced.patcher.signature
|
||||
|
||||
import app.revanced.patcher.resolver.MethodResolver
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.immutable.reference.ImmutableTypeReference
|
||||
|
||||
// TODO: IMPORTANT: we might have to use a class proxy as well here
|
||||
data class MethodSignatureScanResult(
|
||||
val method: Method,
|
||||
val scanData: PatternScanData
|
||||
) {
|
||||
@Suppress("Unused") // TODO(Sculas): remove this when we have coverage for this method.
|
||||
fun findParentMethod(signature: MethodSignature): MethodSignatureScanResult? {
|
||||
// TODO: find a way to get the classNode out of method.definingClass
|
||||
return MethodResolver.resolveMethod(ImmutableTypeReference(method.definingClass) as ClassDef, signature)
|
||||
}
|
||||
}
|
||||
|
||||
data class PatternScanData(
|
||||
val startIndex: Int,
|
||||
val endIndex: Int
|
||||
)
|
|
@ -0,0 +1,20 @@
|
|||
package app.revanced.patcher.signature
|
||||
|
||||
import app.revanced.patcher.proxy.ClassProxy
|
||||
import app.revanced.patcher.resolver.SignatureResolver
|
||||
|
||||
data class SignatureResolverResult(
|
||||
val definingClassProxy: ClassProxy,
|
||||
val resolvedMethodName: String,
|
||||
val scanData: PatternScanResult?
|
||||
) {
|
||||
@Suppress("Unused") // TODO(Sculas): remove this when we have coverage for this method.
|
||||
fun findParentMethod(signature: MethodSignature): SignatureResolverResult? {
|
||||
return SignatureResolver.resolveFromProxy(definingClassProxy, signature)
|
||||
}
|
||||
}
|
||||
|
||||
data class PatternScanResult(
|
||||
val startIndex: Int,
|
||||
val endIndex: Int
|
||||
)
|
|
@ -1,5 +1,6 @@
|
|||
package app.revanced.patcher
|
||||
package patcher
|
||||
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.cache.Cache
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
|
@ -8,13 +9,10 @@ import app.revanced.patcher.patch.PatchResultSuccess
|
|||
import app.revanced.patcher.signature.MethodSignature
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction21c
|
||||
import org.jf.dexlib2.iface.reference.FieldReference
|
||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction35c
|
||||
import org.jf.dexlib2.immutable.reference.ImmutableMethodReference
|
||||
import java.io.File
|
||||
|
||||
|
||||
fun main() {
|
||||
val signatures = arrayOf(
|
||||
MethodSignature(
|
||||
|
@ -33,40 +31,57 @@ fun main() {
|
|||
|
||||
val patcher = Patcher(
|
||||
File("black.apk"),
|
||||
File("folder/"),
|
||||
File("./"),
|
||||
signatures
|
||||
)
|
||||
|
||||
val mainMethodPatchViaClassProxy = object : Patch("main-method-patch-via-proxy") {
|
||||
override fun execute(cache: Cache): PatchResult {
|
||||
val proxy = cache.findClass { classDef ->
|
||||
classDef.methods.any { method ->
|
||||
method.name == "main"
|
||||
}
|
||||
} ?: return PatchResultError("Class with method 'mainMethod' could not be found")
|
||||
classDef.type.contains("XAdRemover")
|
||||
} ?: return PatchResultError("Class 'XAdRemover' could not be found")
|
||||
|
||||
val mainMethodClass = proxy.resolve()
|
||||
val mainMethod = mainMethodClass.methods.single { method -> method.name == "main" }
|
||||
val xAdRemoverClass = proxy.resolve()
|
||||
val hideReelMethod = xAdRemoverClass.methods.single { method -> method.name.contains("HideReel") }
|
||||
|
||||
val hideReelMethodRef = ImmutableMethodReference(
|
||||
"Lfi/razerman/youtube/XAdRemover;",
|
||||
"HideReel",
|
||||
listOf("Landroid/view/View;"),
|
||||
val readSettingsMethodRef = ImmutableMethodReference(
|
||||
"Lfi/razerman/youtube/XGlobals;",
|
||||
"ReadSettings",
|
||||
emptyList(),
|
||||
"V"
|
||||
)
|
||||
|
||||
val mainMethodInstructions = mainMethod.implementation!!.instructions
|
||||
val printStreamFieldRef = (mainMethodInstructions.first() as Instruction21c).reference as FieldReference
|
||||
// TODO: not sure how to use the registers yet, find a way
|
||||
mainMethodInstructions.add(BuilderInstruction21c(Opcode.SGET_OBJECT, 0, printStreamFieldRef))
|
||||
val instructions = hideReelMethod.implementation!!.instructions
|
||||
|
||||
val readSettingsInstruction = BuilderInstruction35c(
|
||||
Opcode.INVOKE_STATIC,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
readSettingsMethodRef
|
||||
)
|
||||
|
||||
// TODO: figure out control flow
|
||||
// otherwise the we would still jump over to the original instruction at index 21 instead to our new one
|
||||
instructions.add(
|
||||
21,
|
||||
readSettingsInstruction
|
||||
)
|
||||
return PatchResultSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
val mainMethodPatchViaSignature = object : Patch("main-method-patch-via-signature") {
|
||||
override fun execute(cache: Cache): PatchResult {
|
||||
cache.resolvedMethods["main-method"].method
|
||||
return PatchResultSuccess()
|
||||
val mainMethodMap = cache.resolvedMethods["main-method"]
|
||||
mainMethodMap.definingClassProxy.immutableClass.methods.single { method ->
|
||||
method.name == mainMethodMap.resolvedMethodName
|
||||
}
|
||||
|
||||
return PatchResultSuccess()
|
||||
}
|
||||
}
|
||||
patcher.addPatches(mainMethodPatchViaClassProxy, mainMethodPatchViaSignature)
|
||||
|
|
Loading…
Reference in a new issue