Skip to content

Commit

Permalink
feat: Add first tests
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Jun 5, 2022
1 parent a16c8ca commit 544bcf7
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 126 deletions.
Expand Up @@ -29,7 +29,7 @@ class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
// Fields
private val _fields by lazy { classDef.fields.map { field -> field.toMutable() }.toMutableSet() }
private val _staticFields by lazy { Iterables.filter(_fields, FieldUtil.FIELD_IS_STATIC).toMutableSet() }
private val _instanceFields by lazy {Iterables.filter(_fields, FieldUtil.FIELD_IS_INSTANCE) .toMutableSet() }
private val _instanceFields by lazy { Iterables.filter(_fields, FieldUtil.FIELD_IS_INSTANCE).toMutableSet() }

fun setType(type: String) {
this.type = type
Expand Down
Expand Up @@ -31,8 +31,8 @@ internal class SignatureResolver(
val classProxy = ClassProxy(classDef, index)
methodMap[signature.name] = SignatureResolverResult(
classProxy,
patternScanData,
method.name,
patternScanData
)
}
}
Expand All @@ -53,8 +53,8 @@ internal class SignatureResolver(
val result = compareSignatureToMethod(signature, method) ?: continue
return SignatureResolverResult(
classProxy,
result,
method.name,
result
)
}
return null
Expand Down
@@ -1,6 +1,7 @@
package app.revanced.patcher.signature

import app.revanced.patcher.proxy.ClassProxy
import app.revanced.patcher.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.resolver.SignatureResolver

/**
Expand All @@ -11,9 +12,14 @@ import app.revanced.patcher.resolver.SignatureResolver
*/
data class SignatureResolverResult(
val definingClassProxy: ClassProxy,
val resolvedMethodName: String,
val scanData: PatternScanResult?
val scanData: PatternScanResult,
private val resolvedMethodName: String,
) {

fun resolveAndGetMethod(): MutableMethod {
return definingClassProxy.resolve().methods.single { it.name == resolvedMethodName }
}

@Suppress("Unused") // TODO(Sculas): remove this when we have coverage for this method.
fun findParentMethod(signature: MethodSignature): SignatureResolverResult? {
return SignatureResolver.resolveFromProxy(definingClassProxy, signature)
Expand Down
128 changes: 128 additions & 0 deletions src/test/kotlin/app/revanced/patcher/PatcherTest.kt
@@ -0,0 +1,128 @@
package app.revanced.patcher

import app.revanced.patcher.cache.Cache
import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.signature.MethodSignature
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
import org.jf.dexlib2.builder.instruction.BuilderInstruction35c
import org.jf.dexlib2.immutable.reference.ImmutableMethodReference
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
import org.junit.jupiter.api.Test
import java.io.File

internal class PatcherTest {
companion object {
val testSignatures: Array<MethodSignature> = arrayOf(
MethodSignature(
"main-method",
"V",
AccessFlags.PUBLIC or AccessFlags.STATIC,
setOf("[L"),
arrayOf(
Opcode.CONST_STRING,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
)
)
)
}

@Test
fun testPatcher() {
val patcher = Patcher(
File(PatcherTest::class.java.getResource("/test1.apk")!!.toURI()),
File("/"),
testSignatures
)

patcher.addPatches(
object : Patch("TestPatch") {
override fun execute(cache: Cache): PatchResult {
// Get the result from the resolver cache
val result = cache.resolvedMethods["main-method"]
// Get the implementation for the resolved method
val implementation = result.resolveAndGetMethod().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.scanData.startIndex

// the instruction format can be found via the docs at https://source.android.com/devices/tech/dalvik/dalvik-bytecode
// in our case we want an instruction with the opcode CONST_STRING and the string "Hello, ReVanced! Editing bytecode."
// the format is 21c, so we create a new BuilderInstruction21c
// with the opcode CONST_STRING and the string "Hello, ReVanced! Editing bytecode."
// This instruction will store the constant string reference in the register v1
// For that a reference to the string is needed. It can be created by creating a ImmutableStringReference
val stringInstruction1 = BuilderInstruction21c(
Opcode.CONST_STRING,
1,
ImmutableStringReference("Hello, ReVanced! Editing bytecode.")
)

// Replace the instruction at index startIndex with a new instruction
// We make sure to use this method to handle references to it via labels in any case
// If we are sure that the instruction is not referenced by any label, we can use the index operator overload
// of the instruction list:
// implementation.instructions[startIndex] = instruction
implementation.replaceInstruction(startIndex, stringInstruction1)

// Now lets print our string twice!

// Create the necessary instructions (we could also clone the existing ones)
val stringInstruction2 = BuilderInstruction21c(
Opcode.CONST_STRING,
1,
ImmutableStringReference("Hello, ReVanced! Adding bytecode.")
)
val invokeInstruction = BuilderInstruction35c(
Opcode.INVOKE_VIRTUAL,
2, 0, 1, 0, 0, 0,
ImmutableMethodReference(
"Ljava.io.PrintStream;",
"println",
setOf("Ljava/lang/String;"),
"V"
)
)

// Insert our instructions after the second instruction by our pattern.
implementation.addInstruction(startIndex + 1, stringInstruction2)
implementation.addInstruction(startIndex + 3, invokeInstruction)

// 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()
}
}
)

// Apply all patches loaded in the patcher
val patchResult = patcher.applyPatches()
// You can check if an error occurred
for ((patchName, result) in patchResult) {
if (result.isFailure) {
throw Exception("Patch $patchName failed", result.exceptionOrNull()!!)
}
}

patcher.save()
}

@Test
fun `test patcher with no changes`() {
Patcher(
File(PatcherTest::class.java.getResource("/test1.apk")!!.toURI()),
File("/no-changes-test"),
testSignatures
).save()
// FIXME(Sculas): There seems to be a 1-byte difference, not sure what it is.
// assertEquals(available, out.size())
}
}
121 changes: 0 additions & 121 deletions src/test/kotlin/patcher/PatcherTest.kt

This file was deleted.

0 comments on commit 544bcf7

Please sign in to comment.