mirror of
https://github.com/revanced/revanced-patcher.git
synced 2025-05-29 13:20:13 +02:00
Add: MethodResolver
, PatternScanner
, SignatureLoader
& Cache
This commit is contained in:
parent
be18b837ba
commit
6666c7a4b7
@ -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 {
|
||||
|
@ -1,2 +1,2 @@
|
||||
rootProject.name = "patcher"
|
||||
rootProject.name = "ReVanced Patcher"
|
||||
|
||||
|
38
src/main/kotlin/net/revanced/patcher/MethodResolver.kt
Normal file
38
src/main/kotlin/net/revanced/patcher/MethodResolver.kt
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reducing to required methods via signatures
|
||||
cache.Methods = MethodResolver(targetMethods, signatures).resolve()
|
||||
|
||||
}
|
||||
|
||||
fun scan() {
|
||||
val methods = PatternScanner(signatures).resolve()
|
||||
companion object {
|
||||
fun loadFromFile(file: String, signatures: List<Signature>): Patcher = Patcher(File(file), signatures)
|
||||
}
|
||||
|
||||
fun patch(): String? {
|
||||
if (!scanned) throw IllegalStateException("Pattern scanner not yet ran")
|
||||
for ((_, patch) in patchStore.patches) {
|
||||
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))
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
7
src/main/kotlin/net/revanced/patcher/cache/Cache.kt
vendored
Normal file
7
src/main/kotlin/net/revanced/patcher/cache/Cache.kt
vendored
Normal 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()
|
||||
)
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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>
|
||||
)
|
@ -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()
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
src/main/kotlin/net/revanced/patcher/util/PatternScanner.kt
Normal file
11
src/main/kotlin/net/revanced/patcher/util/PatternScanner.kt
Normal 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(){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user