Add: MethodResolver, PatternScanner, SignatureLoader & Cache

This commit is contained in:
oSumAtrIX 2022-03-19 01:37:02 +01:00
parent be18b837ba
commit 6666c7a4b7
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
15 changed files with 152 additions and 134 deletions

View File

@ -12,12 +12,12 @@ repositories {
dependencies { dependencies {
implementation(kotlin("stdlib")) implementation(kotlin("stdlib"))
testImplementation(kotlin("test"))
implementation("org.ow2.asm:asm:9.2") implementation("org.ow2.asm:asm:9.2")
implementation("org.ow2.asm:asm-util:9.2") implementation("org.ow2.asm:asm-util:9.2")
implementation("org.ow2.asm:asm-tree:9.2") implementation("org.ow2.asm:asm-tree:9.2")
implementation("org.ow2.asm:asm-commons:9.2") implementation("org.ow2.asm:asm-commons:9.2")
implementation("com.google.code.gson:gson:2.9.0")
testImplementation(kotlin("test"))
} }
tasks.test { tasks.test {

View File

@ -1,2 +1,2 @@
rootProject.name = "patcher" rootProject.name = "ReVanced Patcher"

View File

@ -0,0 +1,38 @@
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<MethodNode>, private val signatures: List<Signature>) {
fun resolve(): MutableMap<String, MethodNode> {
val methods = mutableMapOf<String, MethodNode>()
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<Int>): 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
}

View File

@ -1,48 +1,55 @@
package net.revanced.patcher package net.revanced.patcher
import net.revanced.patcher.cache.Cache
import net.revanced.patcher.patch.Patch import net.revanced.patcher.patch.Patch
import net.revanced.patcher.signature.Signature import net.revanced.patcher.signature.model.Signature
import net.revanced.patcher.store.ASMStore import org.objectweb.asm.ClassReader
import net.revanced.patcher.store.PatchStore import org.objectweb.asm.tree.ClassNode
import net.revanced.patcher.util.Jar2ASM import org.objectweb.asm.tree.MethodNode
import java.io.InputStream import java.io.File
import java.lang.IllegalStateException import java.util.jar.JarFile
/** class Patcher private constructor(
* The patcher. (docs WIP) file: File,
* signatures: List<Signature>
* @param input the input stream to read from, must be a JAR file (for now)
*/
class Patcher(
input: InputStream,
private val signatures: Array<Signature>,
patches: Array<Patch>,
) { ) {
private val patchStore = PatchStore() val cache = Cache()
private val asmStore = ASMStore() private val patches: MutableList<Patch> = mutableListOf()
private val scanned = false
init { init {
patchStore.addPatches(*patches) // collecting all methods here
loadJar(input) val targetMethods: MutableList<MethodNode> = 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()
} }
fun scan() { companion object {
val methods = PatternScanner(signatures).resolve() fun loadFromFile(file: String, signatures: List<Signature>): Patcher = Patcher(File(file), signatures)
} }
fun patch(): String? { fun addPatches(vararg patches: Patch) {
if (!scanned) throw IllegalStateException("Pattern scanner not yet ran") this.patches.addAll(patches)
for ((_, patch) in patchStore.patches) { }
fun executePatches(): String? {
for (patch in patches) {
val result = patch.execute() val result = patch.execute()
if (result.isSuccess()) continue if (result.isSuccess()) continue
return result.error()!!.errorMessage() return result.error()!!.errorMessage()
} }
return null return null
} }
private fun loadJar(input: InputStream) {
asmStore.classes.putAll(Jar2ASM.jar2asm(input))
}
} }

View File

@ -1,9 +0,0 @@
package net.revanced.patcher
import net.revanced.patcher.signature.Signature
class PatternScanner(signatures: Array<Signature>) {
fun resolve() {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,7 @@
package net.revanced.patcher.cache
import org.objectweb.asm.tree.MethodNode
data class Cache(
var Methods: Map<String, MethodNode> = mutableMapOf()
)

View File

@ -1,6 +1,6 @@
package net.revanced.patcher.patch package net.revanced.patcher.patch
class Patch(val name: String, val fn: () -> PatchResult) { class Patch(val patchName: String, val fn: () -> PatchResult) {
fun execute(): PatchResult { fun execute(): PatchResult {
return fn() return fn()
} }

View File

@ -1,48 +0,0 @@
package net.revanced.patcher.signature
import org.objectweb.asm.Type
/**
* An ASM signature list for the Patcher.
*
* @param name The name of the method.
* Do not use the actual method name, instead try to guess what the method name originally was.
* If you are unable to guess a method name, doing something like "patch-name-1" is fine too.
* For example: "override-codec-1".
* 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.
*/
data class Signature(
val name: String,
val returns: Type,
val accessors: Array<Int>,
val parameters: Array<Type>,
val opcodes: Array<Int>
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Signature
if (name != other.name) return false
if (returns != other.returns) return false
if (!accessors.contentEquals(other.accessors)) return false
if (!parameters.contentEquals(other.parameters)) return false
if (!opcodes.contentEquals(other.opcodes)) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + returns.hashCode()
result = 31 * result + accessors.contentHashCode()
result = 31 * result + parameters.contentHashCode()
result = 31 * result + opcodes.contentHashCode()
return result
}
}

View File

@ -0,0 +1,12 @@
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<Signature> {
return gson.fromJson(json, Array<Signature>::class.java)
}
}

View File

@ -0,0 +1,25 @@
package net.revanced.patcher.signature.model
import org.objectweb.asm.Type
import org.objectweb.asm.tree.ParameterNode
/**
* An ASM signature list for the Patcher.
*
* @param name The name of the method.
* Do not use the actual method name, instead try to guess what the method name originally was.
* If you are unable to guess a method name, doing something like "patch-name-1" is fine too.
* For example: "override-codec-1".
* 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.
*/
data class Signature(
val name: String,
val returns: Type,
@Suppress("ArrayInDataClass") val accessors: Int,
@Suppress("ArrayInDataClass") val parameters: Array<ParameterNode>,
@Suppress("ArrayInDataClass") val opcodes: Array<Int>
)

View File

@ -1,9 +0,0 @@
package net.revanced.patcher.store
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
class ASMStore {
val classes: MutableMap<String, ClassNode> = mutableMapOf()
val methods: MutableMap<String, MethodNode> = mutableMapOf()
}

View File

@ -1,13 +0,0 @@
package net.revanced.patcher.store
import net.revanced.patcher.patch.Patch
class PatchStore {
val patches: MutableMap<String, Patch> = mutableMapOf()
fun addPatches(vararg patches: Patch) {
for (patch in patches) {
this.patches[patch.name] = patch
}
}
}

View File

@ -1,18 +0,0 @@
package net.revanced.patcher.util
import org.objectweb.asm.tree.ClassNode
import java.io.InputStream
import java.util.jar.JarInputStream
object Jar2ASM {
fun jar2asm(input: InputStream): Map<String, ClassNode> {
return buildMap {
val jar = JarInputStream(input)
var e = jar.nextJarEntry
while (e != null) {
TODO("Read jar file ...")
e = jar.nextJarEntry
}
}
}
}

View File

@ -0,0 +1,11 @@
package net.revanced.patcher.util
import org.objectweb.asm.tree.ClassNode
class PatternScanner (private val classes: Array<ClassNode>) {
companion object {
fun scan(){
}
}
}

View File

@ -2,14 +2,29 @@ package net.revanced.patcher
import net.revanced.patcher.patch.Patch import net.revanced.patcher.patch.Patch
import net.revanced.patcher.patch.PatchResultError import net.revanced.patcher.patch.PatchResultError
import net.revanced.patcher.patch.PatchResultSuccess
import net.revanced.patcher.signature.SignatureLoader
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
internal class PatcherTest { internal class PatcherTest {
@Test @Test
fun template() { fun template() {
val adRemoverPatch = Patch { val patcher = Patcher.loadFromFile(
"some.apk",
SignatureLoader.LoadFromJson("signatures.json").toMutableList()
)
PatchResultError("") val patches = mutableListOf(
} Patch ("RemoveVideoAds") {
val videoAdShowMethodInstr = patcher.cache.Methods["SomeMethod"]?.instructions
PatchResultSuccess()
},
Patch ("TweakLayout") {
val layoutMethod = patcher.cache.Methods["SomeMethod2"]
PatchResultError("Failed")
}
)
patcher.executePatches()
} }
} }