1
mirror of https://github.com/revanced/revanced-patcher synced 2025-09-06 16:38:50 +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)

View File

@@ -1,2 +1,2 @@
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
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 }

View File

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

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,
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(),

View File

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

View File

@@ -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 ", "
}
}