diff --git a/CHANGELOG.md b/CHANGELOG.md index 62ed0cb..9c000fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +# [11.0.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.0-dev.1...v11.0.0-dev.2) (2023-06-09) + + +### Bug Fixes + +* add imports to fix failing tests ([43d6868](https://github.com/revanced/revanced-patcher/commit/43d6868d1f59922f9812f3e4a2a890f3b331def6)) + +# [11.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v10.0.0...v11.0.0-dev.1) (2023-06-07) + + +* refactor!: move extension functions to their corresponding classes ([a12fe7d](https://github.com/revanced/revanced-patcher/commit/a12fe7dd9e976c38a0a82fe35e6650f58f815de4)) +* refactor!: use proper extension function names ([efdd01a](https://github.com/revanced/revanced-patcher/commit/efdd01a9886b6f06af213731824621964367b2a3)) +* fix!: implement extension functions consistently ([aacf900](https://github.com/revanced/revanced-patcher/commit/aacf9007647b1cc11bc40053625802573efda6ef)) + + +### BREAKING CHANGES + +* This changes the import paths for extension functions. +* This changes the names of extension functions +* This changes the name of functions + # [10.0.0](https://github.com/revanced/revanced-patcher/compare/v9.0.0...v10.0.0) (2023-06-07) diff --git a/gradle.properties b/gradle.properties index 003746e..70baf35 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ kotlin.code.style = official -version = 10.0.0 +version = 11.0.0-dev.2 diff --git a/src/main/kotlin/app/revanced/patcher/Patcher.kt b/src/main/kotlin/app/revanced/patcher/Patcher.kt index f6914e3..d61fb11 100644 --- a/src/main/kotlin/app/revanced/patcher/Patcher.kt +++ b/src/main/kotlin/app/revanced/patcher/Patcher.kt @@ -4,7 +4,6 @@ import app.revanced.patcher.data.Context import app.revanced.patcher.extensions.PatchExtensions.dependencies import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.PatchExtensions.requiresIntegrations -import app.revanced.patcher.extensions.nullOutputStream import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve import app.revanced.patcher.patch.* import app.revanced.patcher.util.VersionReader @@ -25,6 +24,7 @@ import org.jf.dexlib2.Opcodes import org.jf.dexlib2.iface.DexFile import org.jf.dexlib2.writer.io.MemoryDataStore import java.io.File +import java.io.OutputStream import java.nio.file.Files internal val NAMER = BasicDexFileNamer() @@ -247,7 +247,7 @@ class Patcher(private val options: PatcherOptions) { XmlPullStreamDecoder( axmlParser, AndrolibResources().resXmlSerializer ).decodeManifest( - extInputFile.directory.getFileInput("AndroidManifest.xml"), nullOutputStream + extInputFile.directory.getFileInput("AndroidManifest.xml"), OutputStream.nullOutputStream() ) } } diff --git a/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt index 21836e5..290174c 100644 --- a/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt +++ b/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt @@ -1,83 +1,33 @@ package app.revanced.patcher.extensions -import app.revanced.patcher.annotation.* -import app.revanced.patcher.data.Context -import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod -import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint -import app.revanced.patcher.patch.OptionsContainer -import app.revanced.patcher.patch.Patch -import app.revanced.patcher.patch.PatchOptions -import app.revanced.patcher.patch.annotations.DependsOn -import app.revanced.patcher.patch.annotations.RequiresIntegrations import kotlin.reflect.KClass -import kotlin.reflect.KVisibility -import kotlin.reflect.full.companionObject -import kotlin.reflect.full.companionObjectInstance -/** - * Recursively find a given annotation on a class. - * @param targetAnnotation The annotation to find. - * @return The annotation. - */ -private fun Class<*>.findAnnotationRecursively(targetAnnotation: KClass) = - this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf()) +internal object AnnotationExtensions { + /** + * Recursively find a given annotation on a class. + * + * @param targetAnnotation The annotation to find. + * @return The annotation. + */ + fun Class<*>.findAnnotationRecursively(targetAnnotation: KClass): T? { + fun Class<*>.findAnnotationRecursively( + targetAnnotation: Class, traversed: MutableSet + ): T? { + val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name } + @Suppress("UNCHECKED_CAST") if (found != null) return found as T -private fun Class<*>.findAnnotationRecursively( - targetAnnotation: Class, traversed: MutableSet -): T? { - val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name } + for (annotation in this.annotations) { + if (traversed.contains(annotation)) continue + traversed.add(annotation) - @Suppress("UNCHECKED_CAST") if (found != null) return found as T - - for (annotation in this.annotations) { - if (traversed.contains(annotation)) continue - traversed.add(annotation) - - return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed)) ?: continue - } - - return null -} - -object PatchExtensions { - val Class>.patchName: String - get() = findAnnotationRecursively(Name::class)?.name ?: this.simpleName - - val Class>.version - get() = findAnnotationRecursively(Version::class)?.version - - val Class>.include - get() = findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)!!.include - - val Class>.description - get() = findAnnotationRecursively(Description::class)?.description - - val Class>.dependencies - get() = findAnnotationRecursively(DependsOn::class)?.dependencies - - val Class>.compatiblePackages - get() = findAnnotationRecursively(Compatibility::class)?.compatiblePackages - - internal val Class>.requiresIntegrations - get() = findAnnotationRecursively(RequiresIntegrations::class) != null - - val Class>.options: PatchOptions? - get() = kotlin.companionObject?.let { cl -> - if (cl.visibility != KVisibility.PUBLIC) return null - kotlin.companionObjectInstance?.let { - (it as? OptionsContainer)?.options + return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed)) + ?: continue } + + return null } -} -object MethodFingerprintExtensions { - val MethodFingerprint.name: String - get() = this.javaClass.simpleName - - val MethodFingerprint.fuzzyPatternScanMethod - get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class) - - val MethodFingerprint.fuzzyScanThreshold - get() = fuzzyPatternScanMethod?.threshold ?: 0 + return this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf()) + } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt index 5531b9b..a3ef1a1 100644 --- a/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt +++ b/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt @@ -1,236 +1,33 @@ package app.revanced.patcher.extensions import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod -import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable -import app.revanced.patcher.util.smali.ExternalLabel -import app.revanced.patcher.util.smali.toInstruction -import app.revanced.patcher.util.smali.toInstructions import org.jf.dexlib2.AccessFlags -import org.jf.dexlib2.builder.BuilderInstruction -import org.jf.dexlib2.builder.BuilderOffsetInstruction -import org.jf.dexlib2.builder.Label -import org.jf.dexlib2.builder.MutableMethodImplementation -import org.jf.dexlib2.builder.instruction.* -import org.jf.dexlib2.iface.Method -import org.jf.dexlib2.iface.instruction.Instruction -import org.jf.dexlib2.immutable.ImmutableMethod -import org.jf.dexlib2.immutable.ImmutableMethodImplementation -import java.io.OutputStream - -infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value -infix fun Int.or(other: AccessFlags) = this or other.value - -fun MutableMethodImplementation.addInstructions(index: Int, instructions: List) { - for (i in instructions.lastIndex downTo 0) { - this.addInstruction(index, instructions[i]) - } -} - -fun MutableMethodImplementation.addInstructions(instructions: List) { - for (instruction in instructions) { - this.addInstruction(instruction) - } -} - -fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List) { - for (i in instructions.lastIndex downTo 0) { - this.replaceInstruction(index + i, instructions[i]) - } -} - -fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) { - for (i in count - 1 downTo 0) { - this.removeInstruction(index + i) - } -} /** - * Clones the method. - * @param registerCount This parameter allows you to change the register count of the method. - * This may be a positive or negative number. - * @return The **immutable** cloned method. Call [toMutable] or [cloneMutable] to get a **mutable** copy. - */ -internal fun Method.clone(registerCount: Int = 0): ImmutableMethod { - val clonedImplementation = implementation?.let { - ImmutableMethodImplementation( - it.registerCount + registerCount, - it.instructions, - it.tryBlocks, - it.debugItems, - ) - } - return ImmutableMethod( - returnType, name, parameters, returnType, accessFlags, annotations, hiddenApiRestrictions, clonedImplementation - ) -} - -/** - * Add a smali instruction to the method. - * @param instruction The smali instruction to add. - */ -fun MutableMethod.addInstruction(instruction: String) = - this.implementation!!.addInstruction(instruction.toInstruction(this)) - -/** - * Add a smali instruction to the method. - * @param index The index to insert the instruction at. - * @param instruction The smali instruction to add. - */ -fun MutableMethod.addInstruction(index: Int, instruction: String) = - this.implementation!!.addInstruction(index, instruction.toInstruction(this)) - -/** - * Replace a smali instruction within the method. - * @param index The index to replace the instruction at. - * @param instruction The smali instruction to place. - */ -fun MutableMethod.replaceInstruction(index: Int, instruction: String) = - this.implementation!!.replaceInstruction(index, instruction.toInstruction(this)) - -/** - * Remove a smali instruction within the method. - * @param index The index to delete the instruction at. - */ -fun MutableMethod.removeInstruction(index: Int) = this.implementation!!.removeInstruction(index) - -/** - * Create a label for the instruction at given index in the method's implementation. + * Create a label for the instruction at given index. + * * @param index The index to create the label for the instruction at. * @return The label. */ -fun MutableMethod.label(index: Int) = this.implementation!!.newLabelForIndex(index) +fun MutableMethod.newLabel(index: Int) = implementation!!.newLabelForIndex(index) /** - * Get an instruction at the given index in the method's implementation. - * @param index The index to get the instruction at. - * @return The instruction. + * Perform a bitwise OR operation between two [AccessFlags]. + * + * @param other The other [AccessFlags] to perform the operation with. */ -fun MutableMethod.instruction(index: Int): BuilderInstruction = this.implementation!!.instructions[index] +infix fun AccessFlags.or(other: AccessFlags) = value or other.value /** - * Get an instruction at the given index in the method's implementation. - * @param index The index to get the instruction at. - * @param T The type of instruction to return. - * @return The instruction. + * Perform a bitwise OR operation between an [AccessFlags] and an [Int]. + * + * @param other The [Int] to perform the operation with. */ -fun MutableMethod.instruction(index: Int): T = instruction(index) as T +infix fun Int.or(other: AccessFlags) = this or other.value /** - * Add smali instructions to the method. - * @param index The index to insert the instructions at. - * @param smali The smali instructions to add. - * @param externalLabels A list of [ExternalLabel] representing a list of labels for instructions which are not in the method to compile. + * Perform a bitwise OR operation between an [Int] and an [AccessFlags]. + * + * @param other The [AccessFlags] to perform the operation with. */ - -fun MutableMethod.addInstructions(index: Int, smali: String, externalLabels: List = emptyList()) { - // Create reference dummy instructions for the instructions. - val nopedSmali = StringBuilder(smali).also { builder -> - externalLabels.forEach { (name, _) -> - builder.append("\n:$name\nnop") - } - }.toString() - - // Compile the instructions with the dummy labels - val compiledInstructions = nopedSmali.toInstructions(this) - - // Add the compiled list of instructions to the method. - val methodImplementation = this.implementation!! - methodImplementation.addInstructions(index, compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size)) - - val methodInstructions = methodImplementation.instructions - methodInstructions.subList(index, index + compiledInstructions.size - externalLabels.size) - .forEachIndexed { compiledInstructionIndex, compiledInstruction -> - // If the compiled instruction is not an offset instruction, skip it. - if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed - - /** - * Creates a new label for the instruction and replaces it with the label of the [compiledInstruction] at [compiledInstructionIndex]. - */ - fun Instruction.makeNewLabel() { - // Create the final label. - val label = methodImplementation.newLabelForIndex(methodInstructions.indexOf(this)) - // Create the final instruction with the new label. - val newInstruction = replaceOffset( - compiledInstruction, label - ) - // Replace the instruction pointing to the dummy label with the new instruction pointing to the real instruction. - methodImplementation.replaceInstruction(index + compiledInstructionIndex, newInstruction) - } - - // If the compiled instruction targets its own instruction, - // which means it points to some of its own, simply an offset has to be applied. - val labelIndex = compiledInstruction.target.location.index - if (labelIndex < compiledInstructions.size - externalLabels.size) { - // Get the targets index (insertion index + the index of the dummy instruction). - methodInstructions[index + labelIndex].makeNewLabel() - return@forEachIndexed - } - - // Since the compiled instruction points to a dummy instruction, - // we can find the real instruction which it was created for by calculation. - - // Get the index of the instruction in the externalLabels list which the dummy instruction was created for. - // this line works because we created the dummy instructions in the same order as the externalLabels list. - val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex] - instruction.makeNewLabel() - } -} - -/** - * Add smali instructions to the end of the method. - * @param instructions The smali instructions to add. - */ -fun MutableMethod.addInstructions(instructions: String, labels: List = emptyList()) = - this.addInstructions(this.implementation!!.instructions.size, instructions, labels) - -/** - * Replace smali instructions within the method. - * @param index The index to replace the instructions at. - * @param instructions The smali instructions to place. - */ -fun MutableMethod.replaceInstructions(index: Int, instructions: String) = - this.implementation!!.replaceInstructions(index, instructions.toInstructions(this)) - -/** - * Remove smali instructions from the method. - * @param index The index to remove the instructions at. - * @param count The amount of instructions to remove. - */ -fun MutableMethod.removeInstructions(index: Int, count: Int) = this.implementation!!.removeInstructions(index, count) - -private fun replaceOffset( - i: BuilderOffsetInstruction, label: Label -): BuilderOffsetInstruction { - return when (i) { - is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label) - is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label) - is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label) - is BuilderInstruction22t -> BuilderInstruction22t(i.opcode, i.registerA, i.registerB, label) - is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label) - is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label) - else -> throw IllegalStateException("A non-offset instruction was given, this should never happen!") - } -} - -/** - * Clones the method. - * @param registerCount This parameter allows you to change the register count of the method. - * This may be a positive or negative number. - * @return The **mutable** cloned method. Call [clone] to get an **immutable** copy. - */ -internal fun Method.cloneMutable(registerCount: Int = 0) = clone(registerCount).toMutable() - -internal 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 -} - -internal val nullOutputStream = object : OutputStream() { - override fun write(b: Int) {} -} \ No newline at end of file +infix fun AccessFlags.or(other: Int) = value or other \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/extensions/InstructionExtensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/InstructionExtensions.kt new file mode 100644 index 0000000..72d1410 --- /dev/null +++ b/src/main/kotlin/app/revanced/patcher/extensions/InstructionExtensions.kt @@ -0,0 +1,326 @@ +package app.revanced.patcher.extensions + +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patcher.util.smali.toInstruction +import app.revanced.patcher.util.smali.toInstructions +import org.jf.dexlib2.builder.BuilderInstruction +import org.jf.dexlib2.builder.BuilderOffsetInstruction +import org.jf.dexlib2.builder.Label +import org.jf.dexlib2.builder.MutableMethodImplementation +import org.jf.dexlib2.builder.instruction.* +import org.jf.dexlib2.iface.instruction.Instruction + +object InstructionExtensions { + + /** + * Add instructions to a method at the given index. + * + * @param index The index to add the instructions at. + * @param instructions The instructions to add. + */ + fun MutableMethodImplementation.addInstructions( + index: Int, + instructions: List + ) = + instructions.asReversed().forEach { addInstruction(index, it) } + + /** + * Add instructions to a method. + * The instructions will be added at the end of the method. + * + * @param instructions The instructions to add. + */ + fun MutableMethodImplementation.addInstructions(instructions: List) = + instructions.forEach { this.addInstruction(it) } + + /** + * Remove instructions from a method at the given index. + * + * @param index The index to remove the instructions at. + * @param count The amount of instructions to remove. + */ + fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) = repeat(count) { + removeInstruction(index) + } + + /** + * Remove the first instructions from a method. + * + * @param count The amount of instructions to remove. + */ + fun MutableMethodImplementation.removeInstructions(count: Int) = removeInstructions(0, count) + + /** + * Replace instructions at the given index with the given instructions. + * The amount of instructions to replace is the amount of instructions in the given list. + * + * @param index The index to replace the instructions at. + * @param instructions The instructions to replace the instructions with. + */ + fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List) { + // Remove the instructions at the given index. + removeInstructions(index, instructions.size) + + // Add the instructions at the given index. + addInstructions(index, instructions) + } + + /** + * Add an instruction to a method at the given index. + * + * @param index The index to add the instruction at. + * @param instruction The instruction to add. + */ + fun MutableMethod.addInstruction(index: Int, instruction: BuilderInstruction) = + implementation!!.addInstruction(index, instruction) + + /** + * Add an instruction to a method. + * + * @param instruction The instructions to add. + */ + fun MutableMethod.addInstruction(instruction: BuilderInstruction) = + implementation!!.addInstruction(instruction) + + /** + * Add an instruction to a method at the given index. + * + * @param index The index to add the instruction at. + * @param smaliInstructions The instruction to add. + */ + fun MutableMethod.addInstruction(index: Int, smaliInstructions: String) = + implementation!!.addInstruction(index, smaliInstructions.toInstruction(this)) + + /** + * Add an instruction to a method. + * + * @param smaliInstructions The instruction to add. + */ + fun MutableMethod.addInstruction(smaliInstructions: String) = + implementation!!.addInstruction(smaliInstructions.toInstruction(this)) + + + /** + * Add instructions to a method at the given index. + * + * @param index The index to add the instructions at. + * @param instructions The instructions to add. + */ + fun MutableMethod.addInstructions(index: Int, instructions: List) = + implementation!!.addInstructions(index, instructions) + + /** + * Add instructions to a method. + * + * @param instructions The instructions to add. + */ + fun MutableMethod.addInstructions(instructions: List) = + implementation!!.addInstructions(instructions) + + /** + * Add instructions to a method. + * + * @param smaliInstructions The instructions to add. + */ + fun MutableMethod.addInstructions(index: Int, smaliInstructions: String) = + implementation!!.addInstructions(index, smaliInstructions.toInstructions(this)) + + /** + * Add instructions to a method. + * + * @param smaliInstructions The instructions to add. + */ + fun MutableMethod.addInstructions(smaliInstructions: String) = + implementation!!.addInstructions(smaliInstructions.toInstructions(this)) + + /** + * Add instructions to a method at the given index. + * + * @param index The index to add the instructions at. + * @param smaliInstructions The instructions to add. + * @param externalLabels A list of [ExternalLabel] for instructions outside of [smaliInstructions]. + */ +// Special function for adding instructions with external labels. + fun MutableMethod.addInstructionsWithLabels( + index: Int, + smaliInstructions: String, + vararg externalLabels: ExternalLabel + ) { + // Create reference dummy instructions for the instructions. + val nopSmali = StringBuilder(smaliInstructions).also { builder -> + externalLabels.forEach { (name, _) -> + builder.append("\n:$name\nnop") + } + }.toString() + + // Compile the instructions with the dummy labels + val compiledInstructions = nopSmali.toInstructions(this) + + // Add the compiled list of instructions to the method. + addInstructions( + index, + compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size) + ) + + implementation!!.apply { + this@apply.instructions.subList(index, index + compiledInstructions.size - externalLabels.size) + .forEachIndexed { compiledInstructionIndex, compiledInstruction -> + // If the compiled instruction is not an offset instruction, skip it. + if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed + + /** + * Creates a new label for the instruction + * and replaces it with the label of the [compiledInstruction] at [compiledInstructionIndex]. + */ + fun Instruction.makeNewLabel() { + fun replaceOffset( + i: BuilderOffsetInstruction, label: Label + ): BuilderOffsetInstruction { + return when (i) { + is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label) + is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label) + is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label) + is BuilderInstruction22t -> BuilderInstruction22t( + i.opcode, + i.registerA, + i.registerB, + label + ) + is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label) + is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label) + else -> throw IllegalStateException( + "A non-offset instruction was given, this should never happen!" + ) + } + } + + // Create the final label. + val label = newLabelForIndex(this@apply.instructions.indexOf(this)) + + // Create the final instruction with the new label. + val newInstruction = replaceOffset( + compiledInstruction, label + ) + + // Replace the instruction pointing to the dummy label + // with the new instruction pointing to the real instruction. + replaceInstruction(index + compiledInstructionIndex, newInstruction) + } + + // If the compiled instruction targets its own instruction, + // which means it points to some of its own, simply an offset has to be applied. + val labelIndex = compiledInstruction.target.location.index + if (labelIndex < compiledInstructions.size - externalLabels.size) { + // Get the targets index (insertion index + the index of the dummy instruction). + this.instructions[index + labelIndex].makeNewLabel() + return@forEachIndexed + } + + // Since the compiled instruction points to a dummy instruction, + // we can find the real instruction which it was created for by calculation. + + // Get the index of the instruction in the externalLabels list + // which the dummy instruction was created for. + // This works because we created the dummy instructions in the same order as the externalLabels list. + val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex] + instruction.makeNewLabel() + } + } + } + + /** + * Remove an instruction at the given index. + * + * @param index The index to remove the instruction at. + */ + fun MutableMethod.removeInstruction(index: Int) = + implementation!!.removeInstruction(index) + + /** + * Remove instructions at the given index. + * + * @param index The index to remove the instructions at. + * @param count The amount of instructions to remove. + */ + fun MutableMethod.removeInstructions(index: Int, count: Int) = + implementation!!.removeInstructions(index, count) + + /** + * Remove instructions at the given index. + * + * @param count The amount of instructions to remove. + */ + fun MutableMethod.removeInstructions(count: Int) = + implementation!!.removeInstructions(count) + + /** + * Replace an instruction at the given index. + * + * @param index The index to replace the instruction at. + * @param instruction The instruction to replace the instruction with. + */ + fun MutableMethod.replaceInstruction(index: Int, instruction: BuilderInstruction) = + implementation!!.replaceInstruction(index, instruction) + + /** + * Replace an instruction at the given index. + * + * @param index The index to replace the instruction at. + * @param smaliInstruction The smali instruction to replace the instruction with. + */ + fun MutableMethod.replaceInstruction(index: Int, smaliInstruction: String) = + implementation!!.replaceInstruction(index, smaliInstruction.toInstruction(this)) + + /** + * Replace instructions at the given index. + * + * @param index The index to replace the instructions at. + * @param instructions The instructions to replace the instructions with. + */ + fun MutableMethod.replaceInstructions(index: Int, instructions: List) = + implementation!!.replaceInstructions(index, instructions) + + /** + * Replace instructions at the given index. + * + * @param index The index to replace the instructions at. + * @param smaliInstructions The smali instructions to replace the instructions with. + */ + fun MutableMethod.replaceInstructions(index: Int, smaliInstructions: String) = + implementation!!.replaceInstructions(index, smaliInstructions.toInstructions(this)) + + /** + * Get an instruction at the given index. + * + * @param index The index to get the instruction at. + * @return The instruction. + */ + fun MutableMethodImplementation.getInstruction(index: Int): BuilderInstruction = instructions[index] + + /** + * Get an instruction at the given index. + * + * @param index The index to get the instruction at. + * @param T The type of instruction to return. + * @return The instruction. + */ + @Suppress("UNCHECKED_CAST") + fun MutableMethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T + + /** + * Get an instruction at the given index. + * @param index The index to get the instruction at. + * @return The instruction. + */ + fun MutableMethod.getInstruction(index: Int): BuilderInstruction = implementation!!.getInstruction(index) + + /** + * Get an instruction at the given index. + * @param index The index to get the instruction at. + * @param T The type of instruction to return. + * @return The instruction. + */ + @Suppress("UNCHECKED_CAST") + fun MutableMethod.getInstruction(index: Int): T = implementation!!.getInstruction(index) +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/extensions/MethodFingerprintExtensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/MethodFingerprintExtensions.kt new file mode 100644 index 0000000..1901044 --- /dev/null +++ b/src/main/kotlin/app/revanced/patcher/extensions/MethodFingerprintExtensions.kt @@ -0,0 +1,20 @@ +package app.revanced.patcher.extensions + +import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object MethodFingerprintExtensions { + + /** + * The name of a [MethodFingerprint]. + */ + val MethodFingerprint.name: String + get() = this.javaClass.simpleName + + /** + * The [FuzzyPatternScanMethod] annotation of a [MethodFingerprint]. + */ + val MethodFingerprint.fuzzyPatternScanMethod + get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class) +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/extensions/PatchExtensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/PatchExtensions.kt new file mode 100644 index 0000000..06f2224 --- /dev/null +++ b/src/main/kotlin/app/revanced/patcher/extensions/PatchExtensions.kt @@ -0,0 +1,71 @@ +package app.revanced.patcher.extensions + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.Context +import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively +import app.revanced.patcher.patch.OptionsContainer +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.PatchOptions +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.RequiresIntegrations +import kotlin.reflect.KVisibility +import kotlin.reflect.full.companionObject +import kotlin.reflect.full.companionObjectInstance + +object PatchExtensions { + /** + * The name of a [Patch]. + */ + val Class>.patchName: String + get() = findAnnotationRecursively(Name::class)?.name ?: this.simpleName + + /** + * The version of a [Patch]. + */ + val Class>.version + get() = findAnnotationRecursively(Version::class)?.version + + /** + * Weather or not a [Patch] should be included. + */ + val Class>.include + get() = findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)!!.include + + /** + * The description of a [Patch]. + */ + val Class>.description + get() = findAnnotationRecursively(Description::class)?.description + + /** + * The dependencies of a [Patch]. + */ + val Class>.dependencies + get() = findAnnotationRecursively(DependsOn::class)?.dependencies + + /** + * The packages a [Patch] is compatible with. + */ + val Class>.compatiblePackages + get() = findAnnotationRecursively(Compatibility::class)?.compatiblePackages + + /** + * Weather or not a [Patch] requires integrations. + */ + internal val Class>.requiresIntegrations + get() = findAnnotationRecursively(RequiresIntegrations::class) != null + + /** + * The options of a [Patch]. + */ + val Class>.options: PatchOptions? + get() = kotlin.companionObject?.let { cl -> + if (cl.visibility != KVisibility.PUBLIC) return null + kotlin.companionObjectInstance?.let { + (it as? OptionsContainer)?.options + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt b/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt index f297f95..1a7bbf3 100644 --- a/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt +++ b/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt @@ -2,8 +2,6 @@ package app.revanced.patcher.fingerprint.method.impl import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod -import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold -import app.revanced.patcher.extensions.parametersEqual import app.revanced.patcher.fingerprint.Fingerprint import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod import app.revanced.patcher.util.proxy.ClassProxy @@ -90,6 +88,17 @@ abstract class MethodFingerprint( return false + 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 + } + if (methodFingerprint.parameters != null && !parametersEqual( methodFingerprint.parameters, // TODO: parseParameters() method.parameterTypes @@ -155,7 +164,7 @@ abstract class MethodFingerprint( fingerprint: MethodFingerprint ): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? { val instructions = this.implementation!!.instructions - val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyScanThreshold + val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0 val pattern = fingerprint.opcodes!! val instructionLength = instructions.count() diff --git a/src/test/kotlin/app/revanced/patcher/extensions/InstructionExtensionsTest.kt b/src/test/kotlin/app/revanced/patcher/extensions/InstructionExtensionsTest.kt new file mode 100644 index 0000000..be77fcc --- /dev/null +++ b/src/test/kotlin/app/revanced/patcher/extensions/InstructionExtensionsTest.kt @@ -0,0 +1,233 @@ +package app.revanced.patcher.extensions + +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.removeInstruction +import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import app.revanced.patcher.util.smali.ExternalLabel +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.BuilderOffsetInstruction +import org.jf.dexlib2.builder.MutableMethodImplementation +import org.jf.dexlib2.builder.instruction.BuilderInstruction21s +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction +import org.jf.dexlib2.immutable.ImmutableMethod +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +private object InstructionExtensionsTest { + private lateinit var testMethod: MutableMethod + private lateinit var testMethodImplementation: MutableMethodImplementation + + @BeforeEach + fun createTestMethod() = ImmutableMethod( + "TestClass;", + "testMethod", + null, + "V", + AccessFlags.PUBLIC.value, + null, + null, + MutableMethodImplementation(16).also { testMethodImplementation = it }.apply { + repeat(10) { i -> this.addInstruction(TestInstruction(i)) } + }, + ).let { testMethod = it.toMutable() } + + @Test + fun addInstructionsToImplementationIndexed() = applyToImplementation { + addInstructions(5, getTestInstructions(5..6)).also { + assertRegisterIs(5, 5) + assertRegisterIs(6, 6) + + assertRegisterIs(5, 7) + } + } + + @Test + fun addInstructionsToImplementation() = applyToImplementation { + addInstructions(getTestInstructions(10..11)).also { + assertRegisterIs(10, 10) + assertRegisterIs(11, 11) + } + } + + @Test + fun removeInstructionsFromImplementationIndexed() = applyToImplementation { + removeInstructions(5, 5).also { assertRegisterIs(4, 4) } + } + + @Test + fun removeInstructionsFromImplementation() = applyToImplementation { + removeInstructions(0).also { assertRegisterIs(9, 9) } + removeInstructions(1).also { assertRegisterIs(1, 0) } + removeInstructions(2).also { assertRegisterIs(3, 0) } + } + + @Test + fun replaceInstructionsInImplementationIndexed() = applyToImplementation { + replaceInstructions(5, getTestInstructions(0..1)).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + assertRegisterIs(7, 7) + } + } + + @Test + fun addInstructionToMethodIndexed() = applyToMethod { + addInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) } + } + + @Test + fun addInstructionToMethod() = applyToMethod { + addInstruction(TestInstruction(0)).also { assertRegisterIs(0, 10) } + } + + @Test + fun addSmaliInstructionToMethodIndexed() = applyToMethod { + addInstruction(5, getTestSmaliInstruction(0)).also { assertRegisterIs(0, 5) } + } + + @Test + fun addSmaliInstructionToMethod() = applyToMethod { + addInstruction(getTestSmaliInstruction(0)).also { assertRegisterIs(0, 10) } + } + + @Test + fun addInstructionsToMethodIndexed() = applyToMethod { + addInstructions(5, getTestInstructions(0..1)).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + + assertRegisterIs(5, 7) + } + } + + @Test + fun addInstructionsToMethod() = applyToMethod { + addInstructions(getTestInstructions(0..1)).also { + assertRegisterIs(0, 10) + assertRegisterIs(1, 11) + + assertRegisterIs(9, 9) + } + } + + @Test + fun addSmaliInstructionsToMethodIndexed() = applyToMethod { + addInstructionsWithLabels(5, getTestSmaliInstructions(0..1)).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + + assertRegisterIs(5, 7) + } + } + + @Test + fun addSmaliInstructionsToMethod() = applyToMethod { + addInstructions(getTestSmaliInstructions(0..1)).also { + assertRegisterIs(0, 10) + assertRegisterIs(1, 11) + + assertRegisterIs(9, 9) + } + } + + @Test + fun addSmaliInstructionsWithExternalLabelToMethodIndexed() = applyToMethod { + val label = ExternalLabel("testLabel", getInstruction(5)) + + addInstructionsWithLabels( + 5, + getTestSmaliInstructions(0..1).plus("\n").plus("goto :${label.name}"), + label + ).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + assertRegisterIs(5, 8) + + val gotoTarget = getInstruction(7) + .target.location.instruction as OneRegisterInstruction + + assertEquals(5, gotoTarget.registerA) + } + } + + @Test + fun removeInstructionFromMethodIndexed() = applyToMethod { + removeInstruction(5).also { + assertRegisterIs(4, 4) + assertRegisterIs(6, 5) + } + } + + @Test + fun removeInstructionsFromMethodIndexed() = applyToMethod { + removeInstructions(5, 5).also { assertRegisterIs(4, 4) } + } + + @Test + fun removeInstructionsFromMethod() = applyToMethod { + removeInstructions(0).also { assertRegisterIs(9, 9) } + removeInstructions(1).also { assertRegisterIs(1, 0) } + removeInstructions(2).also { assertRegisterIs(3, 0) } + } + + @Test + fun replaceInstructionInMethodIndexed() = applyToMethod { + replaceInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) } + } + + @Test + fun replaceInstructionsInMethodIndexed() = applyToMethod { + replaceInstructions(5, getTestInstructions(0..1)).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + assertRegisterIs(7, 7) + } + } + + @Test + fun replaceSmaliInstructionsInMethodIndexed() = applyToMethod { + replaceInstructions(5, getTestSmaliInstructions(0..1)).also { + assertRegisterIs(0, 5) + assertRegisterIs(1, 6) + assertRegisterIs(7, 7) + } + } + + // region Helper methods + + private fun applyToImplementation(block: MutableMethodImplementation.() -> Unit) { + testMethodImplementation.apply(block) + } + + private fun applyToMethod(block: MutableMethod.() -> Unit) { + testMethod.apply(block) + } + + private fun MutableMethodImplementation.assertRegisterIs(register: Int, atIndex: Int) = assertEquals( + register, getInstruction(atIndex).registerA + ) + + private fun MutableMethod.assertRegisterIs(register: Int, atIndex: Int) = + implementation!!.assertRegisterIs(register, atIndex) + + private fun getTestInstructions(range: IntRange) = range.map { TestInstruction(it) } + + private fun getTestSmaliInstruction(register: Int) = "const/16 v$register, 0" + + private fun getTestSmaliInstructions(range: IntRange) = range.joinToString("\n") { + getTestSmaliInstruction(it) + } + + // endregion + + private class TestInstruction(register: Int) : BuilderInstruction21s(Opcode.CONST_16, register, 0) +} \ No newline at end of file diff --git a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt index 4656b25..0add464 100644 --- a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt +++ b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt @@ -4,9 +4,9 @@ import app.revanced.patcher.annotation.Description import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Version import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.or -import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.* import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.Patch @@ -119,7 +119,7 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { // 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. - method.addInstructions( + method.addInstructionsWithLabels( startIndex + 2, """ invoke-static { }, LTestClass;->returnHello()Ljava/lang/String; diff --git a/src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt b/src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt index 9d1bbd4..ddd14ff 100644 --- a/src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt +++ b/src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt @@ -1,8 +1,9 @@ package app.revanced.patcher.util.smali -import app.revanced.patcher.extensions.addInstructions -import app.revanced.patcher.extensions.instruction -import app.revanced.patcher.extensions.label +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.newLabel import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.Opcode @@ -35,7 +36,7 @@ internal class InlineSmaliCompilerTest { method.addInstructions(arrayOfNulls(insnAmount).also { Arrays.fill(it, "const/4 v0, 0x0") }.joinToString("\n")) - method.addInstructions( + method.addInstructionsWithLabels( targetIndex, """ :test @@ -44,7 +45,7 @@ internal class InlineSmaliCompilerTest { """ ) - val insn = method.instruction(insnIndex) + val insn = method.getInstruction(insnIndex) assertEquals(targetIndex, insn.target.location.index) } @@ -61,19 +62,19 @@ internal class InlineSmaliCompilerTest { """ ) - assertEquals(labelIndex, method.label(labelIndex).location.index) + assertEquals(labelIndex, method.newLabel(labelIndex).location.index) - method.addInstructions( + method.addInstructionsWithLabels( + method.implementation!!.instructions.size, """ const/4 v0, 0x1 if-eqz v0, :test return-void - """, listOf( - ExternalLabel("test",method.instruction(1)) - ) + """, + ExternalLabel("test", method.getInstruction(1)) ) - val insn = method.instruction(insnIndex) + val insn = method.getInstruction(insnIndex) assertTrue(insn.target.isPlaced, "Label was not placed") assertEquals(labelIndex, insn.target.location.index) }