From 6bfe5716c38181bbe9476b5c6ad29526edb4e022 Mon Sep 17 00:00:00 2001 From: Lucaskyy Date: Mon, 27 Jun 2022 21:11:59 +0200 Subject: [PATCH] feat: Improve Smali Compiler - Branching support has been added. See InlineSmaliCompilerTest.kt for an example. - Some other improvements have been made too. --- .../revanced/patcher/extensions/Extensions.kt | 93 ++++++- .../patcher/util/smali/InlineSmaliCompiler.kt | 41 +-- .../util/smali/InstructionConverter.kt | 261 ------------------ .../app/revanced/patcher/usage/PatcherTest.kt | 46 --- .../bytecode/patch/ExampleBytecodePatch.kt | 21 +- .../util/smali/InlineSmaliCompilerTest.kt | 105 +++++++ 6 files changed, 217 insertions(+), 350 deletions(-) delete mode 100644 src/main/kotlin/app/revanced/patcher/util/smali/InstructionConverter.kt delete mode 100644 src/test/kotlin/app/revanced/patcher/usage/PatcherTest.kt create mode 100644 src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt diff --git a/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt index 50384e81..7d909854 100644 --- a/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt +++ b/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt @@ -6,7 +6,10 @@ 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.reference.MethodReference import org.jf.dexlib2.immutable.ImmutableMethod @@ -23,6 +26,12 @@ fun MutableMethodImplementation.addInstructions(index: Int, 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]) @@ -40,9 +49,7 @@ fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) { * @param otherMethod The method to compare against. * @return True if the methods match given the conditions. */ -fun Method.softCompareTo( - otherMethod: MethodReference -): Boolean { +fun Method.softCompareTo(otherMethod: MethodReference): Boolean { if (MethodUtil.isConstructor(this) && !parametersEqual(this.parameterTypes, otherMethod.parameterTypes)) return false return this.name == otherMethod.name @@ -54,9 +61,7 @@ fun Method.softCompareTo( * 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 { +internal fun Method.clone(registerCount: Int = 0): ImmutableMethod { val clonedImplementation = implementation?.let { ImmutableMethodImplementation( it.registerCount + registerCount, @@ -110,10 +115,41 @@ fun MutableMethod.removeInstruction(index: Int) = /** * Add smali instructions to the method. * @param index The index to insert the instructions at. + * @param smali The smali instructions to add. + */ +fun MutableMethod.addInstructions(index: Int, smali: String, labels: List> = emptyList()) { + var code = smali + for ((name, _) in labels) { + code += "\n :$name \n nop" + } + val instructions = code.toInstructions(this).toMutableList() + var fixedInstructions: List? = null // find a better way to do this. + + if (labels.isNotEmpty()) { + val labelRange = instructions.size - labels.size..instructions.size + fixedInstructions = mutableListOf() + for (instructionIndex in 0 until instructions.size - labels.size) { + val instruction = instructions[instructionIndex] + if (instruction !is BuilderOffsetInstruction || !instruction.target.isPlaced) continue + val fakeIndex = instruction.target.location.index + if (!labelRange.contains(fakeIndex)) continue + instructions[instructionIndex] = replaceOffset(instruction, labels[labelRange.indexOf(fakeIndex)].second) + fixedInstructions.add(instructionIndex + index) + } + // find a better way to drop the nop instructions. + instructions.subList(labelRange.first, labelRange.last).clear() + } + + this.implementation!!.addInstructions(index, instructions) + this.fixInstructions(index, instructions, fixedInstructions) +} + +/** + * Add smali instructions to the end of the method. * @param instructions The smali instructions to add. */ -fun MutableMethod.addInstructions(index: Int, instructions: String) = - this.implementation!!.addInstructions(index, instructions.toInstructions(this)) +fun MutableMethod.addInstructions(instructions: String, labels: List> = emptyList()) = + this.addInstructions(this.implementation!!.instructions.size, instructions, labels) /** * Replace smali instructions within the method. @@ -131,15 +167,43 @@ fun MutableMethod.replaceInstructions(index: Int, instructions: String) = fun MutableMethod.removeInstructions(index: Int, count: Int) = this.implementation!!.removeInstructions(index, count) +fun MutableMethod.label(index: Int) = this.implementation!!.newLabelForIndex(index) +fun MutableMethod.instruction(index: Int): BuilderInstruction = this.implementation!!.instructions[index] + +private fun MutableMethod.fixInstructions(index: Int, instructions: List, skipInstructions: List?) { + for (instructionIndex in index until instructions.size + index) { + val instruction = this.implementation!!.instructions[instructionIndex] + if (instruction !is BuilderOffsetInstruction || !instruction.target.isPlaced) continue + if (skipInstructions?.contains(instructionIndex) == true) continue + val fakeIndex = instruction.target.location.index + val fixedIndex = fakeIndex + index + if (fakeIndex == fixedIndex) continue // no need to replace if the indexes are the same. + this.implementation!!.replaceInstruction(instructionIndex, replaceOffset(instruction, this.label(fixedIndex))) + } +} + +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 Method.cloneMutable(registerCount: Int = 0) = clone(registerCount).toMutable() // FIXME: also check the order of parameters as different order equals different method overload internal fun parametersEqual( @@ -155,10 +219,9 @@ internal fun parametersEqual( } } -internal val nullOutputStream: OutputStream = - object : OutputStream() { - override fun write(b: Int) {} - } +internal val nullOutputStream = object : OutputStream() { + override fun write(b: Int) {} +} /** * Should be used to parse a list of parameters represented by their first letter, diff --git a/src/main/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompiler.kt b/src/main/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompiler.kt index eb5eca5a..88d77832 100644 --- a/src/main/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompiler.kt +++ b/src/main/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompiler.kt @@ -1,12 +1,12 @@ package app.revanced.patcher.util.smali +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import org.antlr.runtime.CommonTokenStream import org.antlr.runtime.TokenSource import org.antlr.runtime.tree.CommonTreeNodeStream import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.Opcodes import org.jf.dexlib2.builder.BuilderInstruction -import org.jf.dexlib2.iface.Method import org.jf.dexlib2.writer.builder.DexBuilder import org.jf.smali.LexerErrorInterface import org.jf.smali.smaliFlexLexer @@ -27,23 +27,25 @@ class InlineSmaliCompiler { companion object { /** * Compiles a string of Smali code to a list of instructions. - * p0, p1 etc. will only work correctly if the parameters and registers are passed. - * Do not cross the boundaries of the control flow (if-nez insn, etc), - * as that will result in exceptions since the labels cannot be calculated. - * Do not create dummy labels to fix the issue, since the code addresses will - * be messed up and results in broken Dalvik bytecode. - * FIXME: Fix the above issue. When this is fixed, add the proper conversions in [InstructionConverter]. + * Special registers (such as p0, p1) will only work correctly + * if the parameters and registers of the method are passed. */ fun compile( instructions: String, parameters: String, registers: Int, forStaticMethod: Boolean ): List { - val input = - METHOD_TEMPLATE.format(if (forStaticMethod) "static" else "", parameters, registers, instructions) + val input = METHOD_TEMPLATE.format( + if (forStaticMethod) { + "static" + } else { + "" + }, parameters, registers, instructions + ) val reader = InputStreamReader(input.byteInputStream()) val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15) val tokens = CommonTokenStream(lexer as TokenSource) val parser = smaliParser(tokens) val result = parser.smali_file() + result.tree if (parser.numberOfSyntaxErrors > 0 || lexer.numberOfSyntaxErrors > 0) { throw IllegalStateException( "Encountered ${parser.numberOfSyntaxErrors} parser syntax errors and ${lexer.numberOfSyntaxErrors} lexer syntax errors!" @@ -54,25 +56,30 @@ class InlineSmaliCompiler { val dexGen = smaliTreeWalker(treeStream) dexGen.setDexBuilder(DexBuilder(Opcodes.getDefault())) val classDef = dexGen.smali_file() - return classDef.methods.first().implementation!!.instructions.map { it.toBuilderInstruction() } + return classDef.methods.first().implementation!!.instructions.map { it as BuilderInstruction } } } } /** * Compile lines of Smali code to a list of instructions. - * @param templateMethod The method to compile the instructions against. + * + * Note: Adding compiled instructions to an existing method with + * offset instructions WITHOUT specifying a parent method will not work. + * @param method The method to compile the instructions against. * @returns A list of instructions. */ -fun String.toInstructions(templateMethod: Method? = null) = InlineSmaliCompiler.compile(this, - templateMethod?.parameters?.joinToString("") { it } ?: "", - templateMethod?.implementation?.registerCount ?: 1, - templateMethod?.let { AccessFlags.STATIC.isSet(it.accessFlags) } ?: true -) +fun String.toInstructions(method: MutableMethod? = null): List { + return InlineSmaliCompiler.compile(this, + method?.parameters?.joinToString("") { it } ?: "", + method?.implementation?.registerCount ?: 1, + method?.let { AccessFlags.STATIC.isSet(it.accessFlags) } ?: true + ) +} /** * Compile a line of Smali code to an instruction. * @param templateMethod The method to compile the instructions against. * @return The instruction. */ -fun String.toInstruction(templateMethod: Method? = null) = this.toInstructions(templateMethod).first() +fun String.toInstruction(templateMethod: MutableMethod? = null) = this.toInstructions(templateMethod).first() \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/util/smali/InstructionConverter.kt b/src/main/kotlin/app/revanced/patcher/util/smali/InstructionConverter.kt deleted file mode 100644 index e4c5ae1b..00000000 --- a/src/main/kotlin/app/revanced/patcher/util/smali/InstructionConverter.kt +++ /dev/null @@ -1,261 +0,0 @@ -package app.revanced.patcher.util.smali - -import org.jf.dexlib2.Format -import org.jf.dexlib2.builder.instruction.* -import org.jf.dexlib2.iface.instruction.Instruction -import org.jf.dexlib2.iface.instruction.formats.* -import org.jf.util.ExceptionWithContext - -fun Instruction.toBuilderInstruction() = - when (this.opcode.format) { - Format.Format10x -> InstructionConverter.newBuilderInstruction10x(this as Instruction10x) - Format.Format11n -> InstructionConverter.newBuilderInstruction11n(this as Instruction11n) - Format.Format11x -> InstructionConverter.newBuilderInstruction11x(this as Instruction11x) - Format.Format12x -> InstructionConverter.newBuilderInstruction12x(this as Instruction12x) - Format.Format20bc -> InstructionConverter.newBuilderInstruction20bc(this as Instruction20bc) - Format.Format21c -> InstructionConverter.newBuilderInstruction21c(this as Instruction21c) - Format.Format21ih -> InstructionConverter.newBuilderInstruction21ih(this as Instruction21ih) - Format.Format21lh -> InstructionConverter.newBuilderInstruction21lh(this as Instruction21lh) - Format.Format21s -> InstructionConverter.newBuilderInstruction21s(this as Instruction21s) - Format.Format22b -> InstructionConverter.newBuilderInstruction22b(this as Instruction22b) - Format.Format22c -> InstructionConverter.newBuilderInstruction22c(this as Instruction22c) - Format.Format22cs -> InstructionConverter.newBuilderInstruction22cs(this as Instruction22cs) - Format.Format22s -> InstructionConverter.newBuilderInstruction22s(this as Instruction22s) - Format.Format22x -> InstructionConverter.newBuilderInstruction22x(this as Instruction22x) - Format.Format23x -> InstructionConverter.newBuilderInstruction23x(this as Instruction23x) - Format.Format31c -> InstructionConverter.newBuilderInstruction31c(this as Instruction31c) - Format.Format31i -> InstructionConverter.newBuilderInstruction31i(this as Instruction31i) - Format.Format32x -> InstructionConverter.newBuilderInstruction32x(this as Instruction32x) - Format.Format35c -> InstructionConverter.newBuilderInstruction35c(this as Instruction35c) - Format.Format35mi -> InstructionConverter.newBuilderInstruction35mi(this as Instruction35mi) - Format.Format35ms -> InstructionConverter.newBuilderInstruction35ms(this as Instruction35ms) - Format.Format3rc -> InstructionConverter.newBuilderInstruction3rc(this as Instruction3rc) - Format.Format3rmi -> InstructionConverter.newBuilderInstruction3rmi(this as Instruction3rmi) - Format.Format3rms -> InstructionConverter.newBuilderInstruction3rms(this as Instruction3rms) - Format.Format51l -> InstructionConverter.newBuilderInstruction51l(this as Instruction51l) - else -> throw ExceptionWithContext("Instruction format %s not supported", this.opcode.format) - } - -internal class InstructionConverter { - companion object { - internal fun newBuilderInstruction10x(instruction: Instruction10x): BuilderInstruction10x { - return BuilderInstruction10x( - instruction.opcode - ) - } - - internal fun newBuilderInstruction11n(instruction: Instruction11n): BuilderInstruction11n { - return BuilderInstruction11n( - instruction.opcode, - instruction.registerA, - instruction.narrowLiteral - ) - } - - internal fun newBuilderInstruction11x(instruction: Instruction11x): BuilderInstruction11x { - return BuilderInstruction11x( - instruction.opcode, - instruction.registerA - ) - } - - internal fun newBuilderInstruction12x(instruction: Instruction12x): BuilderInstruction12x { - return BuilderInstruction12x( - instruction.opcode, - instruction.registerA, - instruction.registerB - ) - } - - internal fun newBuilderInstruction20bc(instruction: Instruction20bc): BuilderInstruction20bc { - return BuilderInstruction20bc( - instruction.opcode, - instruction.verificationError, - instruction.reference - ) - } - - internal fun newBuilderInstruction21c(instruction: Instruction21c): BuilderInstruction21c { - return BuilderInstruction21c( - instruction.opcode, - instruction.registerA, - instruction.reference - ) - } - - internal fun newBuilderInstruction21ih(instruction: Instruction21ih): BuilderInstruction21ih { - return BuilderInstruction21ih( - instruction.opcode, - instruction.registerA, - instruction.narrowLiteral - ) - } - - internal fun newBuilderInstruction21lh(instruction: Instruction21lh): BuilderInstruction21lh { - return BuilderInstruction21lh( - instruction.opcode, - instruction.registerA, - instruction.wideLiteral - ) - } - - internal fun newBuilderInstruction21s(instruction: Instruction21s): BuilderInstruction21s { - return BuilderInstruction21s( - instruction.opcode, - instruction.registerA, - instruction.narrowLiteral - ) - } - - internal fun newBuilderInstruction22b(instruction: Instruction22b): BuilderInstruction22b { - return BuilderInstruction22b( - instruction.opcode, - instruction.registerA, - instruction.registerB, - instruction.narrowLiteral - ) - } - - internal fun newBuilderInstruction22c(instruction: Instruction22c): BuilderInstruction22c { - return BuilderInstruction22c( - instruction.opcode, - instruction.registerA, - instruction.registerB, - instruction.reference - ) - } - - internal fun newBuilderInstruction22cs(instruction: Instruction22cs): BuilderInstruction22cs { - return BuilderInstruction22cs( - instruction.opcode, - instruction.registerA, - instruction.registerB, - instruction.fieldOffset - ) - } - - internal fun newBuilderInstruction22s(instruction: Instruction22s): BuilderInstruction22s { - return BuilderInstruction22s( - instruction.opcode, - instruction.registerA, - instruction.registerB, - instruction.narrowLiteral - ) - } - - internal fun newBuilderInstruction22x(instruction: Instruction22x): BuilderInstruction22x { - return BuilderInstruction22x( - instruction.opcode, - instruction.registerA, - instruction.registerB - ) - } - - internal fun newBuilderInstruction23x(instruction: Instruction23x): BuilderInstruction23x { - return BuilderInstruction23x( - instruction.opcode, - instruction.registerA, - instruction.registerB, - instruction.registerC - ) - } - - internal fun newBuilderInstruction31c(instruction: Instruction31c): BuilderInstruction31c { - return BuilderInstruction31c( - instruction.opcode, - instruction.registerA, - instruction.reference - ) - } - - internal fun newBuilderInstruction31i(instruction: Instruction31i): BuilderInstruction31i { - return BuilderInstruction31i( - instruction.opcode, - instruction.registerA, - instruction.narrowLiteral - ) - } - - internal fun newBuilderInstruction32x(instruction: Instruction32x): BuilderInstruction32x { - return BuilderInstruction32x( - instruction.opcode, - instruction.registerA, - instruction.registerB - ) - } - - internal fun newBuilderInstruction35c(instruction: Instruction35c): BuilderInstruction35c { - return BuilderInstruction35c( - instruction.opcode, - instruction.registerCount, - instruction.registerC, - instruction.registerD, - instruction.registerE, - instruction.registerF, - instruction.registerG, - instruction.reference - ) - } - - internal fun newBuilderInstruction35mi(instruction: Instruction35mi): BuilderInstruction35mi { - return BuilderInstruction35mi( - instruction.opcode, - instruction.registerCount, - instruction.registerC, - instruction.registerD, - instruction.registerE, - instruction.registerF, - instruction.registerG, - instruction.inlineIndex - ) - } - - internal fun newBuilderInstruction35ms(instruction: Instruction35ms): BuilderInstruction35ms { - return BuilderInstruction35ms( - instruction.opcode, - instruction.registerCount, - instruction.registerC, - instruction.registerD, - instruction.registerE, - instruction.registerF, - instruction.registerG, - instruction.vtableIndex - ) - } - - internal fun newBuilderInstruction3rc(instruction: Instruction3rc): BuilderInstruction3rc { - return BuilderInstruction3rc( - instruction.opcode, - instruction.startRegister, - instruction.registerCount, - instruction.reference - ) - } - - internal fun newBuilderInstruction3rmi(instruction: Instruction3rmi): BuilderInstruction3rmi { - return BuilderInstruction3rmi( - instruction.opcode, - instruction.startRegister, - instruction.registerCount, - instruction.inlineIndex - ) - } - - internal fun newBuilderInstruction3rms(instruction: Instruction3rms): BuilderInstruction3rms { - return BuilderInstruction3rms( - instruction.opcode, - instruction.startRegister, - instruction.registerCount, - instruction.vtableIndex - ) - } - - internal fun newBuilderInstruction51l(instruction: Instruction51l): BuilderInstruction51l { - return BuilderInstruction51l( - instruction.opcode, - instruction.registerA, - instruction.wideLiteral - ) - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/app/revanced/patcher/usage/PatcherTest.kt b/src/test/kotlin/app/revanced/patcher/usage/PatcherTest.kt deleted file mode 100644 index a6845efc..00000000 --- a/src/test/kotlin/app/revanced/patcher/usage/PatcherTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package app.revanced.patcher.usage - -import org.junit.jupiter.api.Test - -internal class PatcherTest { - @Test - fun testPatcher() { - return // FIXME: create a proper resource to pass this test - /** - val patcher = Patcher( - File(PatcherTest::class.java.getResource("/example.apk")!!.toURI()), - "exampleCacheDirectory", - patchResources = true - ) - - patcher.addPatches(listOf(ExampleBytecodePatch(), ExampleResourcePatch())) - - for (signature in patcher.resolveSignatures()) { - if (!signature.resolved) { - throw Exception("Signature ${signature.metadata.name} was not resolved!") - } - val patternScanMethod = signature.metadata.patternScanMethod - if (patternScanMethod is PatternScanMethod.Fuzzy) { - val warnings = patternScanMethod.warnings - if (warnings != null) { - println("Signature ${signature.metadata.name} had ${warnings.size} warnings!") - for (warning in warnings) { - println(warning.toString()) - } - } else { - println("Signature ${signature.metadata.name} used the fuzzy resolver, but the warnings list is null!") - } - } - } - for ((metadata, result) in patcher.applyPatches()) { - if (result.isFailure) { - throw Exception("Patch ${metadata.shortName} failed", result.exceptionOrNull()!!) - } else { - println("Patch ${metadata.shortName} applied successfully!") - } - } - val out = patcher.save() - assertTrue(out.isNotEmpty(), "Expected the output of Patcher#save() to not be empty.") - */ - } -} \ No newline at end of file diff --git a/src/test/kotlin/app/revanced/patcher/usage/bytecode/patch/ExampleBytecodePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/bytecode/patch/ExampleBytecodePatch.kt index b4351bbf..095e2b78 100644 --- a/src/test/kotlin/app/revanced/patcher/usage/bytecode/patch/ExampleBytecodePatch.kt +++ b/src/test/kotlin/app/revanced/patcher/usage/bytecode/patch/ExampleBytecodePatch.kt @@ -6,6 +6,7 @@ import app.revanced.patcher.annotation.Version import app.revanced.patcher.data.impl.BytecodeData import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.or +import app.revanced.patcher.extensions.replaceInstruction import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResultSuccess import app.revanced.patcher.patch.annotations.Patch @@ -14,7 +15,6 @@ import app.revanced.patcher.usage.bytecode.fingerprints.ExampleFingerprint import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable -import app.revanced.patcher.util.smali.toInstruction import com.google.common.collect.ImmutableList import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.Format @@ -107,22 +107,21 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { ) // store the fields initial value into the first virtual register - implementation.replaceInstruction( - 0, - "sget-object v0, LTestClass;->dummyField:Ljava/io/PrintStream;".toInstruction() - ) + method.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. - 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 - """ - method.addInstructions(startIndex + 2, instructions) + method.addInstructions( + 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 + """ + ) // Finally, tell the patcher that this patch was a success. // You can also return PatchResultError with a message. diff --git a/src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt b/src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt new file mode 100644 index 00000000..99e63ecb --- /dev/null +++ b/src/test/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompilerTest.kt @@ -0,0 +1,105 @@ +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.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.BuilderInstruction +import org.jf.dexlib2.builder.MutableMethodImplementation +import org.jf.dexlib2.builder.instruction.BuilderInstruction21c +import org.jf.dexlib2.builder.instruction.BuilderInstruction21t +import org.jf.dexlib2.immutable.ImmutableMethod +import org.jf.dexlib2.immutable.reference.ImmutableStringReference +import java.util.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +internal class InlineSmaliCompilerTest { + @Test + fun `compiler should output valid instruction`() { + val want = BuilderInstruction21c(Opcode.CONST_STRING, 0, ImmutableStringReference("Test")) as BuilderInstruction + val have = "const-string v0, \"Test\"".toInstruction() + instructionEquals(want, have) + } + + @Test + fun `compiler should support branching with own branches`() { + val method = createMethod() + val insnAmount = 8 + val insnIndex = insnAmount - 2 + val targetIndex = insnIndex - 1 + + method.addInstructions(arrayOfNulls(insnAmount).also { + Arrays.fill(it, "const/4 v0, 0x0") + }.joinToString("\n")) + method.addInstructions( + targetIndex, + """ + :test + const/4 v0, 0x1 + if-eqz v0, :test + """ + ) + + val insn = method.instruction(insnIndex) as BuilderInstruction21t + assertEquals(targetIndex, insn.target.location.index) + } + + @Test + fun `compiler should support branching to outside branches`() { + val method = createMethod() + val insnIndex = 3 + val labelIndex = 1 + + method.addInstructions( + """ + const/4 v0, 0x1 + :test + const/4 v0, 0x0 + """ + ) + + assertEquals(labelIndex, method.label(labelIndex).location.index) + + method.addInstructions( + """ + const/4 v0, 0x1 + if-eqz v0, :test + return-void + """, listOf( + "test" to method.label(labelIndex) + ) + ) + + val insn = method.instruction(insnIndex) as BuilderInstruction21t + assertTrue(insn.target.isPlaced, "Label was not placed") + assertEquals(labelIndex, insn.target.location.index) + } + + companion object { + private fun createMethod( + name: String = "dummy", + returnType: String = "V", + accessFlags: Int = AccessFlags.STATIC.value, + registerCount: Int = 1, + ) = ImmutableMethod( + "Ldummy;", + name, + emptyList(), // parameters + returnType, + accessFlags, + emptySet(), + emptySet(), + MutableMethodImplementation(registerCount) + ).toMutable() + + private fun instructionEquals(want: BuilderInstruction, have: BuilderInstruction) { + assertEquals(want.opcode, have.opcode) + assertEquals(want.format, have.format) + assertEquals(want.codeUnits, have.codeUnits) + } + } +} \ No newline at end of file