package app.revanced.patcher.patch.usage import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.or import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Format import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction11x import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c import com.android.tools.smali.dexlib2.immutable.ImmutableField import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation import com.android.tools.smali.dexlib2.immutable.reference.ImmutableFieldReference import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference import com.android.tools.smali.dexlib2.immutable.value.ImmutableFieldEncodedValue import com.android.tools.smali.dexlib2.util.Preconditions import com.google.common.collect.ImmutableList @Suppress("unused") @Patch( name = "Example bytecode patch", description = "Example demonstration of a bytecode patch.", dependencies = [ExampleResourcePatch::class], compatiblePackages = [CompatiblePackage("com.example.examplePackage", arrayOf("0.0.1", "0.0.2"))], ) object ExampleBytecodePatch : BytecodePatch(setOf(ExampleFingerprint)) { // Entry point of a patch. Supplied fingerprints are resolved at this point. override fun execute(context: BytecodeContext) { ExampleFingerprint.result?.let { result -> // Let's modify it, so it prints "Hello, ReVanced! Editing bytecode." // Get the start index of our opcode pattern. // This will be the index of the instruction with the opcode CONST_STRING. val startIndex = result.scanResult.patternScanResult!!.startIndex result.mutableMethod.apply { replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.") // Store the fields initial value into the first virtual register. replaceInstruction(0, "sget-object v0, LTestClass;->dummyField:Ljava/io/PrintStream;") // Now let's 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 TestClass field dummyField inside the virtual register 0. // // Control flow instructions are not supported as of now. addInstructionsWithLabels( startIndex + 2, """ invoke-static { }, LTestClass;->returnHello()Ljava/lang/String; move-result-object v1 invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V """, ) } // Find the class in which the method matching our fingerprint is defined in. context.findClass(result.classDef.type)!!.mutableClass.apply { // Add a new method that returns a string. methods.add( ImmutableMethod( result.classDef.type, "returnHello", null, "Ljava/lang/String;", AccessFlags.PRIVATE or AccessFlags.STATIC, null, null, ImmutableMethodImplementation( 1, ImmutableList.of( BuilderInstruction21c( Opcode.CONST_STRING, 0, ImmutableStringReference("Hello, ReVanced! Adding bytecode."), ), BuilderInstruction11x(Opcode.RETURN_OBJECT, 0), ), null, null, ), ).toMutable(), ) // Add a field in the main class. // We will use this field in our method below to call println on. // The field holds the Ljava/io/PrintStream->out; field. fields.add( ImmutableField( type, "dummyField", "Ljava/io/PrintStream;", AccessFlags.PRIVATE or AccessFlags.STATIC, ImmutableFieldEncodedValue( ImmutableFieldReference( "Ljava/lang/System;", "out", "Ljava/io/PrintStream;", ), ), null, null, ).toMutable(), ) } } ?: throw PatchException("Fingerprint failed to resolve.") } /** * Replace an existing instruction with a new one containing a reference to a new string. * @param index The index of the instruction to replace. * @param string The replacement string. */ private fun MutableMethod.replaceStringAt( index: Int, string: String, ) { val instruction = getInstruction(index) // Utility method of dexlib2. Preconditions.checkFormat(instruction.opcode, Format.Format21c) // Cast this to an instruction of the format 21c. // The instruction format can be found in the docs at // https://source.android.com/devices/tech/dalvik/dalvik-bytecode val strInstruction = instruction as Instruction21c // In our case we want an instruction with the opcode CONST_STRING // The format is 21c, so we create a new BuilderInstruction21c // This instruction will hold the string reference constant in the virtual register of the original instruction // 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. replaceInstruction( index, "const-string ${strInstruction.registerA}, ${ImmutableStringReference(string)}", ) } }