mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-18 00:43:56 +00:00
It's so much better now. Really happy with the current system. BREAKING CHANGE: Options has been moved from Patch to a new interface called OptionsContainer and are now handled entirely different. Make sure to check the examples to understand how it works.
201 lines
8.1 KiB
Kotlin
201 lines
8.1 KiB
Kotlin
package app.revanced.patcher.usage.bytecode
|
|
|
|
import app.revanced.patcher.annotation.Description
|
|
import app.revanced.patcher.annotation.Name
|
|
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.OptionsContainer
|
|
import app.revanced.patcher.patch.PatchOption
|
|
import app.revanced.patcher.patch.PatchResult
|
|
import app.revanced.patcher.patch.PatchResultSuccess
|
|
import app.revanced.patcher.patch.annotations.DependsOn
|
|
import app.revanced.patcher.patch.annotations.Patch
|
|
import app.revanced.patcher.patch.impl.BytecodePatch
|
|
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 com.google.common.collect.ImmutableList
|
|
import org.jf.dexlib2.AccessFlags
|
|
import org.jf.dexlib2.Format
|
|
import org.jf.dexlib2.Opcode
|
|
import org.jf.dexlib2.builder.MutableMethodImplementation
|
|
import org.jf.dexlib2.builder.instruction.BuilderInstruction11x
|
|
import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
|
|
import org.jf.dexlib2.iface.instruction.formats.Instruction21c
|
|
import org.jf.dexlib2.immutable.ImmutableField
|
|
import org.jf.dexlib2.immutable.ImmutableMethod
|
|
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
|
import org.jf.dexlib2.immutable.reference.ImmutableFieldReference
|
|
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
|
|
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
|
|
import org.jf.dexlib2.util.Preconditions
|
|
import java.io.File
|
|
import java.nio.file.Path
|
|
|
|
@Patch
|
|
@Name("example-bytecode-patch")
|
|
@Description("Example demonstration of a bytecode patch.")
|
|
@ExampleResourceCompatibility
|
|
@Version("0.0.1")
|
|
@DependsOn([ExampleBytecodePatch::class])
|
|
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
|
// This function will be executed by the patcher.
|
|
// You can treat it as a constructor
|
|
override fun execute(data: BytecodeData): PatchResult {
|
|
// Get the resolved method by its fingerprint from the resolver cache
|
|
val result = ExampleFingerprint.result!!
|
|
|
|
// Patch options
|
|
println(key1)
|
|
key2 = false
|
|
|
|
// Get the implementation for the resolved method
|
|
val method = result.mutableMethod
|
|
val implementation = method.implementation!!
|
|
|
|
// Let's modify it, so it prints "Hello, ReVanced! Editing bytecode."
|
|
// Get the start index of our opcode pattern.
|
|
// This will be the index of the instruction with the opcode CONST_STRING.
|
|
val startIndex = result.patternScanResult!!.startIndex
|
|
|
|
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
|
|
|
|
// Get the class in which the method matching our fingerprint is defined in.
|
|
val mainClass = data.findClass {
|
|
it.type == result.classDef.type
|
|
}!!.resolve()
|
|
|
|
// Add a new method returning a string
|
|
mainClass.methods.add(
|
|
ImmutableMethod(
|
|
result.classDef.type,
|
|
"returnHello",
|
|
null,
|
|
"Ljava/lang/String;",
|
|
AccessFlags.PRIVATE or AccessFlags.STATIC,
|
|
null,
|
|
null,
|
|
ImmutableMethodImplementation(
|
|
1,
|
|
ImmutableList.of(
|
|
BuilderInstruction21c(
|
|
Opcode.CONST_STRING,
|
|
0,
|
|
ImmutableStringReference("Hello, ReVanced! Adding bytecode.")
|
|
),
|
|
BuilderInstruction11x(Opcode.RETURN_OBJECT, 0)
|
|
),
|
|
null,
|
|
null
|
|
)
|
|
).toMutable()
|
|
)
|
|
|
|
// Add a field in the main class
|
|
// We will use this field in our method below to call println on
|
|
// The field holds the Ljava/io/PrintStream->out; field
|
|
mainClass.fields.add(
|
|
ImmutableField(
|
|
mainClass.type,
|
|
"dummyField",
|
|
"Ljava/io/PrintStream;",
|
|
AccessFlags.PRIVATE or AccessFlags.STATIC,
|
|
ImmutableFieldEncodedValue(
|
|
ImmutableFieldReference(
|
|
"Ljava/lang/System;",
|
|
"out",
|
|
"Ljava/io/PrintStream;"
|
|
)
|
|
),
|
|
null,
|
|
null
|
|
).toMutable()
|
|
)
|
|
|
|
// store the fields initial value into the first virtual register
|
|
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.
|
|
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.
|
|
// If an exception is thrown inside this function,
|
|
// a PatchResultError will be returned with the error message.
|
|
return PatchResultSuccess()
|
|
}
|
|
|
|
/**
|
|
* Replace the string for an instruction at the given index with a new one.
|
|
* @param index The index of the instruction to replace the string for
|
|
* @param string The replacing string
|
|
*/
|
|
private fun MutableMethodImplementation.replaceStringAt(index: Int, string: String) {
|
|
val instruction = this.instructions[index]
|
|
|
|
// Utility method of dexlib2
|
|
Preconditions.checkFormat(instruction.opcode, Format.Format21c)
|
|
|
|
// Cast this to an instruction of the format 21c
|
|
// The instruction format can be found in the docs at
|
|
// https://source.android.com/devices/tech/dalvik/dalvik-bytecode
|
|
val strInstruction = instruction as Instruction21c
|
|
|
|
// In our case we want an instruction with the opcode CONST_STRING
|
|
// The format is 21c, so we create a new BuilderInstruction21c
|
|
// This instruction will hold the string reference constant in the virtual register of the original instruction
|
|
// For that a reference to the string is needed. It can be created with an ImmutableStringReference.
|
|
// At last, use the method replaceInstruction to replace it at the given index startIndex.
|
|
this.replaceInstruction(
|
|
index,
|
|
BuilderInstruction21c(
|
|
Opcode.CONST_STRING,
|
|
strInstruction.registerA,
|
|
ImmutableStringReference(string)
|
|
)
|
|
)
|
|
}
|
|
|
|
companion object : OptionsContainer() {
|
|
private var key1: String by option(
|
|
PatchOption.StringOption(
|
|
"key1", "default", "title", "description", true
|
|
)
|
|
)
|
|
private var key2: Boolean by option(
|
|
PatchOption.BooleanOption(
|
|
"key2", true, "title", "description" // required defaults to false
|
|
)
|
|
)
|
|
private var key3: List<String> by option(
|
|
PatchOption.StringListOption(
|
|
"key3", "TEST", listOf("TEST", "TEST1", "TEST2"), "title", "description"
|
|
)
|
|
)
|
|
private var key4: List<Int> by option(
|
|
PatchOption.IntListOption(
|
|
"key4", 1, listOf(1, 2, 3), "title", "description"
|
|
)
|
|
)
|
|
private var key5: Path by option(
|
|
PatchOption.PathOption(
|
|
"key5", File("test.txt").toPath(), "title", "description"
|
|
)
|
|
)
|
|
}
|
|
}
|