mirror of
https://github.com/revanced/revanced-patcher.git
synced 2025-04-30 05:14:26 +02:00
Refactor Patcher
This commit is contained in:
parent
733fb6a3b8
commit
a9e7f19d51
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-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"))
|
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.cache.Cache
|
||||||
import net.revanced.patcher.patch.Patch
|
import net.revanced.patcher.patch.Patch
|
||||||
import net.revanced.patcher.signature.model.Signature
|
import net.revanced.patcher.patch.PatchResult
|
||||||
import org.objectweb.asm.ClassReader
|
import net.revanced.patcher.resolver.MethodResolver
|
||||||
import org.objectweb.asm.tree.ClassNode
|
import net.revanced.patcher.signature.Signature
|
||||||
import org.objectweb.asm.tree.MethodNode
|
import net.revanced.patcher.util.Jar2ASM
|
||||||
import java.io.File
|
import java.io.InputStream
|
||||||
import java.util.jar.JarFile
|
import java.util.jar.JarFile
|
||||||
|
|
||||||
class Patcher private constructor(
|
/**
|
||||||
file: File,
|
* The patcher. (docs WIP)
|
||||||
signatures: List<Signature>
|
*
|
||||||
|
* @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()
|
val cache = Cache()
|
||||||
private val patches: MutableList<Patch> = mutableListOf()
|
private val patches: MutableList<Patch> = mutableListOf()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// collecting all methods here
|
cache.methods.putAll(MethodResolver(Jar2ASM.jar2asm(input), signatures).resolve())
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addPatches(vararg patches: Patch) {
|
fun addPatches(vararg patches: Patch) {
|
||||||
this.patches.addAll(patches)
|
this.patches.addAll(patches)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun executePatches(): String? {
|
fun executePatches(): Map<String, Result<Nothing?>> {
|
||||||
for (patch in patches) {
|
return buildMap {
|
||||||
val result = patch.execute()
|
for (patch in patches) {
|
||||||
if (result.isSuccess()) continue
|
val result: Result<Nothing?> = try {
|
||||||
return result.error()!!.errorMessage()
|
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
|
package net.revanced.patcher.cache
|
||||||
|
|
||||||
import org.objectweb.asm.tree.MethodNode
|
|
||||||
|
|
||||||
data class Cache(
|
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.Type
|
||||||
import org.objectweb.asm.tree.ParameterNode
|
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.
|
* This method name will be used to find the corresponding patch.
|
||||||
* @param returns The return type/signature of the method.
|
* @param returns The return type/signature of the method.
|
||||||
* @param accessors The accessors of the method.
|
* @param accessors The accessors of the method.
|
||||||
* @param parameters The parameter types/signatures of the method.
|
* @param parameters The parameter types of the method.
|
||||||
* @param opcodes The opcode pattern of the method, used to find the method by signature scanning.
|
* @param opcodes The opcode pattern of the method, used to find the method by pattern scanning.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("ArrayInDataClass")
|
||||||
data class Signature(
|
data class Signature(
|
||||||
val name: String,
|
val name: String,
|
||||||
val returns: Type,
|
val returns: Type,
|
||||||
@Suppress("ArrayInDataClass") val accessors: Int,
|
val accessors: Int,
|
||||||
@Suppress("ArrayInDataClass") val parameters: Array<ParameterNode>,
|
val parameters: Array<Type>,
|
||||||
@Suppress("ArrayInDataClass") val opcodes: Array<Int>
|
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
|
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.PatchResultSuccess
|
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.junit.jupiter.api.Test
|
||||||
|
import org.objectweb.asm.Opcodes.*
|
||||||
|
import org.objectweb.asm.Type
|
||||||
|
|
||||||
internal class PatcherTest {
|
internal class PatcherTest {
|
||||||
@Test
|
private val testSigs: Array<Signature> = arrayOf(
|
||||||
fun template() {
|
Signature(
|
||||||
val patcher = Patcher.loadFromFile(
|
"testMethod",
|
||||||
"some.apk",
|
Type.BOOLEAN_TYPE,
|
||||||
SignatureLoader.LoadFromJson("signatures.json").toMutableList()
|
ACC_PUBLIC or ACC_STATIC,
|
||||||
|
arrayOf(
|
||||||
|
ExtraTypes.ArrayAny,
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
GETSTATIC,
|
||||||
|
LDC,
|
||||||
|
INVOKEVIRTUAL
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val patches = mutableListOf(
|
@Test
|
||||||
Patch ("RemoveVideoAds") {
|
fun testPatcher() {
|
||||||
val videoAdShowMethodInstr = patcher.cache.Methods["SomeMethod"]?.instructions
|
val testData = PatcherTest::class.java.getResourceAsStream("/test1.jar")!!
|
||||||
|
val patcher = Patcher(testData, testSigs)
|
||||||
|
|
||||||
|
patcher.addPatches(
|
||||||
|
Patch ("TestPatch") {
|
||||||
|
patcher.cache.methods["testMethod"]
|
||||||
PatchResultSuccess()
|
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