mirror of
https://github.com/revanced/revanced-patcher
synced 2025-02-17 22:21:25 +01:00
Refactor Patcher
This commit is contained in:
parent
733fb6a3b8
commit
a9e7f19d51
.idea
build.gradle.ktssettings.gradle.ktssrc
main/kotlin/net/revanced/patcher
test/kotlin/net/revanced/patcher
6
.idea/compiler.xml
generated
6
.idea/compiler.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
20
.idea/jarRepositories.xml
generated
20
.idea/jarRepositories.xml
generated
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="MavenRepo" />
|
||||
<option name="name" value="MavenRepo" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
10
.idea/runConfigurations.xml
generated
10
.idea/runConfigurations.xml
generated
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -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"))
|
||||
}
|
||||
|
||||
|
@ -1,2 +1 @@
|
||||
rootProject.name = "ReVanced Patcher"
|
||||
|
||||
rootProject.name = "revanced-patcher"
|
||||
|
@ -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<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
|
||||
}
|
@ -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<Signature>
|
||||
/**
|
||||
* 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<Signature>,
|
||||
) {
|
||||
val cache = Cache()
|
||||
private val patches: MutableList<Patch> = mutableListOf()
|
||||
|
||||
init {
|
||||
// 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()
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun loadFromFile(file: String, signatures: List<Signature>): 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<String, Result<Nothing?>> {
|
||||
return buildMap {
|
||||
for (patch in patches) {
|
||||
val result: Result<Nothing?> = 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
|
||||
}
|
||||
}
|
@ -1,7 +1,13 @@
|
||||
package net.revanced.patcher.cache
|
||||
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
data class Cache(
|
||||
var Methods: Map<String, MethodNode> = mutableMapOf()
|
||||
val methods: MethodMap = MethodMap()
|
||||
)
|
||||
|
||||
class MethodMap : LinkedHashMap<String, PatchData>() {
|
||||
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)
|
||||
|
9
src/main/kotlin/net/revanced/patcher/cache/PatchData.kt
vendored
Normal file
9
src/main/kotlin/net/revanced/patcher/cache/PatchData.kt
vendored
Normal file
@ -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
|
||||
)
|
@ -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<ClassNode>, private val signatures: Array<Signature>) {
|
||||
fun resolve(): MutableMap<String, PatchData> {
|
||||
val patchData = mutableMapOf<String, PatchData>()
|
||||
|
||||
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<MethodNode> {
|
||||
return this.methods
|
||||
}
|
||||
|
||||
private fun InsnList.scanFor(pattern: Array<Int>): 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)
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package net.revanced.patcher.resolver
|
||||
|
||||
internal data class ScanResult(
|
||||
val found: Boolean,
|
||||
val startIndex: Int? = 0,
|
||||
val endIndex: Int? = 0
|
||||
)
|
@ -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<ParameterNode>,
|
||||
@Suppress("ArrayInDataClass") val opcodes: Array<Int>
|
||||
val accessors: Int,
|
||||
val parameters: Array<Type>,
|
||||
val opcodes: Array<Int>
|
||||
)
|
@ -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<Signature> {
|
||||
return gson.fromJson(json, Array<Signature>::class.java)
|
||||
}
|
||||
}
|
12
src/main/kotlin/net/revanced/patcher/util/ExtraTypes.kt
Normal file
12
src/main/kotlin/net/revanced/patcher/util/ExtraTypes.kt
Normal file
@ -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<String>::class.java)
|
||||
}
|
22
src/main/kotlin/net/revanced/patcher/util/Jar2ASM.kt
Normal file
22
src/main/kotlin/net/revanced/patcher/util/Jar2ASM.kt
Normal file
@ -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<ClassNode> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package net.revanced.patcher.util
|
||||
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
|
||||
class PatternScanner (private val classes: Array<ClassNode>) {
|
||||
companion object {
|
||||
fun scan(){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Signature> = 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()!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user