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 {
implementation(kotlin("stdlib"))
testImplementation(kotlin("test"))
implementation("org.ow2.asm:asm:9.2")
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"))
}
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
import net.revanced.patcher.cache.Cache
import net.revanced.patcher.patch.Patch
import net.revanced.patcher.signature.Signature
import net.revanced.patcher.store.ASMStore
import net.revanced.patcher.store.PatchStore
import net.revanced.patcher.util.Jar2ASM
import java.io.InputStream
import java.lang.IllegalStateException
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 java.util.jar.JarFile
/**
* The patcher. (docs WIP)
*
* @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>,
class Patcher private constructor(
file: File,
signatures: List<Signature>
) {
private val patchStore = PatchStore()
private val asmStore = ASMStore()
private val scanned = false
val cache = Cache()
private val patches: MutableList<Patch> = mutableListOf()
init {
patchStore.addPatches(*patches)
loadJar(input)
// collecting all methods here
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)
}
}
}
fun scan() {
val methods = PatternScanner(signatures).resolve()
// reducing to required methods via signatures
cache.Methods = MethodResolver(targetMethods, signatures).resolve()
}
fun patch(): String? {
if (!scanned) throw IllegalStateException("Pattern scanner not yet ran")
for ((_, patch) in patchStore.patches) {
companion object {
fun loadFromFile(file: String, signatures: List<Signature>): Patcher = Patcher(File(file), signatures)
}
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()
}
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
class Patch(val name: String, val fn: () -> PatchResult) {
class Patch(val patchName: String, val fn: () -> PatchResult) {
fun execute(): PatchResult {
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.PatchResultError
import net.revanced.patcher.patch.PatchResultSuccess
import net.revanced.patcher.signature.SignatureLoader
import org.junit.jupiter.api.Test
internal class PatcherTest {
@Test
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()
}
}