mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-30 14:41:03 +00:00
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:
@@ -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.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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user