1
mirror of https://github.com/revanced/revanced-patcher synced 2025-09-13 18:30:49 +02:00

Compare commits

...

13 Commits

Author SHA1 Message Date
semantic-release-bot
6b1337e4fc chore(release): 1.0.0-dev.7 [skip ci]
# [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 cd57a8c9a0 ([1af31b2](1af31b2aa3))
2022-03-24 22:31:58 +00:00
Lucaskyy
f4589db3a9 test: fix assert message 2022-03-24 23:31:01 +01:00
Lucaskyy
1af31b2aa3 fix(MethodResolver): fix cd57a8c9a0 2022-03-24 23:29:32 +01:00
semantic-release-bot
14f7667156 chore(release): 1.0.0-dev.6 [skip ci]
# [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](cd57a8c9a0))
2022-03-24 21:49:47 +00:00
Lucaskyy
cd57a8c9a0 fix(MethodResolver): strip labels nodes so opcode patterns match
this commit is also a fix for 8d1bb5f3d9 because it corrupted the stack by completely removing the nodes
2022-03-24 22:48:34 +01:00
Lucaskyy
0d3beb353d Merge remote-tracking branch 'origin/dev' into dev 2022-03-24 21:38:22 +01:00
Lucaskyy
ddef338631 refactor: log as trace instead of debug
so there's less spam in console
2022-03-24 21:38:13 +01:00
semantic-release-bot
fc4b673087 chore(release): 1.0.0-dev.5 [skip ci]
# [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](8d1bb5f3d9))
2022-03-24 20:30:54 +00:00
Lucaskyy
8d1bb5f3d9 fix(MethodResolver): strip labels and line numbers so opcode patterns match 2022-03-24 21:27:44 +01:00
Lucaskyy
c8a017a4c0 refactor: only compute maxs and use existing stack frames 2022-03-24 19:45:13 +01:00
semantic-release-bot
51fb59a43c chore(release): 1.0.0-dev.4 [skip ci]
# [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](e8f6973938))
2022-03-23 22:02:18 +00:00
Lucaskyy
a78715133c Merge remote-tracking branch 'origin/dev' into dev 2022-03-23 23:01:20 +01:00
Lucaskyy
e8f6973938 fix: give ClassWriter a ClassReader for symtable
removed SafeClassWriter as it was unused
2022-03-23 23:01:13 +01:00
8 changed files with 84 additions and 178 deletions

View File

