From a9e7f19d51937f21ebe2aa52a669bcab50b3345b Mon Sep 17 00:00:00 2001 From: Lucaskyy Date: Sat, 19 Mar 2022 18:35:41 +0100 Subject: [PATCH] Refactor Patcher --- .idea/compiler.xml | 6 -- .idea/jarRepositories.xml | 20 ------ .idea/runConfigurations.xml | 10 --- build.gradle.kts | 1 - settings.gradle.kts | 3 +- .../net/revanced/patcher/MethodResolver.kt | 38 ----------- .../kotlin/net/revanced/patcher/Patcher.kt | 61 +++++++---------- .../net/revanced/patcher/cache/Cache.kt | 12 +++- .../net/revanced/patcher/cache/PatchData.kt | 9 +++ .../patcher/resolver/MethodResolver.kt | 67 +++++++++++++++++++ .../revanced/patcher/resolver/ScanResult.kt | 7 ++ .../signature/{model => }/Signature.kt | 13 ++-- .../patcher/signature/SignatureLoader.kt | 12 ---- .../net/revanced/patcher/util/ExtraTypes.kt | 12 ++++ .../net/revanced/patcher/util/Jar2ASM.kt | 22 ++++++ .../revanced/patcher/util/PatternScanner.kt | 11 --- .../net/revanced/patcher/PatcherTest.kt | 47 ++++++++----- 17 files changed, 192 insertions(+), 159 deletions(-) delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/jarRepositories.xml delete mode 100644 .idea/runConfigurations.xml delete mode 100644 src/main/kotlin/net/revanced/patcher/MethodResolver.kt create mode 100644 src/main/kotlin/net/revanced/patcher/cache/PatchData.kt create mode 100644 src/main/kotlin/net/revanced/patcher/resolver/MethodResolver.kt create mode 100644 src/main/kotlin/net/revanced/patcher/resolver/ScanResult.kt rename src/main/kotlin/net/revanced/patcher/signature/{model => }/Signature.kt (67%) delete mode 100644 src/main/kotlin/net/revanced/patcher/signature/SignatureLoader.kt create mode 100644 src/main/kotlin/net/revanced/patcher/util/ExtraTypes.kt create mode 100644 src/main/kotlin/net/revanced/patcher/util/Jar2ASM.kt delete mode 100644 src/main/kotlin/net/revanced/patcher/util/PatternScanner.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b589d56..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index fdc392f..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 797acea..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 1797fec..b734860 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,6 @@ dependencies { implementation("org.ow2.asm:asm-util:9.2") implementation("org.ow2.asm:asm-tree:9.2") implementation("org.ow2.asm:asm-commons:9.2") - implementation("com.google.code.gson:gson:2.9.0") testImplementation(kotlin("test")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index c675417..b4d1a12 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1 @@ -rootProject.name = "ReVanced Patcher" - +rootProject.name = "revanced-patcher" diff --git a/src/main/kotlin/net/revanced/patcher/MethodResolver.kt b/src/main/kotlin/net/revanced/patcher/MethodResolver.kt deleted file mode 100644 index 0e1a367..0000000 --- a/src/main/kotlin/net/revanced/patcher/MethodResolver.kt +++ /dev/null @@ -1,38 +0,0 @@ -package net.revanced.patcher - -import net.revanced.patcher.signature.model.Signature -import org.objectweb.asm.tree.InsnList -import org.objectweb.asm.tree.MethodNode - -internal class MethodResolver(private val targetMethods: List, private val signatures: List) { - fun resolve(): MutableMap { - val methods = mutableMapOf() - - for (signature in signatures) { - val method = targetMethods.firstOrNull { method -> - method.access == signature.accessors && - signature.parameters.all { parameter -> - method.parameters.any { methodParameter -> - true //TODO check for parameter element type - } - } && method.instructions.scanFor(signature.opcodes) - } ?: continue - methods[signature.name] = method - } - - return methods - } -} - -//TODO: implement returning the index of the needle in the hay -private fun InsnList.scanFor(pattern: Array): Boolean { - for (i in 0 until this.size()) { - var occurrence = 0 - while (i + occurrence < this.size()) { - if (this.get(i + occurrence).opcode != pattern.get(occurrence)) break - if (++occurrence >= pattern.size) return true - } - } - - return false -} diff --git a/src/main/kotlin/net/revanced/patcher/Patcher.kt b/src/main/kotlin/net/revanced/patcher/Patcher.kt index 050095c..410ef77 100644 --- a/src/main/kotlin/net/revanced/patcher/Patcher.kt +++ b/src/main/kotlin/net/revanced/patcher/Patcher.kt @@ -2,54 +2,45 @@ package net.revanced.patcher import net.revanced.patcher.cache.Cache import net.revanced.patcher.patch.Patch -import net.revanced.patcher.signature.model.Signature -import org.objectweb.asm.ClassReader -import org.objectweb.asm.tree.ClassNode -import org.objectweb.asm.tree.MethodNode -import java.io.File +import net.revanced.patcher.patch.PatchResult +import net.revanced.patcher.resolver.MethodResolver +import net.revanced.patcher.signature.Signature +import net.revanced.patcher.util.Jar2ASM +import java.io.InputStream import java.util.jar.JarFile -class Patcher private constructor( - file: File, - signatures: List +/** + * The patcher. (docs WIP) + * + * @param input the input stream to read from, must be a JAR file (for now) + */ +class Patcher ( + input: InputStream, + signatures: Array, ) { val cache = Cache() private val patches: MutableList = mutableListOf() init { - // collecting all methods here - val targetMethods: MutableList = mutableListOf() - - val jarFile = JarFile(file) - jarFile.stream().forEach { jarEntry -> - jarFile.getInputStream(jarEntry).use { jis -> - if (jarEntry.name.endsWith(".class")) { - val classNode = ClassNode() - ClassReader(jis.readAllBytes()).accept(classNode, ClassReader.EXPAND_FRAMES) - targetMethods.addAll(classNode.methods) - } - } - } - - // reducing to required methods via signatures - cache.Methods = MethodResolver(targetMethods, signatures).resolve() - - } - - companion object { - fun loadFromFile(file: String, signatures: List): Patcher = Patcher(File(file), signatures) + cache.methods.putAll(MethodResolver(Jar2ASM.jar2asm(input), signatures).resolve()) } fun addPatches(vararg patches: Patch) { this.patches.addAll(patches) } - fun executePatches(): String? { - for (patch in patches) { - val result = patch.execute() - if (result.isSuccess()) continue - return result.error()!!.errorMessage() + fun executePatches(): Map> { + return buildMap { + for (patch in patches) { + val result: Result = try { + val pr = patch.execute() + if (pr.isSuccess()) continue + Result.failure(Exception(pr.error()?.errorMessage() ?: "Unknown error")) + } catch (e: Exception) { + Result.failure(e) + } + this[patch.patchName] = result + } } - return null } } \ No newline at end of file diff --git a/src/main/kotlin/net/revanced/patcher/cache/Cache.kt b/src/main/kotlin/net/revanced/patcher/cache/Cache.kt index d8fec48..79a9d31 100644 --- a/src/main/kotlin/net/revanced/patcher/cache/Cache.kt +++ b/src/main/kotlin/net/revanced/patcher/cache/Cache.kt @@ -1,7 +1,13 @@ package net.revanced.patcher.cache -import org.objectweb.asm.tree.MethodNode - data class Cache( - var Methods: Map = mutableMapOf() + val methods: MethodMap = MethodMap() ) + +class MethodMap : LinkedHashMap() { + override fun get(key: String): PatchData { + return super.get(key) ?: throw MethodNotFoundException("Method $key not found in method cache") + } +} + +class MethodNotFoundException(s: String) : Exception(s) diff --git a/src/main/kotlin/net/revanced/patcher/cache/PatchData.kt b/src/main/kotlin/net/revanced/patcher/cache/PatchData.kt new file mode 100644 index 0000000..556b005 --- /dev/null +++ b/src/main/kotlin/net/revanced/patcher/cache/PatchData.kt @@ -0,0 +1,9 @@ +package net.revanced.patcher.cache + +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodNode + +data class PatchData( + val cls: ClassNode, + val method: MethodNode +) diff --git a/src/main/kotlin/net/revanced/patcher/resolver/MethodResolver.kt b/src/main/kotlin/net/revanced/patcher/resolver/MethodResolver.kt new file mode 100644 index 0000000..7373a08 --- /dev/null +++ b/src/main/kotlin/net/revanced/patcher/resolver/MethodResolver.kt @@ -0,0 +1,67 @@ +package net.revanced.patcher.resolver + +import net.revanced.patcher.cache.PatchData +import net.revanced.patcher.signature.Signature +import org.objectweb.asm.Type +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.InsnList +import org.objectweb.asm.tree.MethodNode + +internal class MethodResolver(private val classList: List, private val signatures: Array) { + fun resolve(): MutableMap { + val patchData = mutableMapOf() + + for ((classNode, methods) in classList) { + for (method in methods) { + for (signature in signatures) { + if (patchData.containsKey(signature.name)) continue // method already found for this sig + if (!this.cmp(method, signature)) continue + patchData[signature.name] = PatchData(classNode, method) + } + } + } + + for (signature in signatures) { + if (patchData.containsKey(signature.name)) continue + // not found + // TODO log error or whatever + } + + return patchData + } + + private fun cmp(method: MethodNode, signature: Signature): Boolean { + if (signature.returns != Type.getReturnType(method.desc)) return false + if (signature.accessors != method.access) return false + if (!signature.parameters.contentEquals(Type.getArgumentTypes(method.desc))) return false + + val result = method.instructions.scanFor(signature.opcodes) + if (!result.found) return false + // TODO make use of the startIndex and endIndex we have from the result + + return true + } +} + +private operator fun ClassNode.component1(): ClassNode { + return this +} + +private operator fun ClassNode.component2(): List { + return this.methods +} + +private fun InsnList.scanFor(pattern: Array): ScanResult { + for (i in 0 until this.size()) { + var occurrence = 0 + while (i + occurrence < this.size()) { + val current = i + occurrence + if (this[current].opcode != pattern[occurrence]) break + if (++occurrence >= pattern.size) { + return ScanResult(true, current - pattern.size, current) + } + } + } + + return ScanResult(false) +} diff --git a/src/main/kotlin/net/revanced/patcher/resolver/ScanResult.kt b/src/main/kotlin/net/revanced/patcher/resolver/ScanResult.kt new file mode 100644 index 0000000..4d31f98 --- /dev/null +++ b/src/main/kotlin/net/revanced/patcher/resolver/ScanResult.kt @@ -0,0 +1,7 @@ +package net.revanced.patcher.resolver + +internal data class ScanResult( + val found: Boolean, + val startIndex: Int? = 0, + val endIndex: Int? = 0 +) diff --git a/src/main/kotlin/net/revanced/patcher/signature/model/Signature.kt b/src/main/kotlin/net/revanced/patcher/signature/Signature.kt similarity index 67% rename from src/main/kotlin/net/revanced/patcher/signature/model/Signature.kt rename to src/main/kotlin/net/revanced/patcher/signature/Signature.kt index 5c90ec2..936414a 100644 --- a/src/main/kotlin/net/revanced/patcher/signature/model/Signature.kt +++ b/src/main/kotlin/net/revanced/patcher/signature/Signature.kt @@ -1,4 +1,4 @@ -package net.revanced.patcher.signature.model +package net.revanced.patcher.signature import org.objectweb.asm.Type import org.objectweb.asm.tree.ParameterNode @@ -13,13 +13,14 @@ import org.objectweb.asm.tree.ParameterNode * This method name will be used to find the corresponding patch. * @param returns The return type/signature of the method. * @param accessors The accessors of the method. - * @param parameters The parameter types/signatures of the method. - * @param opcodes The opcode pattern of the method, used to find the method by signature scanning. + * @param parameters The parameter types of the method. + * @param opcodes The opcode pattern of the method, used to find the method by pattern scanning. */ +@Suppress("ArrayInDataClass") data class Signature( val name: String, val returns: Type, - @Suppress("ArrayInDataClass") val accessors: Int, - @Suppress("ArrayInDataClass") val parameters: Array, - @Suppress("ArrayInDataClass") val opcodes: Array + val accessors: Int, + val parameters: Array, + val opcodes: Array ) \ No newline at end of file diff --git a/src/main/kotlin/net/revanced/patcher/signature/SignatureLoader.kt b/src/main/kotlin/net/revanced/patcher/signature/SignatureLoader.kt deleted file mode 100644 index 2e9eac2..0000000 --- a/src/main/kotlin/net/revanced/patcher/signature/SignatureLoader.kt +++ /dev/null @@ -1,12 +0,0 @@ -package net.revanced.patcher.signature - -import com.google.gson.Gson -import net.revanced.patcher.signature.model.Signature - -object SignatureLoader { - private val gson = Gson() - - fun LoadFromJson(json: String): Array { - return gson.fromJson(json, Array::class.java) - } -} diff --git a/src/main/kotlin/net/revanced/patcher/util/ExtraTypes.kt b/src/main/kotlin/net/revanced/patcher/util/ExtraTypes.kt new file mode 100644 index 0000000..4113f69 --- /dev/null +++ b/src/main/kotlin/net/revanced/patcher/util/ExtraTypes.kt @@ -0,0 +1,12 @@ +package net.revanced.patcher.util + +import org.objectweb.asm.Type + +object ExtraTypes { + /** + * Any object type. + * Should be used instead of types such as: "Ljava/lang/String;" + */ + val Any = Type.getType(Object::class.java) + val ArrayAny = Type.getType(Array::class.java) +} \ No newline at end of file diff --git a/src/main/kotlin/net/revanced/patcher/util/Jar2ASM.kt b/src/main/kotlin/net/revanced/patcher/util/Jar2ASM.kt new file mode 100644 index 0000000..c192384 --- /dev/null +++ b/src/main/kotlin/net/revanced/patcher/util/Jar2ASM.kt @@ -0,0 +1,22 @@ +package net.revanced.patcher.util + +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.ClassNode +import java.io.InputStream +import java.util.jar.JarInputStream + +object Jar2ASM { + fun jar2asm(input: InputStream): List { + return buildList { + val jar = JarInputStream(input) + while (true) { + val e = jar.nextJarEntry ?: break + if (e.name.endsWith(".class")) { + val classNode = ClassNode() + ClassReader(jar.readAllBytes()).accept(classNode, ClassReader.EXPAND_FRAMES) + this.add(classNode) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/revanced/patcher/util/PatternScanner.kt b/src/main/kotlin/net/revanced/patcher/util/PatternScanner.kt deleted file mode 100644 index 46a53ce..0000000 --- a/src/main/kotlin/net/revanced/patcher/util/PatternScanner.kt +++ /dev/null @@ -1,11 +0,0 @@ -package net.revanced.patcher.util - -import org.objectweb.asm.tree.ClassNode - -class PatternScanner (private val classes: Array) { - companion object { - fun scan(){ - - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/net/revanced/patcher/PatcherTest.kt b/src/test/kotlin/net/revanced/patcher/PatcherTest.kt index 1504abb..a1dbc08 100644 --- a/src/test/kotlin/net/revanced/patcher/PatcherTest.kt +++ b/src/test/kotlin/net/revanced/patcher/PatcherTest.kt @@ -1,30 +1,47 @@ package net.revanced.patcher import net.revanced.patcher.patch.Patch -import net.revanced.patcher.patch.PatchResultError import net.revanced.patcher.patch.PatchResultSuccess -import net.revanced.patcher.signature.SignatureLoader +import net.revanced.patcher.signature.Signature +import net.revanced.patcher.util.ExtraTypes import org.junit.jupiter.api.Test +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.Type internal class PatcherTest { - @Test - fun template() { - val patcher = Patcher.loadFromFile( - "some.apk", - SignatureLoader.LoadFromJson("signatures.json").toMutableList() + private val testSigs: Array = arrayOf( + Signature( + "testMethod", + Type.BOOLEAN_TYPE, + ACC_PUBLIC or ACC_STATIC, + arrayOf( + ExtraTypes.ArrayAny, + ), + arrayOf( + GETSTATIC, + LDC, + INVOKEVIRTUAL + ) ) + ) - val patches = mutableListOf( - Patch ("RemoveVideoAds") { - val videoAdShowMethodInstr = patcher.cache.Methods["SomeMethod"]?.instructions + @Test + fun testPatcher() { + val testData = PatcherTest::class.java.getResourceAsStream("/test1.jar")!! + val patcher = Patcher(testData, testSigs) + + patcher.addPatches( + Patch ("TestPatch") { + patcher.cache.methods["testMethod"] PatchResultSuccess() - }, - Patch ("TweakLayout") { - val layoutMethod = patcher.cache.Methods["SomeMethod2"] - PatchResultError("Failed") } ) - patcher.executePatches() + val result = patcher.executePatches() + for ((s, r) in result) { + if (r.isFailure) { + throw Exception("Patch $s failed", r.exceptionOrNull()!!) + } + } } } \ No newline at end of file