diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 788ba42..8e5d259 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -30,6 +30,8 @@ jobs:
- name: Make gradlew executable
run: chmod +x gradlew
- name: Build with Gradle
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build
- name: Setup semantic-release
run: npm install -g semantic-release @semantic-release/git @semantic-release/changelog gradle-semantic-release-plugin -D
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..73ab2c8
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index f295c2d..192e4c7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,5 +1,5 @@
plugins {
- kotlin("jvm") version "1.6.10"
+ kotlin("jvm") version "1.6.20"
java
`maven-publish`
}
@@ -8,23 +8,35 @@ group = "app.revanced"
repositories {
mavenCentral()
+ maven {
+ url = uri("https://maven.pkg.github.com/revanced/multidexlib2")
+ credentials {
+ // DO NOT set these variables in the project's gradle.properties.
+ // Instead, you should set them in:
+ // Windows: %homepath%\.gradle\gradle.properties
+ // Linux: ~/.gradle/gradle.properties
+ username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") // DO NOT CHANGE!
+ password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") // DO NOT CHANGE!
+ }
+ }
}
dependencies {
implementation(kotlin("stdlib"))
- implementation("org.ow2.asm:asm:9.2")
- implementation("org.ow2.asm:asm-util:9.2")
- implementation("org.ow2.asm:asm-tree:9.2")
- implementation("org.ow2.asm:asm-commons:9.2")
- implementation("io.github.microutils:kotlin-logging:2.1.21")
- testImplementation("ch.qos.logback:logback-classic:1.2.11") // use your own logger!
+
+ api("org.apktool:apktool-lib:2.6.1")
+ api("app.revanced:multidexlib2:2.5.2.r2")
+ api("org.smali:smali:2.5.2")
+
testImplementation(kotlin("test"))
}
-tasks.test {
- useJUnitPlatform()
- testLogging {
- events("PASSED", "SKIPPED", "FAILED")
+tasks {
+ test {
+ useJUnitPlatform()
+ testLogging {
+ events("PASSED", "SKIPPED", "FAILED")
+ }
}
}
@@ -33,15 +45,21 @@ java {
withJavadocJar()
}
+val isGitHubCI = System.getenv("GITHUB_ACTOR") != null
+
publishing {
repositories {
- maven {
- name = "GitHubPackages"
- url = uri("https://maven.pkg.github.com/ReVancedTeam/revanced-patcher")
- credentials {
- username = System.getenv("GITHUB_ACTOR")
- password = System.getenv("GITHUB_TOKEN")
+ if (isGitHubCI) {
+ maven {
+ name = "GitHubPackages"
+ url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
+ credentials {
+ username = System.getenv("GITHUB_ACTOR")
+ password = System.getenv("GITHUB_TOKEN")
+ }
}
+ } else {
+ mavenLocal()
}
}
publications {
@@ -49,4 +67,4 @@ publishing {
from(components["java"])
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/Patcher.kt b/src/main/kotlin/app/revanced/patcher/Patcher.kt
index 618dbdd..d984438 100644
--- a/src/main/kotlin/app/revanced/patcher/Patcher.kt
+++ b/src/main/kotlin/app/revanced/patcher/Patcher.kt
@@ -1,70 +1,209 @@
package app.revanced.patcher
-import app.revanced.patcher.cache.Cache
-import app.revanced.patcher.patch.Patch
-import app.revanced.patcher.resolver.MethodResolver
-import app.revanced.patcher.signature.Signature
-import app.revanced.patcher.util.Io
-import org.objectweb.asm.tree.ClassNode
-import java.io.IOException
-import java.io.InputStream
-import java.io.OutputStream
+import app.revanced.patcher.data.PatcherData
+import app.revanced.patcher.data.base.Data
+import app.revanced.patcher.data.implementation.findIndexed
+import app.revanced.patcher.patch.base.Patch
+import app.revanced.patcher.patch.implementation.BytecodePatch
+import app.revanced.patcher.patch.implementation.ResourcePatch
+import app.revanced.patcher.patch.implementation.metadata.PatchMetadata
+import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
+import app.revanced.patcher.signature.MethodSignature
+import app.revanced.patcher.signature.resolver.SignatureResolver
+import app.revanced.patcher.util.ListBackedSet
+import brut.androlib.Androlib
+import brut.androlib.meta.UsesFramework
+import brut.directory.ExtFile
+import lanchon.multidexlib2.BasicDexFileNamer
+import lanchon.multidexlib2.DexIO
+import lanchon.multidexlib2.MultiDexIO
+import org.jf.dexlib2.Opcodes
+import org.jf.dexlib2.iface.ClassDef
+import org.jf.dexlib2.iface.DexFile
+import org.jf.dexlib2.writer.io.MemoryDataStore
+import java.io.File
+
+val NAMER = BasicDexFileNamer()
/**
- * The Patcher class.
- * ***It is of utmost importance that the input and output streams are NEVER closed.***
- *
- * @param input the input stream to read from, must be a JAR
- * @param output the output stream to write to
- * @param signatures the signatures
- * @sample app.revanced.patcher.PatcherTest
- * @throws IOException if one of the streams are closed
+ * The ReVanced Patcher.
+ * @param inputFile The input file (usually an apk file).
+ * @param resourceCacheDirectory Directory to cache resources.
+ * @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
*/
class Patcher(
- private val input: InputStream,
- private val output: OutputStream,
- signatures: Array,
+ inputFile: File,
+ // TODO: maybe a file system in memory is better. Could cause high memory usage.
+ private val resourceCacheDirectory: String,
+ private val patchResources: Boolean = false
) {
- var cache: Cache
+ val packageVersion: String
+ val packageName: String
+
+ private val usesFramework: UsesFramework
+ private val patcherData: PatcherData
+ private val opcodes: Opcodes
+ private var signaturesResolved = false
+ private val androlib = Androlib()
- private var io: Io
- private val patches = mutableListOf()
init {
- val classes = mutableListOf()
- io = Io(input, output, classes)
- io.readFromJar()
- cache = Cache(classes, MethodResolver(classes, signatures).resolve())
+ val extFileInput = ExtFile(inputFile)
+ val resourceTable = androlib.getResTable(extFileInput, true)
+ val outDir = File(resourceCacheDirectory)
+
+ if (outDir.exists()) outDir.deleteRecursively()
+ outDir.mkdir()
+
+ // 1. decode resources to cache directory
+ androlib.decodeManifestWithResources(extFileInput, outDir, resourceTable)
+ androlib.decodeResourcesFull(extFileInput, outDir, resourceTable)
+
+ // 2. read framework ids from the resource table
+ usesFramework = UsesFramework()
+ usesFramework.ids = resourceTable.listFramePackages().map { it.id }.sorted()
+
+ // 3. read package info
+ packageName = resourceTable.packageOriginal
+ packageVersion = resourceTable.versionInfo.versionName
+
+ // read dex files
+ val dexFile = MultiDexIO.readDexFile(true, inputFile, NAMER, null, null)
+ opcodes = dexFile.opcodes
+
+ // save to patcher data
+ patcherData = PatcherData(dexFile.classes.toMutableList(), resourceCacheDirectory)
}
/**
- * Saves the output to the output stream.
- * Calling this method will close the input and output streams,
- * meaning this method should NEVER be called after.
- *
- * @throws IOException if one of the streams are closed
+ * Add additional dex file container to the patcher.
+ * @param files The dex file containers to add to the patcher.
+ * @param allowedOverwrites A list of class types that are allowed to be overwritten.
+ * @param throwOnDuplicates If this is set to true, the patcher will throw an exception if a duplicate class has been found.
*/
- fun save() {
- io.saveAsJar()
- }
-
- fun addPatches(vararg patches: Patch) {
- this.patches.addAll(patches)
- }
-
- fun applyPatches(stopOnError: Boolean = false): Map> {
- return buildMap {
- for (patch in patches) {
- val result: Result = try {
- val pr = patch.execute(cache)
- if (pr.isSuccess()) continue
- Result.failure(Exception(pr.error()?.errorMessage() ?: "Unknown error"))
- } catch (e: Exception) {
- Result.failure(e)
+ fun addFiles(
+ files: Iterable,
+ allowedOverwrites: Iterable = emptyList(),
+ throwOnDuplicates: Boolean = false
+ ) {
+ for (file in files) {
+ val dexFile = MultiDexIO.readDexFile(true, file, NAMER, null, null)
+ for (classDef in dexFile.classes) {
+ val e = patcherData.bytecodeData.classes.internalClasses.findIndexed { it.type == classDef.type }
+ if (e != null) {
+ if (throwOnDuplicates) {
+ throw Exception("Class ${classDef.type} has already been added to the patcher.")
+ }
+ val (_, idx) = e
+ if (allowedOverwrites.contains(classDef.type)) {
+ patcherData.bytecodeData.classes.internalClasses[idx] = classDef
+ }
+ continue
}
- this[patch.patchName] = result
- if (stopOnError && result.isFailure) break
+ patcherData.bytecodeData.classes.internalClasses.add(classDef)
}
}
}
-}
\ No newline at end of file
+
+ /**
+ * Save the patched dex file.
+ */
+ fun save(): Map {
+ val newDexFile = object : DexFile {
+ override fun getClasses(): Set {
+ patcherData.bytecodeData.classes.applyProxies()
+ return ListBackedSet(patcherData.bytecodeData.classes.internalClasses)
+ }
+
+ override fun getOpcodes(): Opcodes {
+ return this@Patcher.opcodes
+ }
+ }
+
+ // build modified resources
+ if (patchResources) {
+ val extDir = ExtFile(resourceCacheDirectory)
+ androlib.buildResources(extDir, usesFramework)
+ }
+
+ // write dex modified files
+ val output = mutableMapOf()
+ MultiDexIO.writeDexFile(
+ true, -1, // core count
+ output, NAMER, newDexFile,
+ DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
+ null
+ )
+ return output
+ }
+
+ /**
+ * Add a patch to the patcher.
+ * @param patches The patches to add.
+ */
+ fun addPatches(patches: Iterable>) {
+ patcherData.patches.addAll(patches)
+ }
+
+ /**
+ * Resolves all signatures.
+ */
+ fun resolveSignatures(): List {
+ val signatures = buildList {
+ for (patch in patcherData.patches) {
+ if (patch !is BytecodePatch) continue
+ this.addAll(patch.signatures)
+ }
+ }
+ if (signatures.isEmpty()) {
+ return emptyList()
+ }
+
+ SignatureResolver(patcherData.bytecodeData.classes.internalClasses, signatures).resolve(patcherData)
+ signaturesResolved = true
+ return signatures
+ }
+
+ /**
+ * Apply patches loaded into the patcher.
+ * @param stopOnError If true, the patches will stop on the first error.
+ * @return A map of [PatchResultSuccess]. If the [Patch] was successfully applied,
+ * [PatchResultSuccess] will always be returned to the wrapping Result object.
+ * If the [Patch] failed to apply, an Exception will always be returned to the wrapping Result object.
+ */
+ fun applyPatches(
+ stopOnError: Boolean = false,
+ callback: (String) -> Unit = {}
+ ): Map> {
+ if (!signaturesResolved) {
+ resolveSignatures()
+ }
+ return buildMap {
+ for (patch in patcherData.patches) {
+ val resourcePatch = patch is ResourcePatch
+ if (!patchResources && resourcePatch) continue
+
+ callback(patch.metadata.shortName)
+ val result: Result = try {
+ val data = if (resourcePatch) {
+ patcherData.resourceData
+ } else {
+ patcherData.bytecodeData
+ }
+
+ val pr = patch.execute(data)
+
+ if (pr.isSuccess()) {
+ Result.success(pr.success()!!)
+ } else {
+ Result.failure(Exception(pr.error()?.errorMessage() ?: "Unknown error"))
+ }
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+ this[patch.metadata] = result
+ if (result.isFailure && stopOnError) break
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/cache/Cache.kt b/src/main/kotlin/app/revanced/patcher/cache/Cache.kt
deleted file mode 100644
index 3a1d1a9..0000000
--- a/src/main/kotlin/app/revanced/patcher/cache/Cache.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package app.revanced.patcher.cache
-
-import org.objectweb.asm.tree.ClassNode
-
-class Cache(
- val classes: List,
- val methods: MethodMap
-)
-
-class MethodMap : LinkedHashMap() {
- override fun get(key: String): PatchData {
- return super.get(key) ?: throw MethodNotFoundException("Method $key was not found in the method cache")
- }
-}
-
-class MethodNotFoundException(s: String) : Exception(s)
diff --git a/src/main/kotlin/app/revanced/patcher/cache/PatchData.kt b/src/main/kotlin/app/revanced/patcher/cache/PatchData.kt
deleted file mode 100644
index 033e497..0000000
--- a/src/main/kotlin/app/revanced/patcher/cache/PatchData.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package app.revanced.patcher.cache
-
-import app.revanced.patcher.resolver.MethodResolver
-import app.revanced.patcher.signature.Signature
-import org.objectweb.asm.tree.ClassNode
-import org.objectweb.asm.tree.MethodNode
-
-data class PatchData(
- val declaringClass: ClassNode,
- val method: MethodNode,
- val scanData: PatternScanData
-) {
- @Suppress("Unused") // TODO(Sculas): remove this when we have coverage for this method.
- fun findParentMethod(signature: Signature): PatchData? {
- return MethodResolver.resolveMethod(declaringClass, signature)
- }
-}
-
-data class PatternScanData(
- val startIndex: Int,
- val endIndex: Int
-)
diff --git a/src/main/kotlin/app/revanced/patcher/data/PatcherData.kt b/src/main/kotlin/app/revanced/patcher/data/PatcherData.kt
new file mode 100644
index 0000000..f225d8f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/data/PatcherData.kt
@@ -0,0 +1,18 @@
+package app.revanced.patcher.data
+
+import app.revanced.patcher.data.base.Data
+import app.revanced.patcher.data.implementation.BytecodeData
+import app.revanced.patcher.data.implementation.ResourceData
+import app.revanced.patcher.patch.base.Patch
+import org.jf.dexlib2.iface.ClassDef
+import java.io.File
+
+internal data class PatcherData(
+ val internalClasses: MutableList,
+ val resourceCacheDirectory: String
+) {
+ internal val patches = mutableListOf>()
+
+ internal val bytecodeData = BytecodeData(patches, internalClasses)
+ internal val resourceData = ResourceData(File(resourceCacheDirectory))
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/data/base/Data.kt b/src/main/kotlin/app/revanced/patcher/data/base/Data.kt
new file mode 100644
index 0000000..95351bb
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/data/base/Data.kt
@@ -0,0 +1,9 @@
+package app.revanced.patcher.data.base
+
+import app.revanced.patcher.data.implementation.BytecodeData
+import app.revanced.patcher.data.implementation.ResourceData
+
+/**
+ * Constraint interface for [BytecodeData] and [ResourceData]
+ */
+interface Data
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/data/implementation/BytecodeData.kt b/src/main/kotlin/app/revanced/patcher/data/implementation/BytecodeData.kt
new file mode 100644
index 0000000..2c3e09a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/data/implementation/BytecodeData.kt
@@ -0,0 +1,87 @@
+package app.revanced.patcher.data.implementation
+
+import app.revanced.patcher.data.base.Data
+import app.revanced.patcher.methodWalker.MethodWalker
+import app.revanced.patcher.patch.base.Patch
+import app.revanced.patcher.patch.implementation.BytecodePatch
+import app.revanced.patcher.proxy.ClassProxy
+import app.revanced.patcher.signature.SignatureResolverResult
+import app.revanced.patcher.util.ProxyBackedClassList
+import org.jf.dexlib2.iface.ClassDef
+import org.jf.dexlib2.iface.Method
+
+class BytecodeData(
+ // FIXME: ugly solution due to design.
+ // It does not make sense for a BytecodeData instance to have access to the patches
+ private val patches: List>,
+ internalClasses: MutableList
+) : Data {
+ val classes = ProxyBackedClassList(internalClasses)
+
+ /**
+ * Find a class by a given class name
+ * @return A proxy for the first class that matches the class name
+ */
+ fun findClass(className: String) = findClass { it.type.contains(className) }
+
+ /**
+ * Find a class by a given predicate
+ * @return A proxy for the first class that matches the predicate
+ */
+ fun findClass(predicate: (ClassDef) -> Boolean): ClassProxy? {
+ // if we already proxied the class matching the predicate...
+ for (patch in patches) {
+ if (patch !is BytecodePatch) continue
+ for (signature in patch.signatures) {
+ val result = signature.result
+ result ?: continue
+
+ if (predicate(result.definingClassProxy.immutableClass)) return result.definingClassProxy // ...then return that proxy
+ }
+ }
+ // else resolve the class to a proxy and return it, if the predicate is matching a class
+ return classes.find(predicate)?.let {
+ proxy(it)
+ }
+ }
+}
+
+
+class MethodMap : LinkedHashMap() {
+ override fun get(key: String): SignatureResolverResult {
+ return super.get(key) ?: throw MethodNotFoundException("Method $key was not found in the method cache")
+ }
+}
+
+internal class MethodNotFoundException(s: String) : Exception(s)
+
+internal inline fun Iterable.find(predicate: (T) -> Boolean): T? {
+ for (element in this) {
+ if (predicate(element)) {
+ return element
+ }
+ }
+ return null
+}
+
+fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker {
+ return MethodWalker(this, startMethod)
+}
+
+internal inline fun Iterable.findIndexed(predicate: (T) -> Boolean): Pair? {
+ for ((index, element) in this.withIndex()) {
+ if (predicate(element)) {
+ return element to index
+ }
+ }
+ return null
+}
+
+fun BytecodeData.proxy(classDef: ClassDef): ClassProxy {
+ var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
+ if (proxy == null) {
+ proxy = ClassProxy(classDef)
+ this.classes.proxies.add(proxy)
+ }
+ return proxy
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/data/implementation/ResourceData.kt b/src/main/kotlin/app/revanced/patcher/data/implementation/ResourceData.kt
new file mode 100644
index 0000000..095692f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/data/implementation/ResourceData.kt
@@ -0,0 +1,49 @@
+package app.revanced.patcher.data.implementation
+
+import app.revanced.patcher.data.base.Data
+import org.w3c.dom.Document
+import java.io.Closeable
+import java.io.File
+import javax.xml.XMLConstants
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.transform.TransformerFactory
+import javax.xml.transform.dom.DOMSource
+import javax.xml.transform.stream.StreamResult
+
+class ResourceData(private val resourceCacheDirectory: File) : Data {
+ private fun resolve(path: String) = resourceCacheDirectory.resolve(path)
+
+ fun forEach(action: (File) -> Unit) = resourceCacheDirectory.walkTopDown().forEach(action)
+ fun reader(path: String) = resolve(path).reader()
+ fun writer(path: String) = resolve(path).writer()
+
+ fun replace(path: String, oldValue: String, newValue: String, oldValueIsRegex: Boolean = false) {
+ // TODO: buffer this somehow
+ val content = resolve(path).readText()
+
+ if (oldValueIsRegex) {
+ content.replace(Regex(oldValue), newValue)
+ return
+ }
+ }
+
+ fun getXmlEditor(path: String) = DomFileEditor(resolve(path))
+}
+
+class DomFileEditor internal constructor(private val domFile: File) : Closeable {
+ val file: Document
+
+ init {
+ val factory = DocumentBuilderFactory.newInstance()
+ factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
+
+ val builder = factory.newDocumentBuilder()
+
+ // this will expectedly throw
+ file = builder.parse(domFile)
+ file.normalize()
+ }
+
+ override fun close() = TransformerFactory.newInstance().newTransformer()
+ .transform(DOMSource(file), StreamResult(domFile.outputStream()))
+}
diff --git a/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt
new file mode 100644
index 0000000..5e64bdd
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/extensions/Extensions.kt
@@ -0,0 +1,81 @@
+package app.revanced.patcher.extensions
+
+import app.revanced.patcher.proxy.mutableTypes.MutableMethod.Companion.toMutable
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.builder.BuilderInstruction
+import org.jf.dexlib2.builder.MutableMethodImplementation
+import org.jf.dexlib2.iface.Method
+import org.jf.dexlib2.iface.reference.MethodReference
+import org.jf.dexlib2.immutable.ImmutableMethod
+import org.jf.dexlib2.immutable.ImmutableMethodImplementation
+import org.jf.dexlib2.util.MethodUtil
+
+infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value
+infix fun Int.or(other: AccessFlags) = this or other.value
+
+fun MutableMethodImplementation.addInstructions(index: Int, instructions: List) {
+ for (i in instructions.lastIndex downTo 0) {
+ this.addInstruction(index, instructions[i])
+ }
+}
+
+/**
+ * Clones the method.
+ * @param registerCount This parameter allows you to change the register count of the method.
+ * This may be a positive or negative number.
+ * @return The **immutable** cloned method. Call [toMutable] or [cloneMutable] to get a **mutable** copy.
+ */
+internal fun Method.clone(
+ registerCount: Int = 0,
+): ImmutableMethod {
+ val clonedImplementation = implementation?.let {
+ ImmutableMethodImplementation(
+ it.registerCount + registerCount,
+ it.instructions,
+ it.tryBlocks,
+ it.debugItems,
+ )
+ }
+ return ImmutableMethod(
+ returnType,
+ name,
+ parameters,
+ returnType,
+ accessFlags,
+ annotations,
+ hiddenApiRestrictions,
+ clonedImplementation
+ )
+}
+
+/**
+ * Clones the method.
+ * @param registerCount This parameter allows you to change the register count of the method.
+ * This may be a positive or negative number.
+ * @return The **mutable** cloned method. Call [clone] to get an **immutable** copy.
+ */
+internal fun Method.cloneMutable(
+ registerCount: Int = 0,
+) = clone(registerCount).toMutable()
+
+internal fun Method.softCompareTo(
+ otherMethod: MethodReference
+): Boolean {
+ if (MethodUtil.isConstructor(this) && !parametersEqual(this.parameterTypes, otherMethod.parameterTypes))
+ return false
+ return this.name == otherMethod.name
+}
+
+// FIXME: also check the order of parameters as different order equals different method overload
+internal fun parametersEqual(
+ parameters1: Iterable,
+ parameters2: Iterable
+): Boolean {
+ return parameters1.count() == parameters2.count() && parameters1.all { parameter ->
+ parameters2.any {
+ it.startsWith(
+ parameter
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/methodWalker/MethodWalker.kt b/src/main/kotlin/app/revanced/patcher/methodWalker/MethodWalker.kt
new file mode 100644
index 0000000..04ed0e7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/methodWalker/MethodWalker.kt
@@ -0,0 +1,55 @@
+package app.revanced.patcher.methodWalker
+
+import app.revanced.patcher.data.implementation.BytecodeData
+import app.revanced.patcher.data.implementation.MethodNotFoundException
+import app.revanced.patcher.extensions.softCompareTo
+import app.revanced.patcher.proxy.mutableTypes.MutableMethod
+import org.jf.dexlib2.Format
+import org.jf.dexlib2.iface.Method
+import org.jf.dexlib2.iface.instruction.formats.Instruction35c
+import org.jf.dexlib2.iface.reference.MethodReference
+import org.jf.dexlib2.util.Preconditions
+
+/**
+ * Find a method from another method via instruction offsets.
+ * @param bytecodeData The bytecodeData to use when resolving the next method reference.
+ * @param currentMethod The method to start from.
+ */
+class MethodWalker internal constructor(
+ private val bytecodeData: BytecodeData,
+ private var currentMethod: Method
+) {
+ /**
+ * Get the method which was walked last.
+ * It is possible to cast this method to a [MutableMethod], if the method has been walked mutably.
+ */
+ fun getMethod(): Method {
+ return currentMethod
+ }
+
+ /**
+ * Walk to a method defined at the offset in the instruction list of the current method.
+ * @param offset The offset of the instruction. This instruction must be of format 35c.
+ * @param walkMutable If this is true, the class of the method will be resolved mutably.
+ * The current method will be mutable.
+ */
+ fun walk(offset: Int, walkMutable: Boolean = false): MethodWalker {
+ currentMethod.implementation?.instructions?.let { instructions ->
+ val instruction = instructions.elementAt(offset)
+
+ Preconditions.checkFormat(instruction.opcode, Format.Format35c)
+
+ val newMethod = (instruction as Instruction35c).reference as MethodReference
+ val proxy = bytecodeData.findClass(newMethod.definingClass)!!
+
+ val methods = if (walkMutable) proxy.resolve().methods else proxy.immutableClass.methods
+ currentMethod = methods.first { it ->
+ return@first it.softCompareTo(newMethod)
+ }
+ return this
+ }
+ throw MethodNotFoundException("This method can not be walked at offset $offset inside the method ${currentMethod.name}")
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/patch/Patch.kt b/src/main/kotlin/app/revanced/patcher/patch/Patch.kt
deleted file mode 100644
index e2e14ac..0000000
--- a/src/main/kotlin/app/revanced/patcher/patch/Patch.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package app.revanced.patcher.patch
-
-import app.revanced.patcher.cache.Cache
-
-abstract class Patch(val patchName: String) {
- abstract fun execute(cache: Cache): PatchResult
-}
diff --git a/src/main/kotlin/app/revanced/patcher/patch/base/Patch.kt b/src/main/kotlin/app/revanced/patcher/patch/base/Patch.kt
new file mode 100644
index 0000000..f7f99b2
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/patch/base/Patch.kt
@@ -0,0 +1,22 @@
+package app.revanced.patcher.patch.base
+
+import app.revanced.patcher.data.base.Data
+import app.revanced.patcher.patch.implementation.BytecodePatch
+import app.revanced.patcher.patch.implementation.ResourcePatch
+import app.revanced.patcher.patch.implementation.metadata.PatchMetadata
+import app.revanced.patcher.patch.implementation.misc.PatchResult
+
+
+/**
+ * A ReVanced patch.
+ * Can either be a [ResourcePatch] or a [BytecodePatch]
+ */
+abstract class Patch(
+ open val metadata: PatchMetadata
+) {
+ /**
+ * The main function of the [Patch] which the patcher will call.
+ */
+ abstract fun execute(data: @UnsafeVariance T): PatchResult // FIXME: remove the UnsafeVariance annotation
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/patch/implementation/BytecodePatch.kt b/src/main/kotlin/app/revanced/patcher/patch/implementation/BytecodePatch.kt
new file mode 100644
index 0000000..facbed1
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/patch/implementation/BytecodePatch.kt
@@ -0,0 +1,16 @@
+package app.revanced.patcher.patch.implementation
+
+import app.revanced.patcher.data.implementation.BytecodeData
+import app.revanced.patcher.patch.base.Patch
+import app.revanced.patcher.patch.implementation.metadata.PatchMetadata
+import app.revanced.patcher.signature.MethodSignature
+
+/**
+ * Bytecode patch for the Patcher.
+ * @param metadata [PatchMetadata] for the patch.
+ * @param signatures A list of [MethodSignature] this patch relies on.
+ */
+abstract class BytecodePatch(
+ override val metadata: PatchMetadata,
+ val signatures: Iterable
+) : Patch(metadata)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/patch/implementation/ResourcePatch.kt b/src/main/kotlin/app/revanced/patcher/patch/implementation/ResourcePatch.kt
new file mode 100644
index 0000000..bac5820
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/patch/implementation/ResourcePatch.kt
@@ -0,0 +1,13 @@
+package app.revanced.patcher.patch.implementation
+
+import app.revanced.patcher.data.implementation.ResourceData
+import app.revanced.patcher.patch.base.Patch
+import app.revanced.patcher.patch.implementation.metadata.PatchMetadata
+
+/**
+ * Resource patch for the Patcher.
+ * @param metadata [PatchMetadata] for the patch.
+ */
+abstract class ResourcePatch(
+ override val metadata: PatchMetadata
+) : Patch(metadata)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/patch/implementation/metadata/PatchMetadata.kt b/src/main/kotlin/app/revanced/patcher/patch/implementation/metadata/PatchMetadata.kt
new file mode 100644
index 0000000..cfa8f83
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/patch/implementation/metadata/PatchMetadata.kt
@@ -0,0 +1,29 @@
+package app.revanced.patcher.patch.implementation.metadata
+
+import app.revanced.patcher.patch.base.Patch
+
+/**
+ * Metadata about a [Patch].
+ * @param shortName A suggestive short name for the [Patch].
+ * @param name A suggestive name for the [Patch].
+ * @param description A description for the [Patch].
+ * @param compatiblePackages A list of packages this [Patch] is compatible with.
+ * @param version The version of the [Patch].
+ */
+data class PatchMetadata(
+ val shortName: String,
+ val name: String,
+ val description: String,
+ val compatiblePackages: Iterable,
+ val version: String,
+)
+
+/**
+ * Metadata about a package.
+ * @param name The package name.
+ * @param versions Compatible versions of the package.
+ */
+data class PackageMetadata(
+ val name: String,
+ val versions: Iterable
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/patch/PatchResult.kt b/src/main/kotlin/app/revanced/patcher/patch/implementation/misc/PatchResult.kt
similarity index 92%
rename from src/main/kotlin/app/revanced/patcher/patch/PatchResult.kt
rename to src/main/kotlin/app/revanced/patcher/patch/implementation/misc/PatchResult.kt
index 02e1ac6..7c0b068 100644
--- a/src/main/kotlin/app/revanced/patcher/patch/PatchResult.kt
+++ b/src/main/kotlin/app/revanced/patcher/patch/implementation/misc/PatchResult.kt
@@ -1,4 +1,4 @@
-package app.revanced.patcher.patch
+package app.revanced.patcher.patch.implementation.misc
interface PatchResult {
fun error(): PatchResultError? {
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/ClassProxy.kt b/src/main/kotlin/app/revanced/patcher/proxy/ClassProxy.kt
new file mode 100644
index 0000000..0aedeef
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/ClassProxy.kt
@@ -0,0 +1,41 @@
+package app.revanced.patcher.proxy
+
+import app.revanced.patcher.proxy.mutableTypes.MutableClass
+import org.jf.dexlib2.iface.ClassDef
+
+/**
+ * A proxy class for a [ClassDef].
+ *
+ * A class proxy simply holds a reference to the original class
+ * and allocates a mutable clone for the original class if needed.
+ * @param immutableClass The class to proxy
+ */
+class ClassProxy(
+ val immutableClass: ClassDef,
+) {
+ internal var proxyUsed = false
+ internal lateinit var mutatedClass: MutableClass
+
+ init {
+ // in the instance, that a [MutableClass] is being proxied,
+ // do not create an additional clone and reuse the [MutableClass] instance
+ if (immutableClass is MutableClass) {
+ mutatedClass = immutableClass
+ proxyUsed = true
+ }
+ }
+
+ /**
+ * Allocates and returns a mutable clone of the original class.
+ * A patch should always use the original immutable class reference
+ * to avoid unnecessary allocations for the mutable class.
+ * @return A mutable clone of the original class.
+ */
+ fun resolve(): MutableClass {
+ if (!proxyUsed) {
+ proxyUsed = true
+ mutatedClass = MutableClass(immutableClass)
+ }
+ return mutatedClass
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableAnnotation.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableAnnotation.kt
new file mode 100644
index 0000000..927f359
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableAnnotation.kt
@@ -0,0 +1,29 @@
+package app.revanced.patcher.proxy.mutableTypes
+
+import app.revanced.patcher.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
+import org.jf.dexlib2.base.BaseAnnotation
+import org.jf.dexlib2.iface.Annotation
+
+class MutableAnnotation(annotation: Annotation) : BaseAnnotation() {
+ private val visibility = annotation.visibility
+ private val type = annotation.type
+ private val _elements by lazy { annotation.elements.map { element -> element.toMutable() }.toMutableSet() }
+
+ override fun getType(): String {
+ return type
+ }
+
+ override fun getElements(): MutableSet {
+ return _elements
+ }
+
+ override fun getVisibility(): Int {
+ return visibility
+ }
+
+ companion object {
+ fun Annotation.toMutable(): MutableAnnotation {
+ return MutableAnnotation(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableAnnotationElement.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableAnnotationElement.kt
new file mode 100644
index 0000000..85354fe
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableAnnotationElement.kt
@@ -0,0 +1,34 @@
+package app.revanced.patcher.proxy.mutableTypes
+
+import app.revanced.patcher.proxy.mutableTypes.encodedValue.MutableEncodedValue
+import app.revanced.patcher.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
+import org.jf.dexlib2.base.BaseAnnotationElement
+import org.jf.dexlib2.iface.AnnotationElement
+import org.jf.dexlib2.iface.value.EncodedValue
+
+class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnotationElement() {
+ private var name = annotationElement.name
+ private var value = annotationElement.value.toMutable()
+
+ fun setName(name: String) {
+ this.name = name
+ }
+
+ fun setValue(value: MutableEncodedValue) {
+ this.value = value
+ }
+
+ override fun getName(): String {
+ return name
+ }
+
+ override fun getValue(): EncodedValue {
+ return value
+ }
+
+ companion object {
+ fun AnnotationElement.toMutable(): MutableAnnotationElement {
+ return MutableAnnotationElement(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableClass.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableClass.kt
new file mode 100644
index 0000000..eadab5f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableClass.kt
@@ -0,0 +1,103 @@
+package app.revanced.patcher.proxy.mutableTypes
+
+import app.revanced.patcher.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
+import app.revanced.patcher.proxy.mutableTypes.MutableField.Companion.toMutable
+import app.revanced.patcher.proxy.mutableTypes.MutableMethod.Companion.toMutable
+import com.google.common.collect.Iterables
+import org.jf.dexlib2.base.reference.BaseTypeReference
+import org.jf.dexlib2.iface.ClassDef
+import org.jf.dexlib2.util.FieldUtil
+import org.jf.dexlib2.util.MethodUtil
+
+class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
+ // Class
+ private var type = classDef.type
+ private var sourceFile = classDef.sourceFile
+ private var accessFlags = classDef.accessFlags
+ private var superclass = classDef.superclass
+
+ private val _interfaces by lazy { classDef.interfaces.toMutableList() }
+ private val _annotations by lazy {
+ classDef.annotations.map { annotation -> annotation.toMutable() }.toMutableSet()
+ }
+
+ // Methods
+ private val _methods by lazy { classDef.methods.map { method -> method.toMutable() }.toMutableSet() }
+ private val _directMethods by lazy { Iterables.filter(_methods, MethodUtil.METHOD_IS_DIRECT).toMutableSet() }
+ private val _virtualMethods by lazy { Iterables.filter(_methods, MethodUtil.METHOD_IS_VIRTUAL).toMutableSet() }
+
+ // 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() }
+
+ fun setType(type: String) {
+ this.type = type
+ }
+
+ fun setSourceFile(sourceFile: String?) {
+ this.sourceFile = sourceFile
+ }
+
+ fun setAccessFlags(accessFlags: Int) {
+ this.accessFlags = accessFlags
+ }
+
+ fun setSuperClass(superclass: String?) {
+ this.superclass = superclass
+ }
+
+ override fun getType(): String {
+ return type
+ }
+
+ override fun getAccessFlags(): Int {
+ return accessFlags
+ }
+
+ override fun getSourceFile(): String? {
+ return sourceFile
+ }
+
+ override fun getSuperclass(): String? {
+ return superclass
+ }
+
+ override fun getInterfaces(): MutableList {
+ return _interfaces
+ }
+
+ override fun getAnnotations(): MutableSet {
+ return _annotations
+ }
+
+ override fun getStaticFields(): MutableSet {
+ return _staticFields
+ }
+
+ override fun getInstanceFields(): MutableSet {
+ return _instanceFields
+ }
+
+ override fun getFields(): MutableSet {
+ return _fields
+ }
+
+ override fun getDirectMethods(): MutableSet {
+ return _directMethods
+ }
+
+ override fun getVirtualMethods(): MutableSet {
+ return _virtualMethods
+ }
+
+ override fun getMethods(): MutableSet {
+ return _methods
+ }
+
+ companion object {
+ fun ClassDef.toMutable(): MutableClass {
+ return MutableClass(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableField.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableField.kt
new file mode 100644
index 0000000..34c445a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableField.kt
@@ -0,0 +1,73 @@
+package app.revanced.patcher.proxy.mutableTypes
+
+import app.revanced.patcher.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
+import app.revanced.patcher.proxy.mutableTypes.encodedValue.MutableEncodedValue
+import app.revanced.patcher.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
+import org.jf.dexlib2.HiddenApiRestriction
+import org.jf.dexlib2.base.reference.BaseFieldReference
+import org.jf.dexlib2.iface.Field
+
+class MutableField(field: Field) : Field, BaseFieldReference() {
+ private var definingClass = field.definingClass
+ private var name = field.name
+ private var type = field.type
+ private var accessFlags = field.accessFlags
+
+ private var initialValue = field.initialValue?.toMutable()
+ private val _annotations by lazy { field.annotations.map { annotation -> annotation.toMutable() }.toMutableSet() }
+ private val _hiddenApiRestrictions by lazy { field.hiddenApiRestrictions }
+
+ fun setDefiningClass(definingClass: String) {
+ this.definingClass = definingClass
+ }
+
+ fun setName(name: String) {
+ this.name = name
+ }
+
+ fun setType(type: String) {
+ this.type = type
+ }
+
+ fun setAccessFlags(accessFlags: Int) {
+ this.accessFlags = accessFlags
+ }
+
+ fun setInitialValue(initialValue: MutableEncodedValue?) {
+ this.initialValue = initialValue
+ }
+
+ override fun getDefiningClass(): String {
+ return this.definingClass
+ }
+
+ override fun getName(): String {
+ return this.name
+ }
+
+ override fun getType(): String {
+ return this.type
+ }
+
+ override fun getAnnotations(): MutableSet {
+ return this._annotations
+ }
+
+ override fun getAccessFlags(): Int {
+ return this.accessFlags
+ }
+
+ override fun getHiddenApiRestrictions(): MutableSet {
+ return this._hiddenApiRestrictions
+ }
+
+ override fun getInitialValue(): MutableEncodedValue? {
+ return this.initialValue
+ }
+
+ companion object {
+ fun Field.toMutable(): MutableField {
+ return MutableField(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableMethod.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableMethod.kt
new file mode 100644
index 0000000..2a778a5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableMethod.kt
@@ -0,0 +1,64 @@
+package app.revanced.patcher.proxy.mutableTypes
+
+import app.revanced.patcher.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
+import app.revanced.patcher.proxy.mutableTypes.MutableMethodParameter.Companion.toMutable
+import org.jf.dexlib2.HiddenApiRestriction
+import org.jf.dexlib2.base.reference.BaseMethodReference
+import org.jf.dexlib2.builder.MutableMethodImplementation
+import org.jf.dexlib2.iface.Method
+
+class MutableMethod(method: Method) : Method, BaseMethodReference() {
+ private var definingClass = method.definingClass
+ private var name = method.name
+ private var accessFlags = method.accessFlags
+ private var returnType = method.returnType
+
+ // Create own mutable MethodImplementation (due to not being able to change members like register count)
+ private val _implementation by lazy { method.implementation?.let { MutableMethodImplementation(it) } }
+ private val _annotations by lazy { method.annotations.map { annotation -> annotation.toMutable() }.toMutableSet() }
+ private val _parameters by lazy { method.parameters.map { parameter -> parameter.toMutable() }.toMutableList() }
+ private val _parameterTypes by lazy { method.parameterTypes.toMutableList() }
+ private val _hiddenApiRestrictions by lazy { method.hiddenApiRestrictions }
+
+ override fun getDefiningClass(): String {
+ return definingClass
+ }
+
+ override fun getName(): String {
+ return name
+ }
+
+ override fun getParameterTypes(): MutableList {
+ return _parameterTypes
+ }
+
+ override fun getReturnType(): String {
+ return returnType
+ }
+
+ override fun getAnnotations(): MutableSet {
+ return _annotations
+ }
+
+ override fun getAccessFlags(): Int {
+ return accessFlags
+ }
+
+ override fun getHiddenApiRestrictions(): MutableSet {
+ return _hiddenApiRestrictions
+ }
+
+ override fun getParameters(): MutableList {
+ return _parameters
+ }
+
+ override fun getImplementation(): MutableMethodImplementation? {
+ return _implementation
+ }
+
+ companion object {
+ fun Method.toMutable(): MutableMethod {
+ return MutableMethod(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableMethodParameter.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableMethodParameter.kt
new file mode 100644
index 0000000..3b5287f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/MutableMethodParameter.kt
@@ -0,0 +1,37 @@
+package app.revanced.patcher.proxy.mutableTypes
+
+import app.revanced.patcher.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
+import org.jf.dexlib2.base.BaseMethodParameter
+import org.jf.dexlib2.iface.MethodParameter
+
+// TODO: finish overriding all members if necessary
+class MutableMethodParameter(parameter: MethodParameter) : MethodParameter, BaseMethodParameter() {
+ private var type = parameter.type
+ private var name = parameter.name
+ private var signature = parameter.signature
+ private val _annotations by lazy {
+ parameter.annotations.map { annotation -> annotation.toMutable() }.toMutableSet()
+ }
+
+ override fun getType(): String {
+ return type
+ }
+
+ override fun getName(): String? {
+ return name
+ }
+
+ override fun getSignature(): String? {
+ return signature
+ }
+
+ override fun getAnnotations(): MutableSet {
+ return _annotations
+ }
+
+ companion object {
+ fun MethodParameter.toMutable(): MutableMethodParameter {
+ return MutableMethodParameter(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableAnnotationEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableAnnotationEncodedValue.kt
new file mode 100644
index 0000000..e62e573
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableAnnotationEncodedValue.kt
@@ -0,0 +1,33 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import app.revanced.patcher.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
+import org.jf.dexlib2.base.value.BaseAnnotationEncodedValue
+import org.jf.dexlib2.iface.AnnotationElement
+import org.jf.dexlib2.iface.value.AnnotationEncodedValue
+
+class MutableAnnotationEncodedValue(annotationEncodedValue: AnnotationEncodedValue) : BaseAnnotationEncodedValue(),
+ MutableEncodedValue {
+ private var type = annotationEncodedValue.type
+
+ private val _elements by lazy {
+ annotationEncodedValue.elements.map { annotationElement -> annotationElement.toMutable() }.toMutableSet()
+ }
+
+ override fun getType(): String {
+ return this.type
+ }
+
+ fun setType(type: String) {
+ this.type = type
+ }
+
+ override fun getElements(): MutableSet {
+ return _elements
+ }
+
+ companion object {
+ fun AnnotationEncodedValue.toMutable(): MutableAnnotationEncodedValue {
+ return MutableAnnotationEncodedValue(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableArrayEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableArrayEncodedValue.kt
new file mode 100644
index 0000000..268e78e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableArrayEncodedValue.kt
@@ -0,0 +1,22 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import app.revanced.patcher.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
+import org.jf.dexlib2.base.value.BaseArrayEncodedValue
+import org.jf.dexlib2.iface.value.ArrayEncodedValue
+import org.jf.dexlib2.iface.value.EncodedValue
+
+class MutableArrayEncodedValue(arrayEncodedValue: ArrayEncodedValue) : BaseArrayEncodedValue(), MutableEncodedValue {
+ private val _value by lazy {
+ arrayEncodedValue.value.map { encodedValue -> encodedValue.toMutable() }.toMutableList()
+ }
+
+ override fun getValue(): MutableList {
+ return _value
+ }
+
+ companion object {
+ fun ArrayEncodedValue.toMutable(): MutableArrayEncodedValue {
+ return MutableArrayEncodedValue(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableBooleanEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableBooleanEncodedValue.kt
new file mode 100644
index 0000000..d6377af
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableBooleanEncodedValue.kt
@@ -0,0 +1,23 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseBooleanEncodedValue
+import org.jf.dexlib2.iface.value.BooleanEncodedValue
+
+class MutableBooleanEncodedValue(booleanEncodedValue: BooleanEncodedValue) : BaseBooleanEncodedValue(),
+ MutableEncodedValue {
+ private var value = booleanEncodedValue.value
+
+ override fun getValue(): Boolean {
+ return this.value
+ }
+
+ fun setValue(value: Boolean) {
+ this.value = value
+ }
+
+ companion object {
+ fun BooleanEncodedValue.toMutable(): MutableBooleanEncodedValue {
+ return MutableBooleanEncodedValue(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableByteEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableByteEncodedValue.kt
new file mode 100644
index 0000000..61757c4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableByteEncodedValue.kt
@@ -0,0 +1,22 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseByteEncodedValue
+import org.jf.dexlib2.iface.value.ByteEncodedValue
+
+class MutableByteEncodedValue(byteEncodedValue: ByteEncodedValue) : BaseByteEncodedValue(), MutableEncodedValue {
+ private var value = byteEncodedValue.value
+
+ override fun getValue(): Byte {
+ return this.value
+ }
+
+ fun setValue(value: Byte) {
+ this.value = value
+ }
+
+ companion object {
+ fun ByteEncodedValue.toMutable(): MutableByteEncodedValue {
+ return MutableByteEncodedValue(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableCharEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableCharEncodedValue.kt
new file mode 100644
index 0000000..c3cb3ee
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableCharEncodedValue.kt
@@ -0,0 +1,22 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseCharEncodedValue
+import org.jf.dexlib2.iface.value.CharEncodedValue
+
+class MutableCharEncodedValue(charEncodedValue: CharEncodedValue) : BaseCharEncodedValue(), MutableEncodedValue {
+ private var value = charEncodedValue.value
+
+ override fun getValue(): Char {
+ return this.value
+ }
+
+ fun setValue(value: Char) {
+ this.value = value
+ }
+
+ companion object {
+ fun CharEncodedValue.toMutable(): MutableCharEncodedValue {
+ return MutableCharEncodedValue(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableDoubleEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableDoubleEncodedValue.kt
new file mode 100644
index 0000000..ddfb07b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableDoubleEncodedValue.kt
@@ -0,0 +1,23 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseDoubleEncodedValue
+import org.jf.dexlib2.iface.value.DoubleEncodedValue
+
+class MutableDoubleEncodedValue(doubleEncodedValue: DoubleEncodedValue) : BaseDoubleEncodedValue(),
+ MutableEncodedValue {
+ private var value = doubleEncodedValue.value
+
+ override fun getValue(): Double {
+ return this.value
+ }
+
+ fun setValue(value: Double) {
+ this.value = value
+ }
+
+ companion object {
+ fun DoubleEncodedValue.toMutable(): MutableDoubleEncodedValue {
+ return MutableDoubleEncodedValue(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableEncodedValue.kt
new file mode 100644
index 0000000..0f170bf
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableEncodedValue.kt
@@ -0,0 +1,32 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.ValueType
+import org.jf.dexlib2.iface.value.*
+
+interface MutableEncodedValue : EncodedValue {
+ companion object {
+ fun EncodedValue.toMutable(): MutableEncodedValue {
+ return when (this.valueType) {
+ ValueType.TYPE -> MutableTypeEncodedValue(this as TypeEncodedValue)
+ ValueType.FIELD -> MutableFieldEncodedValue(this as FieldEncodedValue)
+ ValueType.METHOD -> MutableMethodEncodedValue(this as MethodEncodedValue)
+ ValueType.ENUM -> MutableEnumEncodedValue(this as EnumEncodedValue)
+ ValueType.ARRAY -> MutableArrayEncodedValue(this as ArrayEncodedValue)
+ ValueType.ANNOTATION -> MutableAnnotationEncodedValue(this as AnnotationEncodedValue)
+ ValueType.BYTE -> MutableByteEncodedValue(this as ByteEncodedValue)
+ ValueType.SHORT -> MutableShortEncodedValue(this as ShortEncodedValue)
+ ValueType.CHAR -> MutableCharEncodedValue(this as CharEncodedValue)
+ ValueType.INT -> MutableIntEncodedValue(this as IntEncodedValue)
+ ValueType.LONG -> MutableLongEncodedValue(this as LongEncodedValue)
+ ValueType.FLOAT -> MutableFloatEncodedValue(this as FloatEncodedValue)
+ ValueType.DOUBLE -> MutableDoubleEncodedValue(this as DoubleEncodedValue)
+ ValueType.METHOD_TYPE -> MutableMethodTypeEncodedValue(this as MethodTypeEncodedValue)
+ ValueType.METHOD_HANDLE -> MutableMethodHandleEncodedValue(this as MethodHandleEncodedValue)
+ ValueType.STRING -> MutableStringEncodedValue(this as StringEncodedValue)
+ ValueType.BOOLEAN -> MutableBooleanEncodedValue(this as BooleanEncodedValue)
+ ValueType.NULL -> MutableNullEncodedValue()
+ else -> this as MutableEncodedValue
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableEnumEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableEnumEncodedValue.kt
new file mode 100644
index 0000000..d9fbdb9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableEnumEncodedValue.kt
@@ -0,0 +1,23 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseEnumEncodedValue
+import org.jf.dexlib2.iface.reference.FieldReference
+import org.jf.dexlib2.iface.value.EnumEncodedValue
+
+class MutableEnumEncodedValue(enumEncodedValue: EnumEncodedValue) : BaseEnumEncodedValue(), MutableEncodedValue {
+ private var value = enumEncodedValue.value
+
+ override fun getValue(): FieldReference {
+ return this.value
+ }
+
+ fun setValue(value: FieldReference) {
+ this.value = value
+ }
+
+ companion object {
+ fun EnumEncodedValue.toMutable(): MutableEnumEncodedValue {
+ return MutableEnumEncodedValue(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableFieldEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableFieldEncodedValue.kt
new file mode 100644
index 0000000..34d9b90
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableFieldEncodedValue.kt
@@ -0,0 +1,28 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.ValueType
+import org.jf.dexlib2.base.value.BaseFieldEncodedValue
+import org.jf.dexlib2.iface.reference.FieldReference
+import org.jf.dexlib2.iface.value.FieldEncodedValue
+
+class MutableFieldEncodedValue(fieldEncodedValue: FieldEncodedValue) : BaseFieldEncodedValue(), MutableEncodedValue {
+ private var value = fieldEncodedValue.value
+
+ override fun getValueType(): Int {
+ return ValueType.FIELD
+ }
+
+ override fun getValue(): FieldReference {
+ return this.value
+ }
+
+ fun setValue(value: FieldReference) {
+ this.value = value
+ }
+
+ companion object {
+ fun FieldEncodedValue.toMutable(): MutableFieldEncodedValue {
+ return MutableFieldEncodedValue(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableFloatEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableFloatEncodedValue.kt
new file mode 100644
index 0000000..c2fff54
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableFloatEncodedValue.kt
@@ -0,0 +1,22 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseFloatEncodedValue
+import org.jf.dexlib2.iface.value.FloatEncodedValue
+
+class MutableFloatEncodedValue(floatEncodedValue: FloatEncodedValue) : BaseFloatEncodedValue(), MutableEncodedValue {
+ private var value = floatEncodedValue.value
+
+ override fun getValue(): Float {
+ return this.value
+ }
+
+ fun setValue(value: Float) {
+ this.value = value
+ }
+
+ companion object {
+ fun FloatEncodedValue.toMutable(): MutableFloatEncodedValue {
+ return MutableFloatEncodedValue(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableIntEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableIntEncodedValue.kt
new file mode 100644
index 0000000..d5918c6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableIntEncodedValue.kt
@@ -0,0 +1,22 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseIntEncodedValue
+import org.jf.dexlib2.iface.value.IntEncodedValue
+
+class MutableIntEncodedValue(intEncodedValue: IntEncodedValue) : BaseIntEncodedValue(), MutableEncodedValue {
+ private var value = intEncodedValue.value
+
+ override fun getValue(): Int {
+ return this.value
+ }
+
+ fun setValue(value: Int) {
+ this.value = value
+ }
+
+ companion object {
+ fun IntEncodedValue.toMutable(): MutableIntEncodedValue {
+ return MutableIntEncodedValue(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableLongEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableLongEncodedValue.kt
new file mode 100644
index 0000000..190a2d8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableLongEncodedValue.kt
@@ -0,0 +1,22 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseLongEncodedValue
+import org.jf.dexlib2.iface.value.LongEncodedValue
+
+class MutableLongEncodedValue(longEncodedValue: LongEncodedValue) : BaseLongEncodedValue(), MutableEncodedValue {
+ private var value = longEncodedValue.value
+
+ override fun getValue(): Long {
+ return this.value
+ }
+
+ fun setValue(value: Long) {
+ this.value = value
+ }
+
+ companion object {
+ fun LongEncodedValue.toMutable(): MutableLongEncodedValue {
+ return MutableLongEncodedValue(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableMethodEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableMethodEncodedValue.kt
new file mode 100644
index 0000000..24f0ba0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableMethodEncodedValue.kt
@@ -0,0 +1,24 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseMethodEncodedValue
+import org.jf.dexlib2.iface.reference.MethodReference
+import org.jf.dexlib2.iface.value.MethodEncodedValue
+
+class MutableMethodEncodedValue(methodEncodedValue: MethodEncodedValue) : BaseMethodEncodedValue(),
+ MutableEncodedValue {
+ private var value = methodEncodedValue.value
+
+ override fun getValue(): MethodReference {
+ return this.value
+ }
+
+ fun setValue(value: MethodReference) {
+ this.value = value
+ }
+
+ companion object {
+ fun MethodEncodedValue.toMutable(): MutableMethodEncodedValue {
+ return MutableMethodEncodedValue(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableMethodHandleEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableMethodHandleEncodedValue.kt
new file mode 100644
index 0000000..54ba088
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableMethodHandleEncodedValue.kt
@@ -0,0 +1,27 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseMethodHandleEncodedValue
+import org.jf.dexlib2.iface.reference.MethodHandleReference
+import org.jf.dexlib2.iface.value.MethodHandleEncodedValue
+
+class MutableMethodHandleEncodedValue(methodHandleEncodedValue: MethodHandleEncodedValue) :
+ BaseMethodHandleEncodedValue(),
+ MutableEncodedValue {
+ private var value = methodHandleEncodedValue.value
+
+ override fun getValue(): MethodHandleReference {
+ return this.value
+ }
+
+ fun setValue(value: MethodHandleReference) {
+ this.value = value
+ }
+
+ companion object {
+ fun MethodHandleEncodedValue.toMutable(): MutableMethodHandleEncodedValue {
+ return MutableMethodHandleEncodedValue(this)
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableMethodTypeEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableMethodTypeEncodedValue.kt
new file mode 100644
index 0000000..dded228
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableMethodTypeEncodedValue.kt
@@ -0,0 +1,26 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseMethodTypeEncodedValue
+import org.jf.dexlib2.iface.reference.MethodProtoReference
+import org.jf.dexlib2.iface.value.MethodTypeEncodedValue
+
+class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedValue) : BaseMethodTypeEncodedValue(),
+ MutableEncodedValue {
+ private var value = methodTypeEncodedValue.value
+
+ override fun getValue(): MethodProtoReference {
+ return this.value
+ }
+
+ fun setValue(value: MethodProtoReference) {
+ this.value = value
+ }
+
+ companion object {
+ fun MethodTypeEncodedValue.toMutable(): MutableMethodTypeEncodedValue {
+ return MutableMethodTypeEncodedValue(this)
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableNullEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableNullEncodedValue.kt
new file mode 100644
index 0000000..cce128d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableNullEncodedValue.kt
@@ -0,0 +1,12 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseNullEncodedValue
+import org.jf.dexlib2.iface.value.ByteEncodedValue
+
+class MutableNullEncodedValue : BaseNullEncodedValue(), MutableEncodedValue {
+ companion object {
+ fun ByteEncodedValue.toMutable(): MutableByteEncodedValue {
+ return MutableByteEncodedValue(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableShortEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableShortEncodedValue.kt
new file mode 100644
index 0000000..e8c0f74
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableShortEncodedValue.kt
@@ -0,0 +1,22 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseShortEncodedValue
+import org.jf.dexlib2.iface.value.ShortEncodedValue
+
+class MutableShortEncodedValue(shortEncodedValue: ShortEncodedValue) : BaseShortEncodedValue(), MutableEncodedValue {
+ private var value = shortEncodedValue.value
+
+ override fun getValue(): Short {
+ return this.value
+ }
+
+ fun setValue(value: Short) {
+ this.value = value
+ }
+
+ companion object {
+ fun ShortEncodedValue.toMutable(): MutableShortEncodedValue {
+ return MutableShortEncodedValue(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableStringEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableStringEncodedValue.kt
new file mode 100644
index 0000000..f02acc4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableStringEncodedValue.kt
@@ -0,0 +1,24 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseStringEncodedValue
+import org.jf.dexlib2.iface.value.ByteEncodedValue
+import org.jf.dexlib2.iface.value.StringEncodedValue
+
+class MutableStringEncodedValue(stringEncodedValue: StringEncodedValue) : BaseStringEncodedValue(),
+ MutableEncodedValue {
+ private var value = stringEncodedValue.value
+
+ override fun getValue(): String {
+ return this.value
+ }
+
+ fun setValue(value: String) {
+ this.value = value
+ }
+
+ companion object {
+ fun ByteEncodedValue.toMutable(): MutableByteEncodedValue {
+ return MutableByteEncodedValue(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableTypeEncodedValue.kt b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableTypeEncodedValue.kt
new file mode 100644
index 0000000..6f56a33
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/proxy/mutableTypes/encodedValue/MutableTypeEncodedValue.kt
@@ -0,0 +1,22 @@
+package app.revanced.patcher.proxy.mutableTypes.encodedValue
+
+import org.jf.dexlib2.base.value.BaseTypeEncodedValue
+import org.jf.dexlib2.iface.value.TypeEncodedValue
+
+class MutableTypeEncodedValue(typeEncodedValue: TypeEncodedValue) : BaseTypeEncodedValue(), MutableEncodedValue {
+ private var value = typeEncodedValue.value
+
+ override fun getValue(): String {
+ return this.value
+ }
+
+ fun setValue(value: String) {
+ this.value = value
+ }
+
+ companion object {
+ fun TypeEncodedValue.toMutable(): MutableTypeEncodedValue {
+ return MutableTypeEncodedValue(this)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patcher/resolver/MethodResolver.kt b/src/main/kotlin/app/revanced/patcher/resolver/MethodResolver.kt
deleted file mode 100644
index ca0a41a..0000000
--- a/src/main/kotlin/app/revanced/patcher/resolver/MethodResolver.kt
+++ /dev/null
@@ -1,156 +0,0 @@
-package app.revanced.patcher.resolver
-
-import app.revanced.patcher.cache.MethodMap
-import app.revanced.patcher.cache.PatchData
-import app.revanced.patcher.cache.PatternScanData
-import app.revanced.patcher.signature.Signature
-import app.revanced.patcher.util.ExtraTypes
-import mu.KotlinLogging
-import org.objectweb.asm.Type
-import org.objectweb.asm.tree.*
-
-private val logger = KotlinLogging.logger("MethodResolver")
-
-internal class MethodResolver(private val classList: List, private val signatures: Array) {
- fun resolve(): MethodMap {
- val methodMap = MethodMap()
-
- for ((classNode, methods) in classList) {
- for (method in methods) {
- for (signature in signatures) {
- if (methodMap.containsKey(signature.name)) { // method already found for this sig
- logger.trace { "Sig ${signature.name} already found, skipping." }
- continue
- }
- logger.trace { "Resolving sig ${signature.name}: ${classNode.name} / ${method.name}" }
- val (r, sr) = cmp(method, signature)
- if (!r || sr == null) {
- logger.trace { "Compare result for sig ${signature.name} has failed!" }
- continue
- }
- logger.trace { "Method for sig ${signature.name} found!" }
- methodMap[signature.name] = PatchData(
- classNode,
- method,
- PatternScanData(
- // sadly we cannot create contracts for a data class, so we must assert
- sr.startIndex!!,
- sr.endIndex!!
- )
- )
- }
- }
- }
-
- for (signature in signatures) {
- if (methodMap.containsKey(signature.name)) continue
- logger.error { "Could not find method for sig ${signature.name}!" }
- }
-
- return methodMap
- }
-
- // These functions do not require the constructor values, so they can be static.
- companion object {
- fun resolveMethod(classNode: ClassNode, signature: Signature): PatchData? {
- for (method in classNode.methods) {
- val (r, sr) = cmp(method, signature)
- if (!r || sr == null) continue
- return PatchData(
- classNode,
- method,
- PatternScanData(0, 0) // opcode list is always ignored.
- )
- }
- return null
- }
-
- private fun cmp(method: MethodNode, signature: Signature): Pair {
- signature.returns?.let { _ ->
- val methodReturns = Type.getReturnType(method.desc).convertObject()
- if (signature.returns != methodReturns) {
- logger.trace {
- """
- Comparing sig ${signature.name}: invalid return type:
- expected ${signature.returns},
- got $methodReturns
- """.trimIndent()
- }
- return@cmp false to null
- }
- }
-
- signature.accessors?.let { _ ->
- if (signature.accessors != method.access) {
- logger.trace {
- """
- Comparing sig ${signature.name}: invalid accessors:
- expected ${signature.accessors},
- got ${method.access}
- """.trimIndent()
- }
- return@cmp false to null
- }
- }
-
- signature.parameters?.let { _ ->
- val parameters = Type.getArgumentTypes(method.desc).convertObjects()
- if (!signature.parameters.contentEquals(parameters)) {
- logger.trace {
- """
- Comparing sig ${signature.name}: invalid parameter types:
- expected ${signature.parameters.joinToString()}},
- got ${parameters.joinToString()}
- """.trimIndent()
- }
- return@cmp false to null
- }
- }
-
- signature.opcodes?.let { _ ->
- val result = method.instructions.scanFor(signature.opcodes)
- if (!result.found) {
- logger.trace { "Comparing sig ${signature.name}: invalid opcode pattern" }
- return@cmp false to null
- }
- return@cmp true to result
- }
-
- return true to ScanResult(true)
- }
- }
-}
-
-private operator fun ClassNode.component1() = this
-private operator fun ClassNode.component2() = this.methods
-
-private fun InsnList.scanFor(pattern: IntArray): ScanResult {
- for (i in 0 until this.size()) {
- var occurrence = 0
- while (i + occurrence < this.size()) {
- val n = this[i + occurrence]
- if (!n.shouldSkip() && n.opcode != pattern[occurrence]) break
- if (++occurrence >= pattern.size) {
- val current = i + occurrence
- return ScanResult(true, current - pattern.size, current)
- }
- }
- }
-
- return ScanResult(false)
-}
-
-private fun Type.convertObject(): Type {
- return when (this.sort) {
- Type.OBJECT -> ExtraTypes.Any
- Type.ARRAY -> ExtraTypes.ArrayAny
- else -> this
- }
-}
-
-private fun Array.convertObjects(): Array {
- return this.map { it.convertObject() }.toTypedArray()
-}
-
-private fun AbstractInsnNode.shouldSkip() =
- type == AbstractInsnNode.LABEL || type == AbstractInsnNode.LINE
diff --git a/src/main/kotlin/app/revanced/patcher/resolver/ScanResult.kt b/src/main/kotlin/app/revanced/patcher/resolver/ScanResult.kt
deleted file mode 100644
index 135b0ca..0000000
--- a/src/main/kotlin/app/revanced/patcher/resolver/ScanResult.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package app.revanced.patcher.resolver
-
-internal data class ScanResult(
- val found: Boolean,
- val startIndex: Int? = 0,
- val endIndex: Int? = 0
-)
diff --git a/src/main/kotlin/app/revanced/patcher/signature/MethodSignature.kt b/src/main/kotlin/app/revanced/patcher/signature/MethodSignature.kt
new file mode 100644
index 0000000..1e7b13a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/signature/MethodSignature.kt
@@ -0,0 +1,111 @@
+package app.revanced.patcher.signature
+
+import app.revanced.patcher.data.implementation.MethodNotFoundException
+import app.revanced.patcher.patch.implementation.metadata.PackageMetadata
+import org.jf.dexlib2.Opcode
+
+/**
+ * Represents the [MethodSignature] for a method.
+ * @param metadata Metadata for this [MethodSignature].
+ * @param returnType The return type of the method.
+ * @param accessFlags The access flags of the method.
+ * @param methodParameters The parameters of the method.
+ * @param opcodes The list of opcodes of the method.
+ * @param strings A list of strings which a method contains.
+ * A `null` opcode is equals to an unknown opcode.
+ */
+class MethodSignature(
+ val metadata: MethodSignatureMetadata,
+ internal val returnType: String?,
+ internal val accessFlags: Int?,
+ internal val methodParameters: Iterable?,
+ internal val opcodes: Iterable?,
+ internal val strings: Iterable? = null
+) {
+ /**
+ * The result of the signature
+ */
+ var result: SignatureResolverResult? = null
+ get() {
+ return field ?: throw MethodNotFoundException(
+ "Could not resolve required signature ${metadata.name}"
+ )
+ }
+ val resolved: Boolean
+ get() {
+ var resolved = false
+ try {
+ resolved = result != null
+ } catch (_: Exception) {
+ }
+ return resolved
+ }
+}
+
+/**
+ * Metadata about a [MethodSignature].
+ * @param name A suggestive name for the [MethodSignature].
+ * @param methodMetadata Metadata about the method for the [MethodSignature].
+ * @param patternScanMethod The pattern scanning method the pattern scanner should rely on.
+ * Can either be [PatternScanMethod.Fuzzy] or [PatternScanMethod.Direct].
+ * @param description An optional description for the [MethodSignature].
+ * @param compatiblePackages The list of packages the [MethodSignature] is compatible with.
+ * @param version The version of this signature.
+ */
+data class MethodSignatureMetadata(
+ val name: String,
+ val methodMetadata: MethodMetadata?,
+ val patternScanMethod: PatternScanMethod,
+ val compatiblePackages: Iterable,
+ val description: String?,
+ val version: String
+)
+
+/**
+ * Metadata about the method for a [MethodSignature].
+ * @param definingClass The defining class name of the method.
+ * @param name A suggestive name for the method which the [MethodSignature] was created for.
+ */
+data class MethodMetadata(
+ val definingClass: String?,
+ val name: String?
+)
+
+/**
+ * The method, the patcher should rely on when scanning the opcode pattern of a [MethodSignature]
+ */
+interface PatternScanMethod {
+ /**
+ * When comparing the signature, if one or more of the opcodes do not match, skip.
+ */
+ class Direct : PatternScanMethod
+
+ /**
+ * When comparing the signature, if [threshold] or more of the opcodes do not match, skip.
+ */
+ class Fuzzy(internal val threshold: Int) : PatternScanMethod {
+ /**
+ * A list of warnings the resolver found.
+ *
+ * This list will be allocated when the signature has been found.
+ * Meaning, if the signature was not found,
+ * or the signature was not yet resolved,
+ * the list will be null.
+ */
+ var warnings: List? = null
+
+ /**
+ * Represents a resolver warning.
+ * @param correctOpcode The opcode the instruction list has.
+ * @param wrongOpcode The opcode the pattern list of the signature currently has.
+ * @param instructionIndex The index of the opcode relative to the instruction list.
+ * @param patternIndex The index of the opcode relative to the pattern list from the signature.
+ */
+ data class Warning(
+ val correctOpcode: Opcode,
+ val wrongOpcode: Opcode,
+ val instructionIndex: Int,
+ val patternIndex: Int,
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/signature/Signature.kt b/src/main/kotlin/app/revanced/patcher/signature/Signature.kt
deleted file mode 100644
index 19f303d..0000000
--- a/src/main/kotlin/app/revanced/patcher/signature/Signature.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package app.revanced.patcher.signature
-
-import org.objectweb.asm.Type
-
-/**
- * An ASM signature list for the Patcher.
- *
- * @param name The name of the method.
- * Do not use the actual method name, instead try to guess what the method name originally was.
- * If you are unable to guess a method name, doing something like "patch-name-1" is fine too.
- * For example: "override-codec-1".
- * This method name will be mapped to the method matching the signature.
- * Even though this is technically not needed for the `findParentMethod` method,
- * it is still recommended giving the method a name, so it can be identified easily.
- * @param returns The return type/signature of the method.
- * @param accessors The accessors of the method.
- * @param parameters The parameter types of the method.
- * @param opcodes The opcode pattern of the method, used to find the method by pattern scanning.
- */
-@Suppress("ArrayInDataClass")
-data class Signature(
- val name: String,
- val returns: Type?,
- val accessors: Int?,
- val parameters: Array?,
- val opcodes: IntArray?
-)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/signature/SignatureResolverResult.kt b/src/main/kotlin/app/revanced/patcher/signature/SignatureResolverResult.kt
new file mode 100644
index 0000000..f3e4c5b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/signature/SignatureResolverResult.kt
@@ -0,0 +1,48 @@
+package app.revanced.patcher.signature
+
+import app.revanced.patcher.extensions.softCompareTo
+import app.revanced.patcher.proxy.ClassProxy
+import app.revanced.patcher.signature.resolver.SignatureResolver
+import org.jf.dexlib2.iface.Method
+
+/**
+ * Represents the result of a [SignatureResolver].
+ * @param definingClassProxy The [ClassProxy] that the matching method was found in.
+ * @param resolvedMethod The actual matching method.
+ * @param scanData Opcodes pattern scan result.
+ */
+data class SignatureResolverResult(
+ val definingClassProxy: ClassProxy,
+ val scanData: PatternScanResult,
+ private val resolvedMethod: Method,
+) {
+ /**
+ * Returns the **mutable** method by the [resolvedMethod] from the [definingClassProxy].
+ *
+ * Please note, this method allocates a [ClassProxy].
+ * Use [immutableMethod] where possible.
+ */
+ val method
+ get() = definingClassProxy.resolve().methods.first {
+ it.softCompareTo(resolvedMethod)
+ }
+
+ /**
+ * Returns the **immutable** method by the [resolvedMethod] from the [definingClassProxy].
+ *
+ * If you need to modify the method, use [method] instead.
+ */
+ val immutableMethod: Method
+ get() = definingClassProxy.immutableClass.methods.first {
+ it.softCompareTo(resolvedMethod)
+ }
+
+ fun findParentMethod(signature: MethodSignature): SignatureResolverResult? {
+ return SignatureResolver.resolveFromProxy(definingClassProxy, signature)
+ }
+}
+
+data class PatternScanResult(
+ val startIndex: Int,
+ val endIndex: Int
+)
diff --git a/src/main/kotlin/app/revanced/patcher/signature/resolver/SignatureResolver.kt b/src/main/kotlin/app/revanced/patcher/signature/resolver/SignatureResolver.kt
new file mode 100644
index 0000000..c7d2d0d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/signature/resolver/SignatureResolver.kt
@@ -0,0 +1,167 @@
+package app.revanced.patcher.signature.resolver
+
+import app.revanced.patcher.data.PatcherData
+import app.revanced.patcher.data.implementation.proxy
+import app.revanced.patcher.extensions.parametersEqual
+import app.revanced.patcher.proxy.ClassProxy
+import app.revanced.patcher.signature.MethodSignature
+import app.revanced.patcher.signature.PatternScanMethod
+import app.revanced.patcher.signature.PatternScanResult
+import app.revanced.patcher.signature.SignatureResolverResult
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.ClassDef
+import org.jf.dexlib2.iface.Method
+import org.jf.dexlib2.iface.instruction.Instruction
+import org.jf.dexlib2.iface.instruction.formats.Instruction21c
+import org.jf.dexlib2.iface.reference.StringReference
+
+internal class SignatureResolver(
+ private val classes: List,
+ private val methodSignatures: Iterable
+) {
+ fun resolve(patcherData: PatcherData) {
+ for (signature in methodSignatures) {
+ for (classDef in classes) {
+ for (method in classDef.methods) {
+ val patternScanData = compareSignatureToMethod(signature, method) ?: continue
+
+ // create class proxy, in case a patch needs mutability
+ val classProxy = patcherData.bytecodeData.proxy(classDef)
+ signature.result = SignatureResolverResult(
+ classProxy,
+ patternScanData,
+ method,
+ )
+ }
+ }
+ }
+ }
+
+ // These functions do not require the constructor values, so they can be static.
+ companion object {
+ fun resolveFromProxy(classProxy: ClassProxy, signature: MethodSignature): SignatureResolverResult? {
+ for (method in classProxy.immutableClass.methods) {
+ val result = compareSignatureToMethod(signature, method) ?: continue
+ return SignatureResolverResult(
+ classProxy,
+ result,
+ method,
+ )
+ }
+ return null
+ }
+
+ private fun compareSignatureToMethod(
+ signature: MethodSignature,
+ method: Method
+ ): PatternScanResult? {
+ signature.returnType?.let {
+ if (!method.returnType.startsWith(signature.returnType)) {
+ return null
+ }
+ }
+
+ signature.accessFlags?.let {
+ if (signature.accessFlags != method.accessFlags) {
+ return null
+ }
+ }
+
+ signature.methodParameters?.let {
+ if (!parametersEqual(signature.methodParameters, method.parameterTypes)) {
+ return null
+ }
+ }
+
+ signature.strings?.let { strings ->
+ method.implementation ?: return null
+
+ method.implementation!!.instructions.let { instructions ->
+ val stringsList = strings.toMutableList()
+
+ for (instruction in instructions) {
+ if (instruction.opcode != Opcode.CONST_STRING) continue
+
+ val string = ((instruction as Instruction21c).reference as StringReference).string
+ val i = stringsList.indexOfFirst { it == string }
+ if (i != -1) stringsList.removeAt(i)
+ }
+
+ if (stringsList.isNotEmpty()) return null
+ }
+ }
+
+ return if (signature.opcodes == null) {
+ PatternScanResult(0, 0)
+ } else {
+ method.implementation?.instructions?.let {
+ compareOpcodes(signature, it)
+ }
+ }
+ }
+
+ private fun compareOpcodes(
+ signature: MethodSignature,
+ instructions: Iterable
+ ): PatternScanResult? {
+ val count = instructions.count()
+ val pattern = signature.opcodes!!
+ val size = pattern.count()
+ val method = signature.metadata.patternScanMethod
+ val threshold = if (method is PatternScanMethod.Fuzzy)
+ method.threshold else 0
+
+ for (instructionIndex in 0 until count) {
+ var patternIndex = 0
+ var currentThreshold = threshold
+ while (instructionIndex + patternIndex < count) {
+ val originalOpcode = instructions.elementAt(instructionIndex + patternIndex).opcode
+ val patternOpcode = pattern.elementAt(patternIndex)
+ if (
+ patternOpcode != null && // unknown opcode
+ originalOpcode != patternOpcode &&
+ currentThreshold-- == 0
+ ) break
+ if (++patternIndex < size) continue
+ patternIndex-- // fix pattern offset
+
+ val result = PatternScanResult(instructionIndex, instructionIndex + patternIndex)
+ if (method is PatternScanMethod.Fuzzy) {
+ method.warnings = generateWarnings(
+ signature, instructions, result
+ )
+ }
+ return result
+ }
+ }
+
+ return null
+ }
+
+ private fun generateWarnings(
+ signature: MethodSignature,
+ instructions: Iterable,
+ scanResult: PatternScanResult,
+ ) = buildList {
+ val pattern = signature.opcodes!!
+ for ((patternIndex, instructionIndex) in (scanResult.startIndex until scanResult.endIndex).withIndex()) {
+ val correctOpcode = instructions.elementAt(instructionIndex).opcode
+ val patternOpcode = pattern.elementAt(patternIndex)
+ if (
+ patternOpcode != null && // unknown opcode
+ correctOpcode != patternOpcode
+ ) {
+ this.add(
+ PatternScanMethod.Fuzzy.Warning(
+ correctOpcode, patternOpcode,
+ instructionIndex, patternIndex,
+ )
+ )
+ }
+ }
+ }
+ }
+}
+
+private operator fun ClassDef.component1() = this
+private operator fun ClassDef.component2() = this.methods
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/smali/InlineSmaliCompiler.kt b/src/main/kotlin/app/revanced/patcher/smali/InlineSmaliCompiler.kt
new file mode 100644
index 0000000..f0ac965
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/smali/InlineSmaliCompiler.kt
@@ -0,0 +1,58 @@
+package app.revanced.patcher.smali
+
+import org.antlr.runtime.CommonTokenStream
+import org.antlr.runtime.TokenSource
+import org.antlr.runtime.tree.CommonTreeNodeStream
+import org.jf.dexlib2.Opcodes
+import org.jf.dexlib2.builder.BuilderInstruction
+import org.jf.dexlib2.writer.builder.DexBuilder
+import org.jf.smali.LexerErrorInterface
+import org.jf.smali.smaliFlexLexer
+import org.jf.smali.smaliParser
+import org.jf.smali.smaliTreeWalker
+import java.io.InputStreamReader
+
+private const val METHOD_TEMPLATE = """
+.class public Linlinecompiler;
+.super Ljava/lang/Object;
+.method public static compiler(%s)V
+ .registers %d
+ %s
+.end method
+"""
+
+class InlineSmaliCompiler {
+ companion object {
+ /**
+ * 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.
+ * Do not cross the boundaries of the control flow (if-nez insn, etc),
+ * 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 compileMethodInstructions(instructions: String, parameters: String, registers: Int): List {
+ val input = METHOD_TEMPLATE.format(parameters, registers, instructions)
+ val reader = InputStreamReader(input.byteInputStream())
+ val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15)
+ val tokens = CommonTokenStream(lexer as TokenSource)
+ val parser = smaliParser(tokens)
+ val result = parser.smali_file()
+ if (parser.numberOfSyntaxErrors > 0 || lexer.numberOfSyntaxErrors > 0) {
+ throw IllegalStateException(
+ "Encountered ${parser.numberOfSyntaxErrors} parser syntax errors and ${lexer.numberOfSyntaxErrors} lexer syntax errors!"
+ )
+ }
+ val treeStream = CommonTreeNodeStream(result.tree)
+ treeStream.tokenStream = tokens
+ val dexGen = smaliTreeWalker(treeStream)
+ dexGen.setDexBuilder(DexBuilder(Opcodes.getDefault()))
+ val classDef = dexGen.smali_file()
+ return classDef.methods.first().implementation!!.instructions.map { it.toBuilderInstruction() }
+ }
+ }
+}
+
+fun String.toInstructions(parameters: String = "", registers: Int = 1) = InlineSmaliCompiler.compileMethodInstructions(this, parameters, registers)
+fun String.toInstruction(parameters: String = "", registers: Int = 1) = this.toInstructions(parameters, registers).first()
diff --git a/src/main/kotlin/app/revanced/patcher/smali/InstructionConverter.kt b/src/main/kotlin/app/revanced/patcher/smali/InstructionConverter.kt
new file mode 100644
index 0000000..57363d7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/smali/InstructionConverter.kt
@@ -0,0 +1,261 @@
+package app.revanced.patcher.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
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/util/ExtraTypes.kt b/src/main/kotlin/app/revanced/patcher/util/ExtraTypes.kt
deleted file mode 100644
index 430a4be..0000000
--- a/src/main/kotlin/app/revanced/patcher/util/ExtraTypes.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package app.revanced.patcher.util
-
-import org.objectweb.asm.Type
-
-object ExtraTypes {
- /**
- * Any object type.
- * Should be used instead of types such as: "Ljava/lang/String;"
- */
- val Any: Type = Type.getType(Object::class.java)
- val ArrayAny: Type = Type.getType(Array::class.java)
-}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/util/Io.kt b/src/main/kotlin/app/revanced/patcher/util/Io.kt
deleted file mode 100644
index 4db1a27..0000000
--- a/src/main/kotlin/app/revanced/patcher/util/Io.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-package app.revanced.patcher.util
-
-import org.objectweb.asm.ClassReader
-import org.objectweb.asm.ClassWriter
-import org.objectweb.asm.tree.ClassNode
-import java.io.BufferedInputStream
-import java.io.InputStream
-import java.io.OutputStream
-import java.util.jar.JarEntry
-import java.util.jar.JarInputStream
-import java.util.zip.ZipEntry
-import java.util.zip.ZipInputStream
-import java.util.zip.ZipOutputStream
-
-internal class Io(
- private val input: InputStream,
- private val output: OutputStream,
- private val classes: MutableList
-) {
- private val bufferedInputStream = BufferedInputStream(input)
-
- fun readFromJar() {
- bufferedInputStream.mark(Integer.MAX_VALUE)
- // create a BufferedInputStream in order to read the input stream again when calling saveAsJar(..)
- val jis = JarInputStream(bufferedInputStream)
-
- // read all entries from the input stream
- // we use JarEntry because we only read .class files
- lateinit var jarEntry: JarEntry
- while (jis.nextJarEntry.also { if (it != null) jarEntry = it } != null) {
- // if the current entry ends with .class (indicating a java class file), add it to our list of classes to return
- if (jarEntry.name.endsWith(".class")) {
- // create a new ClassNode
- val classNode = ClassNode()
- // read the bytes with a ClassReader into the ClassNode
- ClassReader(jis.readBytes()).accept(classNode, ClassReader.EXPAND_FRAMES)
- // add it to our list
- classes.add(classNode)
- }
-
- // finally, close the entry
- jis.closeEntry()
- }
-
- // at last reset the buffered input stream
- bufferedInputStream.reset()
- }
-
- fun saveAsJar() {
- val jis = ZipInputStream(bufferedInputStream)
- val jos = ZipOutputStream(output)
- val classReaders = mutableMapOf()
-
- // first write all non .class zip entries from the original input stream to the output stream
- // we read it first to close the input stream as fast as possible
- // TODO(oSumAtrIX): There is currently no way to remove non .class files.
- lateinit var zipEntry: ZipEntry
- while (jis.nextEntry.also { if (it != null) zipEntry = it } != null) {
- if (zipEntry.name.endsWith(".class")) {
- classReaders[zipEntry.name] = ClassReader(jis.readBytes())
- continue
- }
-
- // create a new zipEntry and write the contents of the zipEntry to the output stream and close it
- jos.putNextEntry(ZipEntry(zipEntry))
- jos.write(jis.readBytes())
- jos.closeEntry()
- }
-
- // finally, close the input stream
- jis.close()
- bufferedInputStream.close()
- input.close()
-
- // now write all the patched classes to the output stream
- for (patchedClass in classes) {
- // create a new entry of the patched class
- val name = patchedClass.name + ".class"
- jos.putNextEntry(JarEntry(name))
-
- // parse the patched class to a byte array and write it to the output stream
- val cw = ClassWriter(classReaders[name]!!, ClassWriter.COMPUTE_MAXS)
- patchedClass.accept(cw)
- jos.write(cw.toByteArray())
-
- // close the newly created jar entry
- jos.closeEntry()
- }
-
- // finally, close the rest of the streams
- jos.close()
- output.close()
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/util/ListBackedSet.kt b/src/main/kotlin/app/revanced/patcher/util/ListBackedSet.kt
new file mode 100644
index 0000000..f019b14
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/util/ListBackedSet.kt
@@ -0,0 +1,15 @@
+package app.revanced.patcher.util
+
+class ListBackedSet(private val list: MutableList) : MutableSet {
+ override val size get() = list.size
+ override fun add(element: E) = list.add(element)
+ override fun addAll(elements: Collection) = list.addAll(elements)
+ override fun clear() = list.clear()
+ override fun iterator() = list.listIterator()
+ override fun remove(element: E) = list.remove(element)
+ override fun removeAll(elements: Collection) = list.removeAll(elements)
+ override fun retainAll(elements: Collection) = list.retainAll(elements)
+ override fun contains(element: E) = list.contains(element)
+ override fun containsAll(elements: Collection) = list.containsAll(elements)
+ override fun isEmpty() = list.isEmpty()
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/util/ProxyBackedClassList.kt b/src/main/kotlin/app/revanced/patcher/util/ProxyBackedClassList.kt
new file mode 100644
index 0000000..6e28f59
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patcher/util/ProxyBackedClassList.kt
@@ -0,0 +1,46 @@
+package app.revanced.patcher.util
+
+import app.revanced.patcher.proxy.ClassProxy
+import org.jf.dexlib2.iface.ClassDef
+
+class ProxyBackedClassList(internal val internalClasses: MutableList) : List {
+ internal val proxies = mutableListOf()
+
+ fun add(classDef: ClassDef) {
+ internalClasses.add(classDef)
+ }
+
+ fun add(classProxy: ClassProxy) {
+ proxies.add(classProxy)
+ }
+
+ /**
+ * Apply all resolved classes into [internalClasses] and clean the [proxies] list.
+ */
+ fun applyProxies() {
+ // FIXME: check if this could cause issues when multiple patches use the same proxy
+ proxies.removeIf { proxy ->
+ // if the proxy is unused, keep it in the list
+ if (!proxy.proxyUsed) return@removeIf false
+
+ // if it has been used, replace the internal class which it proxied
+ val index = internalClasses.indexOfFirst { it.type == proxy.immutableClass.type }
+ internalClasses[index] = proxy.mutatedClass
+
+ // return true to remove it from the proxies list
+ return@removeIf true
+ }
+ }
+
+ override val size get() = internalClasses.size
+ override fun contains(element: ClassDef) = internalClasses.contains(element)
+ override fun containsAll(elements: Collection) = internalClasses.containsAll(elements)
+ override fun get(index: Int) = internalClasses[index]
+ override fun indexOf(element: ClassDef) = internalClasses.indexOf(element)
+ override fun isEmpty() = internalClasses.isEmpty()
+ override fun iterator() = internalClasses.iterator()
+ override fun lastIndexOf(element: ClassDef) = internalClasses.lastIndexOf(element)
+ override fun listIterator() = internalClasses.listIterator()
+ override fun listIterator(index: Int) = internalClasses.listIterator(index)
+ override fun subList(fromIndex: Int, toIndex: Int) = internalClasses.subList(fromIndex, toIndex)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patcher/writer/ASMWriter.kt b/src/main/kotlin/app/revanced/patcher/writer/ASMWriter.kt
deleted file mode 100644
index 1a8a014..0000000
--- a/src/main/kotlin/app/revanced/patcher/writer/ASMWriter.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package app.revanced.patcher.writer
-
-import org.objectweb.asm.tree.AbstractInsnNode
-import org.objectweb.asm.tree.InsnList
-
-object ASMWriter {
- fun InsnList.setAt(index: Int, node: AbstractInsnNode) {
- this[this.get(index)] = node
- }
-
- fun InsnList.insertAt(index: Int = 0, vararg nodes: AbstractInsnNode) {
- this.insert(this.get(index), nodes.toInsnList())
- }
-
- // TODO(Sculas): Should this be public?
- private fun Array.toInsnList(): InsnList {
- val list = InsnList()
- this.forEach { list.add(it) }
- return list
- }
-}
\ No newline at end of file
diff --git a/src/test/kotlin/app/revanced/patcher/PatcherTest.kt b/src/test/kotlin/app/revanced/patcher/PatcherTest.kt
index 94824da..1007aac 100644
--- a/src/test/kotlin/app/revanced/patcher/PatcherTest.kt
+++ b/src/test/kotlin/app/revanced/patcher/PatcherTest.kt
@@ -1,177 +1,49 @@
package app.revanced.patcher
-import app.revanced.patcher.cache.Cache
-import app.revanced.patcher.patch.Patch
-import app.revanced.patcher.patch.PatchResult
-import app.revanced.patcher.patch.PatchResultSuccess
-import app.revanced.patcher.signature.Signature
-import app.revanced.patcher.util.ExtraTypes
-import app.revanced.patcher.util.TestUtil
-import app.revanced.patcher.writer.ASMWriter.insertAt
-import app.revanced.patcher.writer.ASMWriter.setAt
-import org.junit.jupiter.api.assertDoesNotThrow
-import org.objectweb.asm.Opcodes.*
-import org.objectweb.asm.Type
-import org.objectweb.asm.tree.FieldInsnNode
-import org.objectweb.asm.tree.LdcInsnNode
-import org.objectweb.asm.tree.MethodInsnNode
-import java.io.ByteArrayOutputStream
-import java.io.PrintStream
-import kotlin.test.Test
+import app.revanced.patcher.signature.PatternScanMethod
+import app.revanced.patcher.usage.ExampleBytecodePatch
+import app.revanced.patcher.usage.ExampleResourcePatch
+import org.junit.jupiter.api.Test
+import java.io.File
+import kotlin.test.assertTrue
internal class PatcherTest {
- companion object {
- val testSignatures: Array = arrayOf(
- // Java:
- // public static void main(String[] args) {
- // System.out.println("Hello, world!");
- // }
- // Bytecode:
- // public static main(java.lang.String[] arg0) { // Method signature: ([Ljava/lang/String;)V
- // getstatic java/lang/System.out:java.io.PrintStream
- // ldc "Hello, world!" (java.lang.String)
- // invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
- // return
- // }
- Signature(
- "mainMethod",
- Type.VOID_TYPE,
- ACC_PUBLIC or ACC_STATIC,
- arrayOf(ExtraTypes.ArrayAny),
- intArrayOf(
- GETSTATIC,
- LDC,
- INVOKEVIRTUAL,
- RETURN
- )
- )
- )
- }
-
@Test
fun testPatcher() {
+ return // FIXME: create a proper resource to pass this test
val patcher = Patcher(
- PatcherTest::class.java.getResourceAsStream("/test1.jar")!!,
- ByteArrayOutputStream(),
- testSignatures
+ File(PatcherTest::class.java.getResource("/example.apk")!!.toURI()),
+ "exampleCacheDirectory",
+ patchResources = true
)
- patcher.addPatches(
- object : Patch("TestPatch") {
- override fun execute(cache: Cache): PatchResult {
- // Get the method from the resolver cache
- val mainMethod = patcher.cache.methods["mainMethod"]
- // Get the instruction list
- val instructions = mainMethod.method.instructions!!
+ patcher.addPatches(listOf(ExampleBytecodePatch(), ExampleResourcePatch()))
- // 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 LDC instruction.
- val startIndex = mainMethod.scanData.startIndex
-
- // Ignore this, just testing if the method resolver works :)
- TestUtil.assertNodeEqual(
- FieldInsnNode(
- GETSTATIC,
- Type.getInternalName(System::class.java),
- "out",
- // for whatever reason, it adds an "L" and ";" to the node string
- "L${Type.getInternalName(PrintStream::class.java)};"
- ),
- instructions[startIndex]!!
- )
-
- // Create a new LDC node and replace the LDC instruction.
- val stringNode = LdcInsnNode("Hello, ReVanced! Editing bytecode.")
- instructions.setAt(startIndex, stringNode)
-
- // Now lets print our string twice!
- // Insert our instructions after the second instruction by our pattern.
- // This will place our instructions after the original INVOKEVIRTUAL call.
- // You could also copy the instructions from the list and then modify the LDC instruction again,
- // but this is to show a more advanced example of writing bytecode using the patcher and ASM.
- instructions.insertAt(
- startIndex + 1,
- FieldInsnNode(
- GETSTATIC,
- Type.getInternalName(System::class.java), // "java/lang/System"
- "out",
- Type.getInternalName(PrintStream::class.java) // "java/io/PrintStream"
- ),
- LdcInsnNode("Hello, ReVanced! Adding bytecode."),
- MethodInsnNode(
- INVOKEVIRTUAL,
- Type.getInternalName(PrintStream::class.java), // "java/io/PrintStream"
- "println",
- Type.getMethodDescriptor(
- Type.VOID_TYPE,
- Type.getType(String::class.java)
- ) // "(Ljava/lang/String;)V"
- )
- )
-
- // Our code now looks like this:
- // public static main(java.lang.String[] arg0) { // Method signature: ([Ljava/lang/String;)V
- // getstatic java/lang/System.out:java.io.PrintStream
- // ldc "Hello, ReVanced! Editing bytecode." (java.lang.String) // We overwrote this instruction.
- // invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
- // getstatic java/lang/System.out:java.io.PrintStream // This instruction and the 2 instructions below are written manually.
- // ldc "Hello, ReVanced! Adding bytecode." (java.lang.String)
- // invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
- // return
- // }
-
- // 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()
+ 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!")
}
}
- )
-
- // Apply all patches loaded in the patcher
- val patchResult = patcher.applyPatches()
- // You can check if an error occurred
- for ((patchName, result) in patchResult) {
+ }
+ for ((metadata, result) in patcher.applyPatches()) {
if (result.isFailure) {
- throw Exception("Patch $patchName failed", result.exceptionOrNull()!!)
+ throw Exception("Patch ${metadata.shortName} failed", result.exceptionOrNull()!!)
+ } else {
+ println("Patch ${metadata.shortName} applied successfully!")
}
}
-
- patcher.save()
- }
-
- @Test
- fun `test patcher with no changes`() {
- val testData = PatcherTest::class.java.getResourceAsStream("/test1.jar")!!
- // val available = testData.available()
- val out = ByteArrayOutputStream()
- Patcher(testData, out, testSignatures).save()
- // FIXME(Sculas): There seems to be a 1-byte difference, not sure what it is.
- // assertEquals(available, out.size())
- out.close()
- }
-
- @Test()
- fun `should not raise an exception if any signature member except the name is missing`() {
- val sigName = "testMethod"
-
- assertDoesNotThrow(
- "Should not raise an exception if any signature member except the name is missing"
- ) {
- Patcher(
- PatcherTest::class.java.getResourceAsStream("/test1.jar")!!,
- ByteArrayOutputStream(),
- arrayOf(
- Signature(
- sigName,
- null,
- null,
- null,
- null
- ))
- )
- }
+ val out = patcher.save()
+ assertTrue(out.isNotEmpty(), "Expected the output of Patcher#save() to not be empty.")
}
}
\ No newline at end of file
diff --git a/src/test/kotlin/app/revanced/patcher/ReaderTest.kt b/src/test/kotlin/app/revanced/patcher/ReaderTest.kt
deleted file mode 100644
index becd7f6..0000000
--- a/src/test/kotlin/app/revanced/patcher/ReaderTest.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package app.revanced.patcher
-
-import java.io.ByteArrayOutputStream
-import kotlin.test.Test
-
-internal class ReaderTest {
- @Test
- fun `read jar containing multiple classes`() {
- val testData = PatcherTest::class.java.getResourceAsStream("/test2.jar")!!
- Patcher(testData, ByteArrayOutputStream(), PatcherTest.testSignatures).save() // reusing test sigs from PatcherTest
- }
-}
\ No newline at end of file
diff --git a/src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt
new file mode 100644
index 0000000..18745c5
--- /dev/null
+++ b/src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt
@@ -0,0 +1,199 @@
+package app.revanced.patcher.usage
+
+import app.revanced.patcher.data.implementation.BytecodeData
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.patch.implementation.BytecodePatch
+import app.revanced.patcher.patch.implementation.metadata.PackageMetadata
+import app.revanced.patcher.patch.implementation.metadata.PatchMetadata
+import app.revanced.patcher.patch.implementation.misc.PatchResult
+import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
+import app.revanced.patcher.proxy.mutableTypes.MutableField.Companion.toMutable
+import app.revanced.patcher.proxy.mutableTypes.MutableMethod.Companion.toMutable
+import app.revanced.patcher.signature.MethodMetadata
+import app.revanced.patcher.signature.MethodSignature
+import app.revanced.patcher.signature.MethodSignatureMetadata
+import app.revanced.patcher.signature.PatternScanMethod
+import app.revanced.patcher.smali.toInstruction
+import app.revanced.patcher.smali.toInstructions
+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
+
+val packageMetadata = listOf(
+ PackageMetadata(
+ "com.example.examplePackage",
+ listOf("0.0.1", "0.0.2")
+ )
+)
+
+class ExampleBytecodePatch : BytecodePatch(
+ PatchMetadata(
+ "example-patch",
+ "ReVanced example patch",
+ "A demonstrative patch to feature the core features of the ReVanced patcher",
+ packageMetadata,
+ "0.0.1"
+ ),
+ setOf(
+ MethodSignature(
+ MethodSignatureMetadata(
+ "Example signature",
+ MethodMetadata(
+ "TestClass",
+ "main",
+ ),
+ PatternScanMethod.Fuzzy(1),
+ packageMetadata,
+ "The main method of TestClass",
+ "1.0.0"
+ ),
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.STATIC,
+ listOf("[L"),
+ listOf(
+ Opcode.SGET_OBJECT,
+ null, // Testing unknown opcodes.
+ Opcode.INVOKE_STATIC, // This is intentionally wrong to test the Fuzzy resolver.
+ Opcode.RETURN_VOID
+ ),
+ null
+ )
+ )
+) {
+ // 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 for the signature from the resolver cache
+ val result = signatures.first().result!!
+
+ // Get the implementation for the resolved method
+ val implementation = result.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.scanData.startIndex
+
+ implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
+
+ // Get the class in which the method matching our signature is defined in.
+ val mainClass = data.findClass {
+ it.type == result.definingClassProxy.immutableClass.type
+ }!!.resolve()
+
+ // Add a new method returning a string
+ mainClass.methods.add(
+ ImmutableMethod(
+ result.definingClassProxy.immutableClass.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
+ implementation.replaceInstruction(
+ 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!
+ // 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
+ """.trimIndent().toInstructions()
+ implementation.addInstructions(startIndex + 2, instructions)
+
+ // 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)
+ )
+ )
+ }
+}
diff --git a/src/test/kotlin/app/revanced/patcher/usage/ExampleResourcePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/ExampleResourcePatch.kt
new file mode 100644
index 0000000..db3b797
--- /dev/null
+++ b/src/test/kotlin/app/revanced/patcher/usage/ExampleResourcePatch.kt
@@ -0,0 +1,50 @@
+package app.revanced.patcher.usage
+
+import app.revanced.patcher.data.implementation.ResourceData
+import app.revanced.patcher.patch.implementation.ResourcePatch
+import app.revanced.patcher.patch.implementation.metadata.PatchMetadata
+import app.revanced.patcher.patch.implementation.misc.PatchResult
+import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
+import com.sun.org.apache.xerces.internal.dom.ElementImpl
+
+class ExampleResourcePatch : ResourcePatch(
+ PatchMetadata(
+ "example-patch",
+ "Example Resource Patch",
+ "Example demonstration of a resource patch.",
+ packageMetadata,
+ "0.0.1"
+ )
+) {
+ override fun execute(data: ResourceData): PatchResult {
+ val editor = data.getXmlEditor("AndroidManifest.xml")
+
+ // regular DomFileEditor
+ val element = editor
+ .file
+ .getElementsByTagName("application")
+ .item(0) as ElementImpl
+ element
+ .setAttribute(
+ "exampleAttribute",
+ "exampleValue"
+ )
+
+ // close the editor to write changes
+ editor.close()
+
+ // iterate through all available resources
+ data.forEach {
+ if (it.extension.lowercase() != "xml") return@forEach
+
+ data.replace(
+ it.path,
+ "\\ddip", // regex supported
+ "0dip",
+ true
+ )
+ }
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/app/revanced/patcher/util/TestUtil.kt b/src/test/kotlin/app/revanced/patcher/util/TestUtil.kt
deleted file mode 100644
index 6d891e1..0000000
--- a/src/test/kotlin/app/revanced/patcher/util/TestUtil.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package app.revanced.patcher.util
-
-import org.objectweb.asm.tree.AbstractInsnNode
-import org.objectweb.asm.tree.FieldInsnNode
-import org.objectweb.asm.tree.LdcInsnNode
-import kotlin.test.fail
-
-object TestUtil {
- fun assertNodeEqual(expected: T, actual: T) {
- val a = expected.nodeString()
- val b = actual.nodeString()
- if (a != b) {
- fail("expected: $a,\nactual: $b\n")
- }
- }
-
- private fun AbstractInsnNode.nodeString(): String {
- val sb = NodeStringBuilder()
- when (this) {
- // TODO(Sculas): Add more types
- is LdcInsnNode -> sb
- .addType("cst", cst)
- is FieldInsnNode -> sb
- .addType("owner", owner)
- .addType("name", name)
- .addType("desc", desc)
- }
- return "(${this::class.simpleName}): (type = $type, opcode = $opcode, $sb)"
- }
-}
-
-private class NodeStringBuilder {
- private val sb = StringBuilder()
-
- fun addType(name: String, value: Any): NodeStringBuilder {
- sb.append("$name = \"$value\", ")
- return this
- }
-
- override fun toString(): String {
- if (sb.isEmpty()) return ""
- val s = sb.toString()
- return s.substring(0 .. (s.length - 2).coerceAtLeast(0)) // remove the last ", "
- }
-}
diff --git a/src/test/resources/test1.jar b/src/test/resources/test1.jar
deleted file mode 100644
index 33dee5f..0000000
Binary files a/src/test/resources/test1.jar and /dev/null differ
diff --git a/src/test/resources/test2.jar b/src/test/resources/test2.jar
deleted file mode 100644
index 9df8df7..0000000
Binary files a/src/test/resources/test2.jar and /dev/null differ