@@ -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.0.0-dev.3](https://github.com/ReVancedTeam/revanced-patcher/compare/v1.0.0-dev.2...v1.0.0-dev.3) (2022-03-23)

View File

@@ -1,2 +1,2 @@
kotlin.code.style = official kotlin.code.style = official
version = 1.0.0-dev.3 version = 1.0.0-dev.7

View File

@@ -1,15 +1,13 @@
package app.revanced.patcher.resolver package app.revanced.patcher.resolver
import mu.KotlinLogging
import app.revanced.patcher.cache.MethodMap import app.revanced.patcher.cache.MethodMap
import app.revanced.patcher.cache.PatchData import app.revanced.patcher.cache.PatchData
import app.revanced.patcher.cache.PatternScanData import app.revanced.patcher.cache.PatternScanData
import app.revanced.patcher.signature.Signature import app.revanced.patcher.signature.Signature
import app.revanced.patcher.util.ExtraTypes import app.revanced.patcher.util.ExtraTypes
import mu.KotlinLogging
import org.objectweb.asm.Type import org.objectweb.asm.Type
import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.*
import org.objectweb.asm.tree.InsnList
import org.objectweb.asm.tree.MethodNode
private val logger = KotlinLogging.logger("MethodResolver") 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 (method in methods) {
for (signature in signatures) { for (signature in signatures) {
if (methodMap.containsKey(signature.name)) { // method already found for this sig 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 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) val (r, sr) = cmp(method, signature)
if (!r || sr == null) { 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 continue
} }
logger.debug { "Method for sig ${signature.name} found!" } logger.trace { "Method for sig ${signature.name} found!" }
methodMap[signature.name] = PatchData( methodMap[signature.name] = PatchData(
classNode, classNode,
method, method,
@@ -71,7 +69,7 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
signature.returns?.let { _ -> signature.returns?.let { _ ->
val methodReturns = Type.getReturnType(method.desc).convertObject() val methodReturns = Type.getReturnType(method.desc).convertObject()
if (signature.returns != methodReturns) { if (signature.returns != methodReturns) {
logger.debug { logger.trace {
""" """
Comparing sig ${signature.name}: invalid return type: Comparing sig ${signature.name}: invalid return type:
expected ${signature.returns}, expected ${signature.returns},
@@ -84,7 +82,7 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
signature.accessors?.let { _ -> signature.accessors?.let { _ ->
if (signature.accessors != method.access) { if (signature.accessors != method.access) {
logger.debug { logger.trace {
""" """
Comparing sig ${signature.name}: invalid accessors: Comparing sig ${signature.name}: invalid accessors:
expected ${signature.accessors}, expected ${signature.accessors},
@@ -98,7 +96,7 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
signature.parameters?.let { _ -> signature.parameters?.let { _ ->
val parameters = Type.getArgumentTypes(method.desc).convertObjects() val parameters = Type.getArgumentTypes(method.desc).convertObjects()
if (!signature.parameters.contentEquals(parameters)) { if (!signature.parameters.contentEquals(parameters)) {
logger.debug { logger.trace {
""" """
Comparing sig ${signature.name}: invalid parameter types: Comparing sig ${signature.name}: invalid parameter types:
expected ${signature.parameters.joinToString()}}, expected ${signature.parameters.joinToString()}},
@@ -112,7 +110,7 @@ internal class MethodResolver(private val classList: List<ClassNode>, private va
signature.opcodes?.let { _ -> signature.opcodes?.let { _ ->
val result = method.instructions.scanFor(signature.opcodes) val result = method.instructions.scanFor(signature.opcodes)
if (!result.found) { 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 false to null
} }
return@cmp true to result 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 { private operator fun ClassNode.component1() = this
return this private operator fun ClassNode.component2() = this.methods
}
private operator fun ClassNode.component2(): List<MethodNode> {
return this.methods
}
private fun InsnList.scanFor(pattern: IntArray): ScanResult { private fun InsnList.scanFor(pattern: IntArray): ScanResult {
for (i in 0 until this.size()) { for (i in 0 until this.size()) {
var occurrence = 0 var occurrence = 0
while (i + occurrence < this.size()) { 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) { if (++occurrence >= pattern.size) {
val current = i + occurrence val current = i + occurrence
return ScanResult(true, current - pattern.size, current) return ScanResult(true, current - pattern.size, current)
@@ -157,3 +157,6 @@ private fun Type.convertObject(): Type {
private fun Array<Type>.convertObjects(): Array<Type> { private fun Array<Type>.convertObjects(): Array<Type> {
return this.map { it.convertObject() }.toTypedArray() return this.map { it.convertObject() }.toTypedArray()
} }
private fun AbstractInsnNode.anyOf(vararg types: Class<*>): Boolean =
types.any { this@anyOf.javaClass == it }

View File

@@ -1,6 +1,5 @@
package app.revanced.patcher.util package app.revanced.patcher.util
import app.revanced.patcher.writer.SafeClassWriter
import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter import org.objectweb.asm.ClassWriter
import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.ClassNode
@@ -50,21 +49,21 @@ internal class Io(
fun saveAsJar() { fun saveAsJar() {
val jis = ZipInputStream(bufferedInputStream) val jis = ZipInputStream(bufferedInputStream)
val jos = ZipOutputStream(output) 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 // 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 // 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. // TODO(oSumAtrIX): There is currently no way to remove non .class files.
lateinit var zipEntry: ZipEntry lateinit var zipEntry: ZipEntry
while (jis.nextEntry.also { if (it != null) zipEntry = it } != null) { while (jis.nextEntry.also { if (it != null) zipEntry = it } != null) {
// skip all class files because we added them in the loop above if (zipEntry.name.endsWith(".class")) {
// TODO(oSumAtrIX): Check for zipEntry.isDirectory classReaders[zipEntry.name] = ClassReader(jis.readBytes())
if (zipEntry.name.endsWith(".class")) continue 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.putNextEntry(ZipEntry(zipEntry))
jos.write(jis.readBytes()) jos.write(jis.readBytes())
// close the newly created zipEntry
jos.closeEntry() jos.closeEntry()
} }
@@ -76,12 +75,11 @@ internal class Io(
// now write all the patched classes to the output stream // now write all the patched classes to the output stream
for (patchedClass in classes) { for (patchedClass in classes) {
// create a new entry of the patched class // 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 // parse the patched class to a byte array and write it to the output stream
val cw: ClassWriter = SafeClassWriter( val cw = ClassWriter(classReaders[name]!!, ClassWriter.COMPUTE_MAXS)
ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS
)
patchedClass.accept(cw) patchedClass.accept(cw)
jos.write(cw.toByteArray()) jos.write(cw.toByteArray())

View File

@@ -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)
}
}

View File

@@ -39,8 +39,10 @@ internal class PatcherTest {
ACC_PUBLIC or ACC_STATIC, ACC_PUBLIC or ACC_STATIC,
arrayOf(ExtraTypes.ArrayAny), arrayOf(ExtraTypes.ArrayAny),
intArrayOf( intArrayOf(
GETSTATIC,
LDC, LDC,
INVOKEVIRTUAL INVOKEVIRTUAL,
RETURN
) )
) )
) )
@@ -66,7 +68,19 @@ internal class PatcherTest {
// Get the start index of our opcode pattern. // Get the start index of our opcode pattern.
// This will be the index of the LDC instruction. // This will be the index of the LDC instruction.
val startIndex = mainMethod.scanData.startIndex 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. // Create a new LDC node and replace the LDC instruction.
val stringNode = LdcInsnNode("Hello, ReVanced! Editing bytecode.") val stringNode = LdcInsnNode("Hello, ReVanced! Editing bytecode.")
instructions.setAt(startIndex, stringNode) instructions.setAt(startIndex, stringNode)
@@ -82,7 +96,7 @@ internal class PatcherTest {
GETSTATIC, GETSTATIC,
Type.getInternalName(System::class.java), // "java/lang/System" Type.getInternalName(System::class.java), // "java/lang/System"
"out", "out",
"L" + Type.getInternalName(PrintStream::class.java) // "Ljava/io/PrintStream" Type.getInternalName(PrintStream::class.java) // "java/io/PrintStream"
), ),
LdcInsnNode("Hello, ReVanced! Adding bytecode."), LdcInsnNode("Hello, ReVanced! Adding bytecode."),
MethodInsnNode( MethodInsnNode(
@@ -143,7 +157,9 @@ internal class PatcherTest {
fun `should not raise an exception if any signature member except the name is missing`() { fun `should not raise an exception if any signature member except the name is missing`() {
val sigName = "testMethod" 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( Patcher(
PatcherTest::class.java.getResourceAsStream("/test1.jar")!!, PatcherTest::class.java.getResourceAsStream("/test1.jar")!!,
ByteArrayOutputStream(), ByteArrayOutputStream(),

View File

@@ -7,6 +7,6 @@ internal class ReaderTest {
@Test @Test
fun `read jar containing multiple classes`() { fun `read jar containing multiple classes`() {
val testData = PatcherTest::class.java.getResourceAsStream("/test2.jar")!! 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
} }
} }

View File

@@ -38,7 +38,8 @@ private class NodeStringBuilder {
} }
override fun toString(): String { override fun toString(): String {
if (sb.isEmpty()) return ""
val s = sb.toString() 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 ", "
} }
} }