feat: Improve Smali Compiler

- Branching support has been added. See InlineSmaliCompilerTest.kt for an example.
- Some other improvements have been made too.
This commit is contained in:
Lucaskyy 2022-06-27 21:11:59 +02:00
parent d6ed06a327
commit 6bfe5716c3
No known key found for this signature in database
GPG Key ID: 1530BFF96D1EEB89
6 changed files with 217 additions and 350 deletions

View File

@ -6,7 +6,10 @@ import app.revanced.patcher.util.smali.toInstruction
import app.revanced.patcher.util.smali.toInstructions import app.revanced.patcher.util.smali.toInstructions
import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.builder.BuilderInstruction 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.MutableMethodImplementation
import org.jf.dexlib2.builder.instruction.*
import org.jf.dexlib2.iface.Method import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.iface.reference.MethodReference import org.jf.dexlib2.iface.reference.MethodReference
import org.jf.dexlib2.immutable.ImmutableMethod import org.jf.dexlib2.immutable.ImmutableMethod
@ -23,6 +26,12 @@ fun MutableMethodImplementation.addInstructions(index: Int, instructions: List<B
} }
} }
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) {
for (instruction in instructions) {
this.addInstruction(instruction)
}
}
fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) { fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) {
for (i in instructions.lastIndex downTo 0) { for (i in instructions.lastIndex downTo 0) {
this.replaceInstruction(index + i, instructions[i]) this.replaceInstruction(index + i, instructions[i])
@ -40,9 +49,7 @@ fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) {
* @param otherMethod The method to compare against. * @param otherMethod The method to compare against.
* @return True if the methods match given the conditions. * @return True if the methods match given the conditions.
*/ */
fun Method.softCompareTo( fun Method.softCompareTo(otherMethod: MethodReference): Boolean {
otherMethod: MethodReference
): Boolean {
if (MethodUtil.isConstructor(this) && !parametersEqual(this.parameterTypes, otherMethod.parameterTypes)) if (MethodUtil.isConstructor(this) && !parametersEqual(this.parameterTypes, otherMethod.parameterTypes))
return false return false
return this.name == otherMethod.name return this.name == otherMethod.name
@ -54,9 +61,7 @@ fun Method.softCompareTo(
* This may be a positive or negative number. * This may be a positive or negative number.
* @return The **immutable** cloned method. Call [toMutable] or [cloneMutable] to get a **mutable** copy. * @return The **immutable** cloned method. Call [toMutable] or [cloneMutable] to get a **mutable** copy.
*/ */
internal fun Method.clone( internal fun Method.clone(registerCount: Int = 0): ImmutableMethod {
registerCount: Int = 0,
): ImmutableMethod {
val clonedImplementation = implementation?.let { val clonedImplementation = implementation?.let {
ImmutableMethodImplementation( ImmutableMethodImplementation(
it.registerCount + registerCount, it.registerCount + registerCount,
@ -110,10 +115,41 @@ fun MutableMethod.removeInstruction(index: Int) =
/** /**
* Add smali instructions to the method. * Add smali instructions to the method.
* @param index The index to insert the instructions at. * @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<Pair<String, Label>> = emptyList()) {
var code = smali
for ((name, _) in labels) {
code += "\n :$name \n nop"
}
val instructions = code.toInstructions(this).toMutableList()
var fixedInstructions: List<Int>? = 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. * @param instructions The smali instructions to add.
*/ */
fun MutableMethod.addInstructions(index: Int, instructions: String) = fun MutableMethod.addInstructions(instructions: String, labels: List<Pair<String, Label>> = emptyList()) =
this.implementation!!.addInstructions(index, instructions.toInstructions(this)) this.addInstructions(this.implementation!!.instructions.size, instructions, labels)
/** /**
* Replace smali instructions within the method. * Replace smali instructions within the method.
@ -131,15 +167,43 @@ fun MutableMethod.replaceInstructions(index: Int, instructions: String) =
fun MutableMethod.removeInstructions(index: Int, count: Int) = fun MutableMethod.removeInstructions(index: Int, count: Int) =
this.implementation!!.removeInstructions(index, count) 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<BuilderInstruction>, skipInstructions: List<Int>?) {
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. * Clones the method.
* @param registerCount This parameter allows you to change the register count of the method. * @param registerCount This parameter allows you to change the register count of the method.
* This may be a positive or negative number. * This may be a positive or negative number.
* @return The **mutable** cloned method. Call [clone] to get an **immutable** copy. * @return The **mutable** cloned method. Call [clone] to get an **immutable** copy.
*/ */
internal fun Method.cloneMutable( internal fun Method.cloneMutable(registerCount: Int = 0) = clone(registerCount).toMutable()
registerCount: Int = 0,
) = clone(registerCount).toMutable()
// FIXME: also check the order of parameters as different order equals different method overload // FIXME: also check the order of parameters as different order equals different method overload
internal fun parametersEqual( internal fun parametersEqual(
@ -155,10 +219,9 @@ internal fun parametersEqual(
} }
} }
internal val nullOutputStream: OutputStream = internal val nullOutputStream = object : OutputStream() {
object : OutputStream() { override fun write(b: Int) {}
override fun write(b: Int) {} }
}
/** /**
* Should be used to parse a list of parameters represented by their first letter, * Should be used to parse a list of parameters represented by their first letter,

View File

@ -1,12 +1,12 @@
package app.revanced.patcher.util.smali package app.revanced.patcher.util.smali
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import org.antlr.runtime.CommonTokenStream import org.antlr.runtime.CommonTokenStream
import org.antlr.runtime.TokenSource import org.antlr.runtime.TokenSource
import org.antlr.runtime.tree.CommonTreeNodeStream import org.antlr.runtime.tree.CommonTreeNodeStream
import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcodes import org.jf.dexlib2.Opcodes
import org.jf.dexlib2.builder.BuilderInstruction import org.jf.dexlib2.builder.BuilderInstruction
import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.writer.builder.DexBuilder import org.jf.dexlib2.writer.builder.DexBuilder
import org.jf.smali.LexerErrorInterface import org.jf.smali.LexerErrorInterface
import org.jf.smali.smaliFlexLexer import org.jf.smali.smaliFlexLexer
@ -27,23 +27,25 @@ class InlineSmaliCompiler {
companion object { companion object {
/** /**
* Compiles a string of Smali code to a list of instructions. * 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. * Special registers (such as p0, p1) will only work correctly
* Do not cross the boundaries of the control flow (if-nez insn, etc), * if the parameters and registers of the method are passed.
* 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].
*/ */
fun compile( fun compile(
instructions: String, parameters: String, registers: Int, forStaticMethod: Boolean instructions: String, parameters: String, registers: Int, forStaticMethod: Boolean
): List<BuilderInstruction> { ): List<BuilderInstruction> {
val input = val input = METHOD_TEMPLATE.format(
METHOD_TEMPLATE.format(if (forStaticMethod) "static" else "", parameters, registers, instructions) if (forStaticMethod) {
"static"
} else {
""
}, parameters, registers, instructions
)
val reader = InputStreamReader(input.byteInputStream()) val reader = InputStreamReader(input.byteInputStream())
val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15) val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15)
val tokens = CommonTokenStream(lexer as TokenSource) val tokens = CommonTokenStream(lexer as TokenSource)
val parser = smaliParser(tokens) val parser = smaliParser(tokens)
val result = parser.smali_file() val result = parser.smali_file()
result.tree
if (parser.numberOfSyntaxErrors > 0 || lexer.numberOfSyntaxErrors > 0) { if (parser.numberOfSyntaxErrors > 0 || lexer.numberOfSyntaxErrors > 0) {
throw IllegalStateException( throw IllegalStateException(
"Encountered ${parser.numberOfSyntaxErrors} parser syntax errors and ${lexer.numberOfSyntaxErrors} lexer syntax errors!" "Encountered ${parser.numberOfSyntaxErrors} parser syntax errors and ${lexer.numberOfSyntaxErrors} lexer syntax errors!"
@ -54,25 +56,30 @@ class InlineSmaliCompiler {
val dexGen = smaliTreeWalker(treeStream) val dexGen = smaliTreeWalker(treeStream)
dexGen.setDexBuilder(DexBuilder(Opcodes.getDefault())) dexGen.setDexBuilder(DexBuilder(Opcodes.getDefault()))
val classDef = dexGen.smali_file() 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. * 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. * @returns A list of instructions.
*/ */
fun String.toInstructions(templateMethod: Method? = null) = InlineSmaliCompiler.compile(this, fun String.toInstructions(method: MutableMethod? = null): List<BuilderInstruction> {
templateMethod?.parameters?.joinToString("") { it } ?: "", return InlineSmaliCompiler.compile(this,
templateMethod?.implementation?.registerCount ?: 1, method?.parameters?.joinToString("") { it } ?: "",
templateMethod?.let { AccessFlags.STATIC.isSet(it.accessFlags) } ?: true method?.implementation?.registerCount ?: 1,
) method?.let { AccessFlags.STATIC.isSet(it.accessFlags) } ?: true
)
}
/** /**
* Compile a line of Smali code to an instruction. * Compile a line of Smali code to an instruction.
* @param templateMethod The method to compile the instructions against. * @param templateMethod The method to compile the instructions against.
* @return The instruction. * @return The instruction.
*/ */
fun String.toInstruction(templateMethod: Method? = null) = this.toInstructions(templateMethod).first() fun String.toInstruction(templateMethod: MutableMethod? = null) = this.toInstructions(templateMethod).first()

View File

@ -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
)
}
}
}

View File

@ -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.")
*/
}
}

View File

@ -6,6 +6,7 @@ import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.impl.BytecodeData import app.revanced.patcher.data.impl.BytecodeData
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.Patch 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.usage.resource.annotation.ExampleResourceCompatibility
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable 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.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.util.smali.toInstruction
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Format import org.jf.dexlib2.Format
@ -107,22 +107,21 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
) )
// store the fields initial value into the first virtual register // store the fields initial value into the first virtual register
implementation.replaceInstruction( method.replaceInstruction(0, "sget-object v0, LTestClass;->dummyField:Ljava/io/PrintStream;")
0,
"sget-object v0, LTestClass;->dummyField:Ljava/io/PrintStream;".toInstruction()
)
// Now let's create a new call to our method and print the return value! // 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. // 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. // 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. // Control flow instructions are not supported as of now.
val instructions = """ method.addInstructions(
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String; startIndex + 2,
move-result-object v1 """
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
""" move-result-object v1
method.addInstructions(startIndex + 2, instructions) invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
"""
)
// Finally, tell the patcher that this patch was a success. // Finally, tell the patcher that this patch was a success.
// You can also return PatchResultError with a message. // You can also return PatchResultError with a message.

View File

@ -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<String>(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)
}
}
}