mirror of
https://github.com/revanced/revanced-patcher.git
synced 2025-05-21 04:37:05 +02:00
Merge pull request #52 from revanced/feat/smali-branching
feat: improve Smali compiler
This commit is contained in:
commit
91298a8790
@ -2,12 +2,17 @@ package app.revanced.patcher.extensions
|
|||||||
|
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
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.ExternalLabel
|
||||||
import app.revanced.patcher.util.smali.toInstruction
|
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.instruction.Instruction
|
||||||
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
|
||||||
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
||||||
@ -23,6 +28,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,11 +51,11 @@ 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
|
if (MethodUtil.isConstructor(this) && !parametersEqual(
|
||||||
): Boolean {
|
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 +65,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,
|
||||||
@ -66,14 +75,7 @@ internal fun Method.clone(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return ImmutableMethod(
|
return ImmutableMethod(
|
||||||
returnType,
|
returnType, name, parameters, returnType, accessFlags, annotations, hiddenApiRestrictions, clonedImplementation
|
||||||
name,
|
|
||||||
parameters,
|
|
||||||
returnType,
|
|
||||||
accessFlags,
|
|
||||||
annotations,
|
|
||||||
hiddenApiRestrictions,
|
|
||||||
clonedImplementation
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,16 +106,89 @@ fun MutableMethod.replaceInstruction(index: Int, instruction: String) =
|
|||||||
* Remove a smali instruction within the method.
|
* Remove a smali instruction within the method.
|
||||||
* @param index The index to delete the instruction at.
|
* @param index The index to delete the instruction at.
|
||||||
*/
|
*/
|
||||||
fun MutableMethod.removeInstruction(index: Int) =
|
fun MutableMethod.removeInstruction(index: Int) = this.implementation!!.removeInstruction(index)
|
||||||
this.implementation!!.removeInstruction(index)
|
|
||||||
|
/**
|
||||||
|
* Create a label for the instruction at given index in the method's implementation.
|
||||||
|
* @param index The index to create the label for the instruction at.
|
||||||
|
* @return The label.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.label(index: Int) = this.implementation!!.newLabelForIndex(index)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instruction at given index in the method's implementation.
|
||||||
|
* @param index The index to get the instruction at.
|
||||||
|
* @return The instruction.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.instruction(index: Int): BuilderInstruction = this.implementation!!.instructions[index]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
* @param externalLabels A list of [ExternalLabel] representing a list of labels for instructions which are not in the method to compile.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun MutableMethod.addInstructions(index: Int, smali: String, externalLabels: List<ExternalLabel> = 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)
|
||||||
|
|
||||||
|
val methodInstructions = methodImplementation.instructions
|
||||||
|
methodInstructions.subList(index, index + compiledInstructions.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.
|
* @param instructions The smali instructions to add.
|
||||||
*/
|
*/
|
||||||
fun MutableMethod.addInstructions(index: Int, instructions: String) =
|
fun MutableMethod.addInstructions(instructions: String, labels: List<ExternalLabel> = 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.
|
||||||
@ -128,8 +203,21 @@ fun MutableMethod.replaceInstructions(index: Int, instructions: String) =
|
|||||||
* @param index The index to remove the instructions at.
|
* @param index The index to remove the instructions at.
|
||||||
* @param count The amount of instructions to remove.
|
* @param count The amount of instructions to remove.
|
||||||
*/
|
*/
|
||||||
fun MutableMethod.removeInstructions(index: Int, count: Int) =
|
fun MutableMethod.removeInstructions(index: Int, count: Int) = this.implementation!!.removeInstructions(index, count)
|
||||||
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.
|
* Clones the method.
|
||||||
@ -137,14 +225,11 @@ fun MutableMethod.removeInstructions(index: Int, count: Int) =
|
|||||||
* 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(
|
||||||
parameters1: Iterable<CharSequence>,
|
parameters1: Iterable<CharSequence>, parameters2: Iterable<CharSequence>
|
||||||
parameters2: Iterable<CharSequence>
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return parameters1.count() == parameters2.count() && parameters1.all { parameter ->
|
return parameters1.count() == parameters2.count() && parameters1.all { parameter ->
|
||||||
parameters2.any {
|
parameters2.any {
|
||||||
@ -155,10 +240,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,
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package app.revanced.patcher.util.smali
|
||||||
|
|
||||||
|
import org.jf.dexlib2.iface.instruction.Instruction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that represents a label for an instruction.
|
||||||
|
* @param name The label name.
|
||||||
|
* @param instruction The instruction that this label is for.
|
||||||
|
*/
|
||||||
|
data class ExternalLabel(internal val name: String, internal val instruction: Instruction)
|
@ -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,18 +27,19 @@ 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)
|
||||||
@ -54,25 +55,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()
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.")
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
@ -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(
|
||||||
|
startIndex + 2,
|
||||||
|
"""
|
||||||
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
|
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
|
||||||
move-result-object v1
|
move-result-object v1
|
||||||
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||||
"""
|
"""
|
||||||
method.addInstructions(startIndex + 2, instructions)
|
)
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
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
|
||||||
|
const/4 v0, 0x0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(labelIndex, method.label(labelIndex).location.index)
|
||||||
|
|
||||||
|
method.addInstructions(
|
||||||
|
"""
|
||||||
|
const/4 v0, 0x1
|
||||||
|
if-eqz v0, :test
|
||||||
|
return-void
|
||||||
|
""", listOf(
|
||||||
|
ExternalLabel("test",method.instruction(1))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user