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 {
|
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 {
|
||||||
|
@ -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
|
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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
|
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()
|
||||||
}
|
}
|
||||||
|
@ -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.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()
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user