diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableMethod.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableMethod.kt index b35a52d..cf721e5 100644 --- a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableMethod.kt +++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableMethod.kt @@ -55,4 +55,4 @@ class MutableMethod(method: Method) : Method, BaseMethodReference() { return MutableMethod(this) } } -} +} \ No newline at end of file diff --git a/src/test/kotlin/app/revanced/patcher/PatcherTest.kt b/src/test/kotlin/app/revanced/patcher/PatcherTest.kt index 9987085..0a2cedf 100644 --- a/src/test/kotlin/app/revanced/patcher/PatcherTest.kt +++ b/src/test/kotlin/app/revanced/patcher/PatcherTest.kt @@ -1,16 +1,21 @@ package app.revanced.patcher import app.revanced.patcher.cache.Cache +import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.or import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.PatchResult 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.smali.asInstructions +import com.google.common.collect.ImmutableList import org.jf.dexlib2.AccessFlags 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.BuilderInstruction35c -import org.jf.dexlib2.immutable.reference.ImmutableMethodReference +import org.jf.dexlib2.immutable.ImmutableMethod +import org.jf.dexlib2.immutable.ImmutableMethodImplementation import org.jf.dexlib2.immutable.reference.ImmutableStringReference import org.junit.jupiter.api.Test import java.io.File @@ -35,8 +40,8 @@ internal class PatcherTest { @Test fun testPatcher() { val patcher = Patcher( - File(PatcherTest::class.java.getResource("/test1.apk")!!.toURI()), - File("/"), + File(PatcherTest::class.java.getResource("/test1.dex")!!.toURI()), + File("."), testSignatures ) @@ -44,7 +49,7 @@ internal class PatcherTest { object : Patch("TestPatch") { override fun execute(cache: Cache): PatchResult { // 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 val implementation = result.resolveAndGetMethod().implementation!! // 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. val startIndex = result.scanData.startIndex - // the instruction format can be found via the docs at https://source.android.com/devices/tech/dalvik/dalvik-bytecode - // in our case we want an instruction with the opcode CONST_STRING and the string "Hello, ReVanced! Editing bytecode." - // the format is 21c, so we create a new BuilderInstruction21c - // with the opcode CONST_STRING and the string "Hello, ReVanced! Editing bytecode." - // This instruction will store the constant string reference in the register v1 - // For that a reference to the string is needed. It can be created by creating a ImmutableStringReference - val stringInstruction1 = BuilderInstruction21c( - Opcode.CONST_STRING, - 1, - ImmutableStringReference("Hello, ReVanced! Editing bytecode.") - ) - - // Replace the instruction at index startIndex with a new instruction - // We make sure to use this method to handle references to it via labels in any case - // If we are sure that the instruction is not referenced by any label, we can use the index operator overload - // of the instruction list: - // 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" + // Replace the instruction at index startIndex with a new instruction. + // The instruction format can be found in the docs at + // https://source.android.com/devices/tech/dalvik/dalvik-bytecode + // + // In our case we want an instruction with the opcode CONST_STRING + // and the string "Hello, ReVanced! Adding bytecode.". + // The format is 21c, so we create a new BuilderInstruction21c + // This instruction will hold the string reference constant in the virtual register 1. + // For that a reference to the string is needed. It can be created with an ImmutableStringReference. + // At last, use the method replaceInstruction to replace it at the given index startIndex. + implementation.replaceInstruction( + startIndex, + BuilderInstruction21c( + Opcode.CONST_STRING, + 1, + ImmutableStringReference("Hello, ReVanced! Editing bytecode.") ) ) - // Insert our instructions after the second instruction by our pattern. - implementation.addInstruction(startIndex + 1, stringInstruction2) - implementation.addInstruction(startIndex + 3, invokeInstruction) + // Get the class in which the method matching our signature is defined in. + val mainClass = cache.findClass { + 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. // You can also return PatchResultError with a message. @@ -118,11 +162,11 @@ internal class PatcherTest { @Test fun `test patcher with no changes`() { Patcher( - File(PatcherTest::class.java.getResource("/test1.apk")!!.toURI()), - File("/no-changes-test"), + File(PatcherTest::class.java.getResource("/test1.dex")!!.toURI()), + File("."), testSignatures ).save() // FIXME(Sculas): There seems to be a 1-byte difference, not sure what it is. // assertEquals(available, out.size()) } -} \ No newline at end of file +} diff --git a/src/test/resources/test1.dex b/src/test/resources/test1.dex new file mode 100644 index 0000000..6bf4069 Binary files /dev/null and b/src/test/resources/test1.dex differ diff --git a/src/test/resources/test1.jar b/src/test/resources/test1.jar deleted file mode 100644 index 33dee5f..0000000 Binary files a/src/test/resources/test1.jar and /dev/null differ diff --git a/src/test/resources/test2.dex b/src/test/resources/test2.dex new file mode 100644 index 0000000..f553846 Binary files /dev/null and b/src/test/resources/test2.dex differ diff --git a/src/test/resources/test2.jar b/src/test/resources/test2.jar deleted file mode 100644 index 9df8df7..0000000 Binary files a/src/test/resources/test2.jar and /dev/null differ