mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-11 22:06:18 +00:00
feat: Finish first patcher test
This commit is contained in:
@@ -55,4 +55,4 @@ class MutableMethod(method: Method) : Method, BaseMethodReference() {
|
|||||||
return MutableMethod(this)
|
return MutableMethod(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.cache.Cache
|
import app.revanced.patcher.cache.Cache
|
||||||
|
import app.revanced.patcher.extensions.addInstructions
|
||||||
import app.revanced.patcher.extensions.or
|
import app.revanced.patcher.extensions.or
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import app.revanced.patcher.patch.PatchResult
|
import app.revanced.patcher.patch.PatchResult
|
||||||
import app.revanced.patcher.patch.PatchResultSuccess
|
import app.revanced.patcher.patch.PatchResultSuccess
|
||||||
|
import app.revanced.patcher.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
import app.revanced.patcher.signature.MethodSignature
|
import app.revanced.patcher.signature.MethodSignature
|
||||||
|
import app.revanced.patcher.smali.asInstructions
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
import org.jf.dexlib2.AccessFlags
|
import org.jf.dexlib2.AccessFlags
|
||||||
import org.jf.dexlib2.Opcode
|
import org.jf.dexlib2.Opcode
|
||||||
|
import org.jf.dexlib2.builder.instruction.BuilderInstruction11x
|
||||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
|
import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
|
||||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction35c
|
import org.jf.dexlib2.immutable.ImmutableMethod
|
||||||
import org.jf.dexlib2.immutable.reference.ImmutableMethodReference
|
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
||||||
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
|
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -35,8 +40,8 @@ internal class PatcherTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testPatcher() {
|
fun testPatcher() {
|
||||||
val patcher = Patcher(
|
val patcher = Patcher(
|
||||||
File(PatcherTest::class.java.getResource("/test1.apk")!!.toURI()),
|
File(PatcherTest::class.java.getResource("/test1.dex")!!.toURI()),
|
||||||
File("/"),
|
File("."),
|
||||||
testSignatures
|
testSignatures
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,7 +49,7 @@ internal class PatcherTest {
|
|||||||
object : Patch("TestPatch") {
|
object : Patch("TestPatch") {
|
||||||
override fun execute(cache: Cache): PatchResult {
|
override fun execute(cache: Cache): PatchResult {
|
||||||
// Get the result from the resolver cache
|
// Get the result from the resolver cache
|
||||||
val result = cache.resolvedMethods["main-method"]
|
val result = cache.methodMap["main-method"]
|
||||||
// Get the implementation for the resolved method
|
// Get the implementation for the resolved method
|
||||||
val implementation = result.resolveAndGetMethod().implementation!!
|
val implementation = result.resolveAndGetMethod().implementation!!
|
||||||
// Let's modify it, so it prints "Hello, ReVanced! Editing bytecode."
|
// Let's modify it, so it prints "Hello, ReVanced! Editing bytecode."
|
||||||
@@ -52,47 +57,86 @@ internal class PatcherTest {
|
|||||||
// This will be the index of the instruction with the opcode CONST_STRING.
|
// This will be the index of the instruction with the opcode CONST_STRING.
|
||||||
val startIndex = result.scanData.startIndex
|
val startIndex = result.scanData.startIndex
|
||||||
|
|
||||||
// the instruction format can be found via the docs at https://source.android.com/devices/tech/dalvik/dalvik-bytecode
|
// Replace the instruction at index startIndex with a new instruction.
|
||||||
// in our case we want an instruction with the opcode CONST_STRING and the string "Hello, ReVanced! Editing bytecode."
|
// The instruction format can be found in the docs at
|
||||||
// the format is 21c, so we create a new BuilderInstruction21c
|
// https://source.android.com/devices/tech/dalvik/dalvik-bytecode
|
||||||
// with the opcode CONST_STRING and the string "Hello, ReVanced! Editing bytecode."
|
//
|
||||||
// This instruction will store the constant string reference in the register v1
|
// In our case we want an instruction with the opcode CONST_STRING
|
||||||
// For that a reference to the string is needed. It can be created by creating a ImmutableStringReference
|
// and the string "Hello, ReVanced! Adding bytecode.".
|
||||||
val stringInstruction1 = BuilderInstruction21c(
|
// The format is 21c, so we create a new BuilderInstruction21c
|
||||||
Opcode.CONST_STRING,
|
// This instruction will hold the string reference constant in the virtual register 1.
|
||||||
1,
|
// For that a reference to the string is needed. It can be created with an ImmutableStringReference.
|
||||||
ImmutableStringReference("Hello, ReVanced! Editing bytecode.")
|
// At last, use the method replaceInstruction to replace it at the given index startIndex.
|
||||||
)
|
implementation.replaceInstruction(
|
||||||
|
startIndex,
|
||||||
// Replace the instruction at index startIndex with a new instruction
|
BuilderInstruction21c(
|
||||||
// We make sure to use this method to handle references to it via labels in any case
|
Opcode.CONST_STRING,
|
||||||
// If we are sure that the instruction is not referenced by any label, we can use the index operator overload
|
1,
|
||||||
// of the instruction list:
|
ImmutableStringReference("Hello, ReVanced! Editing bytecode.")
|
||||||
// implementation.instructions[startIndex] = instruction
|
|
||||||
implementation.replaceInstruction(startIndex, stringInstruction1)
|
|
||||||
|
|
||||||
// Now lets print our string twice!
|
|
||||||
|
|
||||||
// Create the necessary instructions (we could also clone the existing ones)
|
|
||||||
val stringInstruction2 = BuilderInstruction21c(
|
|
||||||
Opcode.CONST_STRING,
|
|
||||||
1,
|
|
||||||
ImmutableStringReference("Hello, ReVanced! Adding bytecode.")
|
|
||||||
)
|
|
||||||
val invokeInstruction = BuilderInstruction35c(
|
|
||||||
Opcode.INVOKE_VIRTUAL,
|
|
||||||
2, 0, 1, 0, 0, 0,
|
|
||||||
ImmutableMethodReference(
|
|
||||||
"Ljava.io.PrintStream;",
|
|
||||||
"println",
|
|
||||||
setOf("Ljava/lang/String;"),
|
|
||||||
"V"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Insert our instructions after the second instruction by our pattern.
|
// Get the class in which the method matching our signature is defined in.
|
||||||
implementation.addInstruction(startIndex + 1, stringInstruction2)
|
val mainClass = cache.findClass {
|
||||||
implementation.addInstruction(startIndex + 3, invokeInstruction)
|
it.type == result.definingClassProxy.immutableClass.type
|
||||||
|
}!!.resolve()
|
||||||
|
|
||||||
|
// Add a new method returning a string
|
||||||
|
mainClass.methods.add(
|
||||||
|
ImmutableMethod(
|
||||||
|
result.definingClassProxy.immutableClass.type,
|
||||||
|
"returnHello",
|
||||||
|
null,
|
||||||
|
"Ljava/lang/String;",
|
||||||
|
AccessFlags.PRIVATE or AccessFlags.STATIC,
|
||||||
|
null,
|
||||||
|
ImmutableMethodImplementation(
|
||||||
|
1,
|
||||||
|
ImmutableList.of(
|
||||||
|
BuilderInstruction21c(
|
||||||
|
Opcode.CONST_STRING,
|
||||||
|
0,
|
||||||
|
ImmutableStringReference("Hello, ReVanced! Adding bytecode.")
|
||||||
|
),
|
||||||
|
BuilderInstruction11x(Opcode.RETURN_OBJECT, 0)
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
).toMutable()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Now lets create a new call to our method and print the return value!
|
||||||
|
// You can also use the smali compiler to create instructions.
|
||||||
|
// For this sake of example I reuse the class field System.out inside the virtual register 0.
|
||||||
|
// Instead an additional instruction could be added at first to re-set this register.
|
||||||
|
// "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;"
|
||||||
|
//
|
||||||
|
// Control flow instructions are not supported as of now.
|
||||||
|
val instructions = """
|
||||||
|
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
|
||||||
|
move-result-object v1
|
||||||
|
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||||
|
""".trimIndent().asInstructions()
|
||||||
|
implementation.addInstructions(startIndex + 2, instructions)
|
||||||
|
|
||||||
|
// TODO: check TODO of the MutableEncodedValue class
|
||||||
|
//mainClass.fields.add(
|
||||||
|
// ImmutableField(
|
||||||
|
// mainClass.type,
|
||||||
|
// "dummyField",
|
||||||
|
// "Ljava/io/PrintStream",
|
||||||
|
// AccessFlags.PRIVATE or AccessFlags.STATIC,
|
||||||
|
// ImmutableFieldEncodedValue(
|
||||||
|
// ImmutableFieldReference(
|
||||||
|
// "Ljava/lang/System;",
|
||||||
|
// "out",
|
||||||
|
// "Ljava/io/PrintStream;"
|
||||||
|
// )
|
||||||
|
// ),
|
||||||
|
// null
|
||||||
|
// ).toMutable()
|
||||||
|
//)
|
||||||
|
|
||||||
// Finally, tell the patcher that this patch was a success.
|
// Finally, tell the patcher that this patch was a success.
|
||||||
// You can also return PatchResultError with a message.
|
// You can also return PatchResultError with a message.
|
||||||
@@ -118,11 +162,11 @@ internal class PatcherTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `test patcher with no changes`() {
|
fun `test patcher with no changes`() {
|
||||||
Patcher(
|
Patcher(
|
||||||
File(PatcherTest::class.java.getResource("/test1.apk")!!.toURI()),
|
File(PatcherTest::class.java.getResource("/test1.dex")!!.toURI()),
|
||||||
File("/no-changes-test"),
|
File("."),
|
||||||
testSignatures
|
testSignatures
|
||||||
).save()
|
).save()
|
||||||
// FIXME(Sculas): There seems to be a 1-byte difference, not sure what it is.
|
// FIXME(Sculas): There seems to be a 1-byte difference, not sure what it is.
|
||||||
// assertEquals(available, out.size())
|
// assertEquals(available, out.size())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/test/resources/test1.dex
Normal file
BIN
src/test/resources/test1.dex
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/test/resources/test2.dex
Normal file
BIN
src/test/resources/test2.dex
Normal file
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user