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