You've already forked revanced-patcher
mirror of
https://github.com/revanced/revanced-patcher
synced 2025-09-06 16:38:50 +02:00
Compare commits
13 Commits
v1.0.0-dev
...
v1.0.0-dev
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6b1337e4fc | ||
![]() |
f4589db3a9 | ||
![]() |
1af31b2aa3 | ||
![]() |
14f7667156 | ||
![]() |
cd57a8c9a0 | ||
![]() |
0d3beb353d | ||
![]() |
ddef338631 | ||
![]() |
fc4b673087 | ||
![]() |
8d1bb5f3d9 | ||
![]() |
c8a017a4c0 | ||
![]() |
51fb59a43c | ||
![]() |
a78715133c | ||
![]() |
e8f6973938 |
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,3 +1,31 @@
|
||||
# [1.0.0-dev.7](https://github.com/ReVancedTeam/revanced-patcher/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MethodResolver:** fix cd57a8c9a0db7e3ae5ad0bca202e5955930319ab ([1af31b2](https://github.com/ReVancedTeam/revanced-patcher/commit/1af31b2aa3772a7473c04d27bf835c8eae13438d))
|
||||
|
||||
# [1.0.0-dev.6](https://github.com/ReVancedTeam/revanced-patcher/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MethodResolver:** strip labels nodes so opcode patterns match ([cd57a8c](https://github.com/ReVancedTeam/revanced-patcher/commit/cd57a8c9a0db7e3ae5ad0bca202e5955930319ab))
|
||||
|
||||
# [1.0.0-dev.5](https://github.com/ReVancedTeam/revanced-patcher/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MethodResolver:** strip labels and line numbers so opcode patterns match ([8d1bb5f](https://github.com/ReVancedTeam/revanced-patcher/commit/8d1bb5f3d9da544cf6e3e3848bfcc56327cde810))
|
||||
|
||||
# [1.0.0-dev.4](https://github.com/ReVancedTeam/revanced-patcher/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2022-03-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* give ClassWriter a ClassReader for symtable ([e8f6973](https://github.com/ReVancedTeam/revanced-patcher/commit/e8f6973938c70002f04a86f329aa5b134f6ef649))
|
||||
|
||||
# [1.0.0-dev.3](https://github.com/ReVancedTeam/revanced-patcher/compare/v1.0.0-dev.2...v1.0.0-dev.3) (2022-03-23)
|
||||
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
kotlin.code.style = official
|
||||
version = 1.0.0-dev.3
|
||||
version = 1.0.0-dev.7
|
||||
|
@@ -1,15 +1,13 @@
|
||||
package app.revanced.patcher.resolver
|
||||
|
||||
import mu.KotlinLogging
|
||||
import app.revanced.patcher.cache.MethodMap
|
||||
import app.revanced.patcher.cache.PatchData
|
||||
import app.revanced.patcher.cache.PatternScanData
|
||||
import app.revanced.patcher.signature.Signature
|
||||
import app.revanced.patcher.util.ExtraTypes
|
||||
import mu.KotlinLogging
|
||||
import org.objectweb.asm.Type
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.InsnList
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
import org.objectweb.asm.tree.*
|
||||
|
||||
private val logger = KotlinLogging.logger("MethodResolver")
|
||||
|
||||
@@ -21,16 +19,16 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
|
||||
for (method in methods) {
|
||||
for (signature in signatures) {
|
||||
if (methodMap.containsKey(signature.name)) { // method already found for this sig
|
||||
logger.debug { "Sig ${signature.name} already found, skipping." }
|
||||
logger.trace { "Sig ${signature.name} already found, skipping." }
|
||||
continue
|
||||
}
|
||||
logger.debug { "Resolving sig ${signature.name}: ${classNode.name} / ${method.name}" }
|
||||
logger.trace { "Resolving sig ${signature.name}: ${classNode.name} / ${method.name}" }
|
||||
val (r, sr) = cmp(method, signature)
|
||||
if (!r || sr == null) {
|
||||
logger.debug { "Compare result for sig ${signature.name} has failed!" }
|
||||
logger.trace { "Compare result for sig ${signature.name} has failed!" }
|
||||
continue
|
||||
}
|
||||
logger.debug { "Method for sig ${signature.name} found!" }
|
||||
logger.trace { "Method for sig ${signature.name} found!" }
|
||||
methodMap[signature.name] = PatchData(
|
||||
classNode,
|
||||
method,
|
||||
@@ -71,7 +69,7 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
|
||||
signature.returns?.let { _ ->
|
||||
val methodReturns = Type.getReturnType(method.desc).convertObject()
|
||||
if (signature.returns != methodReturns) {
|
||||
logger.debug {
|
||||
logger.trace {
|
||||
"""
|
||||
Comparing sig ${signature.name}: invalid return type:
|
||||
expected ${signature.returns},
|
||||
@@ -84,7 +82,7 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
|
||||
|
||||
signature.accessors?.let { _ ->
|
||||
if (signature.accessors != method.access) {
|
||||
logger.debug {
|
||||
logger.trace {
|
||||
"""
|
||||
Comparing sig ${signature.name}: invalid accessors:
|
||||
expected ${signature.accessors},
|
||||
@@ -98,7 +96,7 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
|
||||
signature.parameters?.let { _ ->
|
||||
val parameters = Type.getArgumentTypes(method.desc).convertObjects()
|
||||
if (!signature.parameters.contentEquals(parameters)) {
|
||||
logger.debug {
|
||||
logger.trace {
|
||||
"""
|
||||
Comparing sig ${signature.name}: invalid parameter types:
|
||||
expected ${signature.parameters.joinToString()}},
|
||||
@@ -112,7 +110,7 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
|
||||
signature.opcodes?.let { _ ->
|
||||
val result = method.instructions.scanFor(signature.opcodes)
|
||||
if (!result.found) {
|
||||
logger.debug { "Comparing sig ${signature.name}: invalid opcode pattern" }
|
||||
logger.trace { "Comparing sig ${signature.name}: invalid opcode pattern" }
|
||||
return@cmp false to null
|
||||
}
|
||||
return@cmp true to result
|
||||
@@ -123,19 +121,21 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun ClassNode.component1(): ClassNode {
|
||||
return this
|
||||
}
|
||||
|
||||
private operator fun ClassNode.component2(): List<MethodNode> {
|
||||
return this.methods
|
||||
}
|
||||
private operator fun ClassNode.component1() = this
|
||||
private operator fun ClassNode.component2() = this.methods
|
||||
|
||||
private fun InsnList.scanFor(pattern: IntArray): ScanResult {
|
||||
for (i in 0 until this.size()) {
|
||||
var occurrence = 0
|
||||
while (i + occurrence < this.size()) {
|
||||
if (this[i + occurrence].opcode != pattern[occurrence]) break
|
||||
val n = this[i + occurrence]
|
||||
if (
|
||||
!n.anyOf(
|
||||
LabelNode::class.java,
|
||||
LineNumberNode::class.java
|
||||
) &&
|
||||
n.opcode != pattern[occurrence]
|
||||
) break
|
||||
if (++occurrence >= pattern.size) {
|
||||
val current = i + occurrence
|
||||
return ScanResult(true, current - pattern.size, current)
|
||||
@@ -157,3 +157,6 @@ private fun Type.convertObject(): Type {
|
||||
private fun Array<Type>.convertObjects(): Array<Type> {
|
||||
return this.map { it.convertObject() }.toTypedArray()
|
||||
}
|
||||
|
||||
private fun AbstractInsnNode.anyOf(vararg types: Class<*>): Boolean =
|
||||
types.any { this@anyOf.javaClass == it }
|
@@ -1,6 +1,5 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.writer.SafeClassWriter
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
@@ -50,21 +49,21 @@ internal class Io(
|
||||
fun saveAsJar() {
|
||||
val jis = ZipInputStream(bufferedInputStream)
|
||||
val jos = ZipOutputStream(output)
|
||||
val classReaders = mutableMapOf<String, ClassReader>()
|
||||
|
||||
// first write all non .class zip entries from the original input stream to the output stream
|
||||
// we read it first to close the input stream as fast as possible
|
||||
// TODO(oSumAtrIX): There is currently no way to remove non .class files.
|
||||
lateinit var zipEntry: ZipEntry
|
||||
while (jis.nextEntry.also { if (it != null) zipEntry = it } != null) {
|
||||
// skip all class files because we added them in the loop above
|
||||
// TODO(oSumAtrIX): Check for zipEntry.isDirectory
|
||||
if (zipEntry.name.endsWith(".class")) continue
|
||||
if (zipEntry.name.endsWith(".class")) {
|
||||
classReaders[zipEntry.name] = ClassReader(jis.readBytes())
|
||||
continue
|
||||
}
|
||||
|
||||
// create a new zipEntry and write the contents of the zipEntry to the output stream
|
||||
// create a new zipEntry and write the contents of the zipEntry to the output stream and close it
|
||||
jos.putNextEntry(ZipEntry(zipEntry))
|
||||
jos.write(jis.readBytes())
|
||||
|
||||
// close the newly created zipEntry
|
||||
jos.closeEntry()
|
||||
}
|
||||
|
||||
@@ -76,12 +75,11 @@ internal class Io(
|
||||
// now write all the patched classes to the output stream
|
||||
for (patchedClass in classes) {
|
||||
// create a new entry of the patched class
|
||||
jos.putNextEntry(JarEntry(patchedClass.name + ".class"))
|
||||
val name = patchedClass.name + ".class"
|
||||
jos.putNextEntry(JarEntry(name))
|
||||
|
||||
// parse the patched class to a byte array and write it to the output stream
|
||||
val cw: ClassWriter = SafeClassWriter(
|
||||
ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS
|
||||
)
|
||||
val cw = ClassWriter(classReaders[name]!!, ClassWriter.COMPUTE_MAXS)
|
||||
patchedClass.accept(cw)
|
||||
jos.write(cw.toByteArray())
|
||||
|
||||
|
@@ -1,140 +0,0 @@
|
||||
package app.revanced.patcher.writer
|
||||
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.Opcodes
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* A ClassWriter that computes the common super class of two classes without
|
||||
* actually loading them with a ClassLoader.
|
||||
*
|
||||
* @author Eric Bruneton
|
||||
*/
|
||||
// TODO(Sculas): should we add the ClassReader parameter back?
|
||||
class SafeClassWriter(flags: Int) : ClassWriter(flags) {
|
||||
override fun getCommonSuperClass(type1: String, type2: String): String {
|
||||
try {
|
||||
val info1 = typeInfo(type1)
|
||||
val info2 = typeInfo(type2)
|
||||
if (info1.access and Opcodes.ACC_INTERFACE != 0) {
|
||||
return if (typeImplements(type2, info2, type1)) {
|
||||
type1
|
||||
} else {
|
||||
"java/lang/Object"
|
||||
}
|
||||
}
|
||||
if (info2.access and Opcodes.ACC_INTERFACE != 0) {
|
||||
return if (typeImplements(type1, info1, type2)) {
|
||||
type2
|
||||
} else {
|
||||
"java/lang/Object"
|
||||
}
|
||||
}
|
||||
val b1 = typeAncestors(type1, info1)
|
||||
val b2 = typeAncestors(type2, info2)
|
||||
var result = "java/lang/Object"
|
||||
var end1 = b1.length
|
||||
var end2 = b2.length
|
||||
while (true) {
|
||||
val start1 = b1.lastIndexOf(";", end1 - 1)
|
||||
val start2 = b2.lastIndexOf(";", end2 - 1)
|
||||
if (start1 != -1 && start2 != -1 && end1 - start1 == end2 - start2) {
|
||||
val p1 = b1.substring(start1 + 1, end1)
|
||||
val p2 = b2.substring(start2 + 1, end2)
|
||||
if (p1 == p2) {
|
||||
result = p1
|
||||
end1 = start1
|
||||
end2 = start2
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw RuntimeException(e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal names of the ancestor classes of the given type.
|
||||
*
|
||||
* @param _type
|
||||
* the internal name of a class or interface.
|
||||
* @param _info
|
||||
* the ClassReader corresponding to 'type'.
|
||||
* @return a StringBuilder containing the ancestor classes of 'type',
|
||||
* separated by ';'. The returned string has the following format:
|
||||
* ";type1;type2 ... ;typeN", where type1 is 'type', and typeN is a
|
||||
* direct subclass of Object. If 'type' is Object, the returned
|
||||
* string is empty.
|
||||
* @throws IOException
|
||||
* if the bytecode of 'type' or of some of its ancestor class
|
||||
* cannot be loaded.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun typeAncestors(_type: String, _info: ClassReader): StringBuilder {
|
||||
var type = _type
|
||||
var info = _info
|
||||
val b = StringBuilder()
|
||||
while ("java/lang/Object" != type) {
|
||||
b.append(';').append(type)
|
||||
type = info.superName
|
||||
info = typeInfo(type)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given type implements the given interface.
|
||||
*
|
||||
* @param _type
|
||||
* the internal name of a class or interface.
|
||||
* @param _info
|
||||
* the ClassReader corresponding to 'type'.
|
||||
* @param itf
|
||||
* the internal name of a interface.
|
||||
* @return true if 'type' implements directly or indirectly 'itf'
|
||||
* @throws IOException
|
||||
* if the bytecode of 'type' or of some of its ancestor class
|
||||
* cannot be loaded.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun typeImplements(_type: String, _info: ClassReader, itf: String): Boolean {
|
||||
var type = _type
|
||||
var info = _info
|
||||
while ("java/lang/Object" != type) {
|
||||
info.interfaces.forEach {
|
||||
if (it == itf) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
info.interfaces.forEach {
|
||||
if (typeImplements(it, typeInfo(it), itf)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
type = info.superName
|
||||
info = typeInfo(type)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ClassReader corresponding to the given class or interface.
|
||||
*
|
||||
* @param type
|
||||
* the internal name of a class or interface.
|
||||
* @return the ClassReader corresponding to 'type'.
|
||||
* @throws IOException
|
||||
* if the bytecode of 'type' cannot be loaded.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun typeInfo(type: String): ClassReader {
|
||||
val input = ClassLoader.getSystemClassLoader().getResourceAsStream("$type.class")
|
||||
?: throw IOException("Cannot create ClassReader for type $type")
|
||||
return input.use(::ClassReader)
|
||||
}
|
||||
}
|
@@ -39,8 +39,10 @@ internal class PatcherTest {
|
||||
ACC_PUBLIC or ACC_STATIC,
|
||||
arrayOf(ExtraTypes.ArrayAny),
|
||||
intArrayOf(
|
||||
GETSTATIC,
|
||||
LDC,
|
||||
INVOKEVIRTUAL
|
||||
INVOKEVIRTUAL,
|
||||
RETURN
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -66,7 +68,19 @@ internal class PatcherTest {
|
||||
// Get the start index of our opcode pattern.
|
||||
// This will be the index of the LDC instruction.
|
||||
val startIndex = mainMethod.scanData.startIndex
|
||||
TestUtil.assertNodeEqual(LdcInsnNode("Hello, world!"), instructions[startIndex]!!)
|
||||
|
||||
// Ignore this, just testing if the method resolver works :)
|
||||
TestUtil.assertNodeEqual(
|
||||
FieldInsnNode(
|
||||
GETSTATIC,
|
||||
Type.getInternalName(System::class.java),
|
||||
"out",
|
||||
// for whatever reason, it adds an "L" and ";" to the node string
|
||||
"L${Type.getInternalName(PrintStream::class.java)};"
|
||||
),
|
||||
instructions[startIndex]!!
|
||||
)
|
||||
|
||||
// Create a new LDC node and replace the LDC instruction.
|
||||
val stringNode = LdcInsnNode("Hello, ReVanced! Editing bytecode.")
|
||||
instructions.setAt(startIndex, stringNode)
|
||||
@@ -82,7 +96,7 @@ internal class PatcherTest {
|
||||
GETSTATIC,
|
||||
Type.getInternalName(System::class.java), // "java/lang/System"
|
||||
"out",
|
||||
"L" + Type.getInternalName(PrintStream::class.java) // "Ljava/io/PrintStream"
|
||||
Type.getInternalName(PrintStream::class.java) // "java/io/PrintStream"
|
||||
),
|
||||
LdcInsnNode("Hello, ReVanced! Adding bytecode."),
|
||||
MethodInsnNode(
|
||||
@@ -143,7 +157,9 @@ internal class PatcherTest {
|
||||
fun `should not raise an exception if any signature member except the name is missing`() {
|
||||
val sigName = "testMethod"
|
||||
|
||||
assertDoesNotThrow("Should raise an exception because opcodes is empty") {
|
||||
assertDoesNotThrow(
|
||||
"Should not raise an exception if any signature member except the name is missing"
|
||||
) {
|
||||
Patcher(
|
||||
PatcherTest::class.java.getResourceAsStream("/test1.jar")!!,
|
||||
ByteArrayOutputStream(),
|
||||
|
@@ -7,6 +7,6 @@ internal class ReaderTest {
|
||||
@Test
|
||||
fun `read jar containing multiple classes`() {
|
||||
val testData = PatcherTest::class.java.getResourceAsStream("/test2.jar")!!
|
||||
Patcher(testData, ByteArrayOutputStream(), PatcherTest.testSignatures) // reusing test sigs from PatcherTest
|
||||
Patcher(testData, ByteArrayOutputStream(), PatcherTest.testSignatures).save() // reusing test sigs from PatcherTest
|
||||
}
|
||||
}
|
@@ -38,7 +38,8 @@ private class NodeStringBuilder {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
if (sb.isEmpty()) return ""
|
||||
val s = sb.toString()
|
||||
return s.substring(0 until s.length - 2) // remove the last ", "
|
||||
return s.substring(0 .. (s.length - 2).coerceAtLeast(0)) // remove the last ", "
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user