@file:Suppress("CONTEXT_RECEIVERS_DEPRECATED") package app.revanced.util import app.revanced.patcher.FingerprintBuilder import app.revanced.patcher.Match import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.instructions import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.PatchException import app.revanced.patcher.util.proxy.mutableTypes.MutableClass 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 app.revanced.patches.shared.mapping.getResourceId import app.revanced.patches.shared.mapping.resourceMappingPatch import app.revanced.util.Utils.printWarn import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode.* import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.MethodParameter import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.Instruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstruction import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.Reference import com.android.tools.smali.dexlib2.iface.reference.StringReference 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.util.MethodUtil import java.util.EnumSet const val REGISTER_TEMPLATE_REPLACEMENT: String = "REGISTER_INDEX" fun parametersEqual( parameters1: Iterable, parameters2: Iterable ): Boolean { if (parameters1.count() != parameters2.count()) return false val iterator1 = parameters1.iterator() parameters2.forEach { if (!it.startsWith(iterator1.next())) return false } return true } /** * Starting from and including the instruction at index [startIndex], * finds the next register that is wrote to and not read from. If a return instruction * is encountered, then the lowest unused register is returned. * * This method can return a non 4-bit register, and the calling code may need to temporarily * swap register contents if a 4-bit register is required. * * @param startIndex Inclusive starting index. * @param registersToExclude Registers to exclude, and consider as used. For most use cases, * all registers used in injected code should be specified. * @throws IllegalArgumentException If a branch or conditional statement is encountered * before a suitable register is found. */ internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude: Int): Int { if (implementation == null) { throw IllegalArgumentException("Method has no implementation: $this") } if (startIndex < 0 || startIndex >= instructions.count()) { throw IllegalArgumentException("startIndex out of bounds: $startIndex") } // All registers used by an instruction. fun Instruction.getRegistersUsed() = when (this) { is FiveRegisterInstruction -> listOf(registerC, registerD, registerE, registerF, registerG) is ThreeRegisterInstruction -> listOf(registerA, registerB, registerC) is TwoRegisterInstruction -> listOf(registerA, registerB) is OneRegisterInstruction -> listOf(registerA) is RegisterRangeInstruction -> (startRegister until (startRegister + registerCount)).toList() else -> emptyList() } // Register that is written to by an instruction. fun Instruction.getRegisterWritten() = when (this) { is ThreeRegisterInstruction -> registerA is TwoRegisterInstruction -> registerA is OneRegisterInstruction -> registerA else -> throw IllegalStateException("Not a write instruction: $this") } val writeOpcodes = EnumSet.of( NEW_INSTANCE, NEW_ARRAY, MOVE, MOVE_FROM16, MOVE_16, MOVE_WIDE, MOVE_WIDE_FROM16, MOVE_WIDE_16, MOVE_OBJECT, MOVE_OBJECT_FROM16, MOVE_OBJECT_16, MOVE_RESULT, MOVE_RESULT_WIDE, MOVE_RESULT_OBJECT, MOVE_EXCEPTION, IGET, IGET_WIDE, IGET_OBJECT, IGET_BOOLEAN, IGET_BYTE, IGET_CHAR, IGET_SHORT, SGET, SGET_WIDE, SGET_OBJECT, SGET_BOOLEAN, SGET_BYTE, SGET_CHAR, SGET_SHORT, ) val branchOpcodes = EnumSet.of( GOTO, GOTO_16, GOTO_32, IF_EQ, IF_NE, IF_LT, IF_GE, IF_GT, IF_LE, IF_EQZ, IF_NEZ, IF_LTZ, IF_GEZ, IF_GTZ, IF_LEZ, ) val returnOpcodes = EnumSet.of( RETURN_VOID, RETURN, RETURN_WIDE, RETURN_OBJECT, ) // Highest 4-bit register available, exclusive. Ideally return a free register less than this. val maxRegister4Bits = 16 var bestFreeRegisterFound: Int? = null val usedRegisters = registersToExclude.toMutableSet() for (i in startIndex until instructions.count()) { val instruction = getInstruction(i) if (instruction.opcode in returnOpcodes) { // Method returns. Use lowest register that hasn't been encountered. val freeRegister = (0 until implementation!!.registerCount).find { it !in usedRegisters } if (freeRegister != null) { return freeRegister } if (bestFreeRegisterFound != null) { return bestFreeRegisterFound; } // Somehow every method register was read from before any register was wrote to. // In practice this never occurs. throw IllegalArgumentException("Could not find a free register from startIndex: " + "$startIndex excluding: $registersToExclude") } if (instruction.opcode in branchOpcodes) { if (bestFreeRegisterFound != null) { return bestFreeRegisterFound; } // This method is simple and does not follow branching. throw IllegalArgumentException("Encountered a branch statement before a free register could be found") } if (instruction.opcode in writeOpcodes) { val freeRegister = instruction.getRegisterWritten() if (freeRegister !in usedRegisters) { if (freeRegister < maxRegister4Bits) { // Found an ideal register. return freeRegister } // Continue searching for a 4-bit register if available. if (bestFreeRegisterFound == null || freeRegister < bestFreeRegisterFound) { bestFreeRegisterFound = freeRegister } } } usedRegisters.addAll(instruction.getRegistersUsed()) } // Cannot be reached since a branch or return statement will // be encountered before the end of the method. throw IllegalStateException() } /** * Find the [MutableMethod] from a given [Method] in a [MutableClass]. * * @param method The [Method] to find. * @return The [MutableMethod]. */ fun MutableClass.findMutableMethodOf(method: Method) = this.methods.first { MethodUtil.methodSignaturesMatch(it, method) } /** * Apply a transform to all methods of the class. * * @param transform The transformation function. Accepts a [MutableMethod] and returns a transformed [MutableMethod]. */ fun MutableClass.transformMethods(transform: MutableMethod.() -> MutableMethod) { val transformedMethods = methods.map { it.transform() } methods.clear() methods.addAll(transformedMethods) } /** * Inject a call to a method that hides a view. * * @param insertIndex The index to insert the call at. * @param viewRegister The register of the view to hide. * @param classDescriptor The descriptor of the class that contains the method. * @param targetMethod The name of the method to call. */ fun MutableMethod.injectHideViewCall( insertIndex: Int, viewRegister: Int, classDescriptor: String, targetMethod: String, ) = addInstruction( insertIndex, "invoke-static { v$viewRegister }, $classDescriptor->$targetMethod(Landroid/view/View;)V", ) /** * Inserts instructions at a given index, using the existing control flow label at that index. * Inserted instructions can have it's own control flow labels as well. * * Effectively this changes the code from: * :label * (original code) * * Into: * :label * (patch code) * (original code) */ internal fun MutableMethod.addInstructionsAtControlFlowLabel( insertIndex: Int, instructions: String, ) { // Duplicate original instruction and add to +1 index. addInstruction(insertIndex + 1, getInstruction(insertIndex)) // Add patch code at same index as duplicated instruction, // so it uses the original instruction control flow label. addInstructionsWithLabels(insertIndex + 1, instructions) // Remove original non duplicated instruction. removeInstruction(insertIndex) // Original instruction is now after the inserted patch instructions, // and the original control flow label is on the first instruction of the patch code. } /** * Get the index of the first instruction with the id of the given resource id name. * * Requires [resourceMappingPatch] as a dependency. * * @param resourceName the name of the resource to find the id for. * @return the index of the first instruction with the id of the given resource name, or -1 if not found. * @throws PatchException if the resource cannot be found. * @see [indexOfFirstResourceIdOrThrow], [indexOfFirstLiteralInstructionReversed] */ fun Method.indexOfFirstResourceId(resourceName: String): Int { val resourceId = getResourceId("id", resourceName) if (resourceId == -1L) { printWarn("Could not find resource type: id name: $name") return -1 } return indexOfFirstLiteralInstruction(resourceId) } /** * Get the index of the first instruction with the id of the given resource name or throw a [PatchException]. * * Requires [resourceMappingPatch] as a dependency. * * @throws [PatchException] if the resource is not found, or the method does not contain the resource id literal value. * @see [indexOfFirstResourceId], [indexOfFirstLiteralInstructionReversedOrThrow] */ fun Method.indexOfFirstResourceIdOrThrow(resourceName: String): Int { val index = indexOfFirstResourceId(resourceName) if (index < 0) { throw PatchException("Found resource id for: '$resourceName' but method does not contain the id: $this") } return index } /** * Find the index of the first literal instruction with the given value. * * @return the first literal instruction with the value, or -1 if not found. * @see indexOfFirstLiteralInstructionOrThrow */ fun Method.indexOfFirstLiteralInstruction(literal: Long) = implementation?.let { it.instructions.indexOfFirst { instruction -> (instruction as? WideLiteralInstruction)?.wideLiteral == literal } } ?: -1 /** * Find the index of the first literal instruction with the given value, * or throw an exception if not found. * * @return the first literal instruction with the value, or throws [PatchException] if not found. */ fun Method.indexOfFirstLiteralInstructionOrThrow(literal: Long): Int { val index = indexOfFirstLiteralInstruction(literal) if (index < 0) throw PatchException("Could not find literal value: $literal") return index } /** * Find the index of the last literal instruction with the given value. * * @return the last literal instruction with the value, or -1 if not found. * @see indexOfFirstLiteralInstructionOrThrow */ fun Method.indexOfFirstLiteralInstructionReversed(literal: Long) = implementation?.let { it.instructions.indexOfLast { instruction -> (instruction as? WideLiteralInstruction)?.wideLiteral == literal } } ?: -1 /** * Find the index of the last wide literal instruction with the given value, * or throw an exception if not found. * * @return the last literal instruction with the value, or throws [PatchException] if not found. */ fun Method.indexOfFirstLiteralInstructionReversedOrThrow(literal: Long): Int { val index = indexOfFirstLiteralInstructionReversed(literal) if (index < 0) throw PatchException("Could not find literal value: $literal") return index } fun Method.indexOfFirstStringInstruction(str: String) = indexOfFirstInstruction { opcode == Opcode.CONST_STRING && getReference()?.string == str } fun Method.indexOfFirstStringInstructionOrThrow(str: String): Int { val index = indexOfFirstStringInstruction(str) if (index < 0) { throw PatchException("Found string value for: '$str' but method does not contain the id: $this") } return index } /** * Check if the method contains a literal with the given value. * * @return if the method contains a literal with the given value. */ fun Method.containsLiteralInstruction(literal: Long) = indexOfFirstLiteralInstruction(literal) >= 0 /** * Traverse the class hierarchy starting from the given root class. * * @param targetClass the class to start traversing the class hierarchy from. * @param callback function that is called for every class in the hierarchy. */ fun BytecodePatchContext.traverseClassHierarchy( targetClass: MutableClass, callback: MutableClass.() -> Unit ) { callback(targetClass) targetClass.superclass ?: return classBy { targetClass.superclass == it.type }?.mutableClass?.let { traverseClassHierarchy(it, callback) } } fun MutableMethod.injectLiteralInstructionViewCall( literal: Long, smaliInstruction: String ) { val literalIndex = indexOfFirstLiteralInstructionOrThrow(literal) val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT_OBJECT) val targetRegister = getInstruction(targetIndex).registerA.toString() addInstructions( targetIndex + 1, smaliInstruction.replace(REGISTER_TEMPLATE_REPLACEMENT, targetRegister) ) } fun BytecodePatchContext.replaceLiteralInstructionCall( originalLiteral: Long, replaceLiteral: Long ) { classes.forEach { classDef -> classDef.methods.forEach { method -> method.implementation.apply { this?.instructions?.forEachIndexed { _, instruction -> if (instruction.opcode != Opcode.CONST) return@forEachIndexed if ((instruction as Instruction31i).wideLiteral != originalLiteral) return@forEachIndexed proxy(classDef) .mutableClass .findMutableMethodOf(method).apply { val index = indexOfFirstLiteralInstructionOrThrow(originalLiteral) val register = (instruction as OneRegisterInstruction).registerA replaceInstruction(index, "const v$register, $replaceLiteral") } } } } } } fun BytecodePatchContext.replaceLiteralInstructionCall( literal: Long, smaliInstruction: String ) { classes.forEach { classDef -> classDef.methods.forEach { method -> method.implementation.apply { this?.instructions?.forEachIndexed { _, instruction -> if (instruction.opcode != Opcode.CONST) return@forEachIndexed if ((instruction as Instruction31i).wideLiteral != literal) return@forEachIndexed proxy(classDef) .mutableClass .findMutableMethodOf(method).apply { val index = indexOfFirstLiteralInstructionOrThrow(literal) val register = (instruction as OneRegisterInstruction).registerA.toString() addInstructions( index + 1, smaliInstruction.replace(REGISTER_TEMPLATE_REPLACEMENT, register) ) } } } } } } /** * Get the [Reference] of an [Instruction] as [T]. * * @param T The type of [Reference] to cast to. * @return The [Reference] as [T] or null * if the [Instruction] is not a [ReferenceInstruction] or the [Reference] is not of type [T]. * @see ReferenceInstruction */ inline fun Instruction.getReference() = (this as? ReferenceInstruction)?.reference as? T /** * @return The index of the first opcode specified, or -1 if not found. * @see indexOfFirstInstructionOrThrow */ fun Method.indexOfFirstInstruction(targetOpcode: Opcode): Int = indexOfFirstInstruction(0, targetOpcode) /** * @param startIndex Optional starting index to start searching from. * @return The index of the first opcode specified, or -1 if not found. * @see indexOfFirstInstructionOrThrow */ fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): Int = indexOfFirstInstruction(startIndex) { opcode == targetOpcode } /** * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex]. * * @param startIndex Optional starting index to start searching from. * @return -1 if the instruction is not found. * @see indexOfFirstInstructionOrThrow */ fun Method.indexOfFirstInstruction(startIndex: Int = 0, filter: Instruction.() -> Boolean): Int { if (implementation == null) { return -1 } var instructions = this.implementation!!.instructions if (startIndex != 0) { instructions = instructions.drop(startIndex) } val index = instructions.indexOfFirst(filter) return if (index >= 0) { startIndex + index } else { -1 } } /** * @return The index of the first opcode specified * @throws PatchException * @see indexOfFirstInstruction */ fun Method.indexOfFirstInstructionOrThrow(targetOpcode: Opcode): Int = indexOfFirstInstructionOrThrow(0, targetOpcode) /** * @return The index of the first opcode specified, starting from the index specified. * @throws PatchException * @see indexOfFirstInstruction */ fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, targetOpcode: Opcode): Int = indexOfFirstInstructionOrThrow(startIndex) { opcode == targetOpcode } /** * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex]. * * @return the index of the instruction * @throws PatchException * @see indexOfFirstInstruction */ fun Method.indexOfFirstInstructionOrThrow( startIndex: Int = 0, filter: Instruction.() -> Boolean ): Int { val index = indexOfFirstInstruction(startIndex, filter) if (index < 0) { throw PatchException("Could not find instruction index") } return index } /** * Get the index of matching instruction, * starting from and [startIndex] and searching down. * * @param startIndex Optional starting index to search down from. Searching includes the start index. * @return -1 if the instruction is not found. * @see indexOfFirstInstructionReversedOrThrow */ fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode: Opcode): Int = indexOfFirstInstructionReversed(startIndex) { opcode == targetOpcode } /** * Get the index of matching instruction, * starting from and [startIndex] and searching down. * * @param startIndex Optional starting index to search down from. Searching includes the start index. * @return -1 if the instruction is not found. * @see indexOfFirstInstructionReversedOrThrow */ fun Method.indexOfFirstInstructionReversed( startIndex: Int? = null, filter: Instruction.() -> Boolean ): Int { if (implementation == null) { return -1 } var instructions = this.implementation!!.instructions if (startIndex != null) { instructions = instructions.take(startIndex + 1) } return instructions.indexOfLast(filter) } fun Method.indexOfFirstInstructionReversedOrThrow(opcode: Opcode): Int = indexOfFirstInstructionReversedOrThrow(null, opcode) /** * Get the index of matching instruction, * starting from and [startIndex] and searching down. * * @param startIndex Optional starting index to search down from. Searching includes the start index. * @return -1 if the instruction is not found. * @see indexOfFirstInstructionReversed */ fun Method.indexOfFirstInstructionReversedOrThrow( startIndex: Int? = null, targetOpcode: Opcode ): Int = indexOfFirstInstructionReversedOrThrow(startIndex) { opcode == targetOpcode } /** * Get the index of matching instruction, * starting from and [startIndex] and searching down. * * @param startIndex Optional starting index to search down from. Searching includes the start index. * @return -1 if the instruction is not found. * @see indexOfFirstInstructionReversed */ fun Method.indexOfFirstInstructionReversedOrThrow( startIndex: Int? = null, filter: Instruction.() -> Boolean ): Int { val index = indexOfFirstInstructionReversed(startIndex, filter) if (index < 0) { throw PatchException("Could not find instruction index") } return index } /** * @return An immutable list of indices of the instructions in reverse order. * _Returns an empty list if no indices are found_ * @see findInstructionIndicesReversedOrThrow */ fun Method.findInstructionIndicesReversed(filter: Instruction.() -> Boolean): List = instructions .withIndex() .filter { (_, instruction) -> filter(instruction) } .map { (index, _) -> index } .asReversed() /** * @return An immutable list of indices of the instructions in reverse order. * @throws PatchException if no matching indices are found. */ fun Method.findInstructionIndicesReversedOrThrow(filter: Instruction.() -> Boolean): List { val indexes = findInstructionIndicesReversed(filter) if (indexes.isEmpty()) throw PatchException("No matching instructions found in: $this") return indexes } /** * @return An immutable list of indices of the opcode in reverse order. * _Returns an empty list if no indices are found_ * @see findInstructionIndicesReversedOrThrow */ fun Method.findInstructionIndicesReversed(opcode: Opcode): List = findInstructionIndicesReversed { this.opcode == opcode } /** * @return An immutable list of indices of the opcode in reverse order. * @throws PatchException if no matching indices are found. */ fun Method.findInstructionIndicesReversedOrThrow(opcode: Opcode): List { val instructions = findInstructionIndicesReversed(opcode) if (instructions.isEmpty()) throw PatchException("Could not find opcode: $opcode in: $this") return instructions } fun Method.referenceMatchesOrThrow(targetIndex: Int, reference: String) { val targetReference = getInstruction(targetIndex).reference.toString() if (reference != targetReference) throw PatchException("References do not match. Expected: '$reference', Found: '$targetReference'") } /** * Called for _all_ instructions with the given literal value. */ fun BytecodePatchContext.forEachLiteralValueInstruction( literal: Long, block: MutableMethod.(literalInstructionIndex: Int) -> Unit, ) { classes.forEach { classDef -> classDef.methods.forEach { method -> method.implementation?.instructions?.forEachIndexed { index, instruction -> if (instruction.opcode == Opcode.CONST && (instruction as WideLiteralInstruction).wideLiteral == literal ) { val mutableMethod = proxy(classDef).mutableClass.findMutableMethodOf(method) block.invoke(mutableMethod, index) } } } } } context(BytecodePatchContext) fun Match.getWalkerMethod(offset: Int) = method.getWalkerMethod(offset) context(BytecodePatchContext) fun MutableMethod.getWalkerMethod(offset: Int): MutableMethod { val newMethod = getInstruction(offset).reference as MethodReference return findMethodOrThrow(newMethod.definingClass) { MethodUtil.methodSignaturesMatch(this, newMethod) } } /** * Taken from BiliRoamingX: * https://github.com/BiliRoamingX/BiliRoamingX/blob/ae58109f3acdd53ec2d2b3fb439c2a2ef1886221/patches/src/main/kotlin/app/revanced/patches/bilibili/utils/Extenstions.kt#L151 */ fun MutableMethod.getFiveRegisters(index: Int) = with(getInstruction(index)) { arrayOf(registerC, registerD, registerE, registerF, registerG) .take(registerCount).joinToString(",") { "v$it" } } context(BytecodePatchContext) fun addStaticFieldToExtension( className: String, methodName: String, fieldName: String, objectClass: String, smaliInstructions: String, shouldAddConstructor: Boolean = true ) { val classDef = classes.find { classDef -> classDef.type == className } ?: throw PatchException("No matching methods found in: $className") val mutableClass = proxy(classDef).mutableClass val objectCall = "$mutableClass->$fieldName:$objectClass" mutableClass.apply { methods.first { method -> method.name == methodName }.apply { staticFields.add( ImmutableField( definingClass, fieldName, objectClass, AccessFlags.PUBLIC or AccessFlags.STATIC, null, annotations, null ).toMutable() ) addInstructionsWithLabels( 0, """ sget-object v0, $objectCall """ + smaliInstructions ) } } if (!shouldAddConstructor) return findMethodsOrThrow(objectClass) .filter { method -> MethodUtil.isConstructor(method) } .forEach { mutableMethod -> mutableMethod.apply { val initializeIndex = indexOfFirstInstructionOrThrow { opcode == Opcode.INVOKE_DIRECT && getReference()?.name == "" } val insertIndex = if (initializeIndex == -1) 1 else initializeIndex + 1 val initializeRegister = if (initializeIndex == -1) "p0" else "v${getInstruction(initializeIndex).registerC}" addInstruction( insertIndex, "sput-object $initializeRegister, $objectCall" ) } } } context(BytecodePatchContext) fun findMethodOrThrow( reference: String, methodPredicate: Method.() -> Boolean = { MethodUtil.isConstructor(this) } ) = findMethodsOrThrow(reference).first(methodPredicate) context(BytecodePatchContext) fun findMethodsOrThrow(reference: String): MutableSet { val classDef = classes.find { classDef -> classDef.type == reference } ?: throw PatchException("No matching methods found in: $reference") return proxy(classDef) .mutableClass .methods } context(BytecodePatchContext) fun updatePatchStatus( className: String, methodName: String ) = findMethodOrThrow(className) { name == methodName } .replaceInstruction( 0, "const/4 v0, 0x1" ) /** * Taken from BiliRoamingX: * https://github.com/BiliRoamingX/BiliRoamingX/blob/ae58109f3acdd53ec2d2b3fb439c2a2ef1886221/patches/src/main/kotlin/app/revanced/patches/bilibili/utils/Extenstions.kt#L51 */ fun Method.cloneMutable( registerCount: Int = implementation?.registerCount ?: 0, clearImplementation: Boolean = false, name: String = this.name, accessFlags: Int = this.accessFlags, parameters: List = this.parameters, returnType: String = this.returnType ): MutableMethod { val clonedImplementation = implementation?.let { ImmutableMethodImplementation( registerCount, if (clearImplementation) emptyList() else it.instructions, if (clearImplementation) emptyList() else it.tryBlocks, if (clearImplementation) emptyList() else it.debugItems, ) } return ImmutableMethod( definingClass, name, parameters, returnType, accessFlags, annotations, hiddenApiRestrictions, clonedImplementation ).toMutable() } /** * Return the method early. */ fun MutableMethod.returnEarly(bool: Boolean = false) { val const = if (bool) "0x1" else "0x0" val stringInstructions = when (returnType.first()) { 'L' -> """ const/4 v0, $const return-object v0 """ 'V' -> "return-void" 'I', 'Z' -> """ const/4 v0, $const return v0 """ else -> throw Exception("This case should never happen.") } addInstructions(0, stringInstructions) } /** * Set the custom condition for this fingerprint to check for a literal value. * * @param literalSupplier The supplier for the literal value to check for. */ // TODO: add a way for subclasses to also use their own custom fingerprint. fun FingerprintBuilder.literal(literalSupplier: () -> Long) { custom { method, _ -> method.containsLiteralInstruction(literalSupplier()) } } /** * Perform a bitwise OR operation between an [AccessFlags] and an [Int]. * * @param other The [Int] to perform the operation with. */ infix fun Int.or(other: AccessFlags) = this or other.value /** * Perform a bitwise OR operation between two [AccessFlags]. * * @param other The other [AccessFlags] to perform the operation with. */ infix fun AccessFlags.or(other: AccessFlags) = value or other.value /** * Perform a bitwise OR operation between an [Int] and an [AccessFlags]. * * @param other The [AccessFlags] to perform the operation with. */ infix fun AccessFlags.or(other: Int) = value or other