diff --git a/build.gradle.kts b/build.gradle.kts index e6541ce..b10681a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ repositories { dependencies { implementation(kotlin("stdlib")) + api("org.apktool:apktool-lib:2.6.1") api("app.revanced:multidexlib2:2.5.2.r2") api("org.smali:smali:2.5.2") @@ -66,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 dd36ec7..e4fe88e 100644 --- a/src/main/kotlin/app/revanced/patcher/Patcher.kt +++ b/src/main/kotlin/app/revanced/patcher/Patcher.kt @@ -1,11 +1,19 @@ package app.revanced.patcher -import app.revanced.patcher.patch.Patch -import app.revanced.patcher.patch.PatchMetadata -import app.revanced.patcher.patch.PatchResultSuccess +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.ApkDecoder +import brut.directory.ExtFile import lanchon.multidexlib2.BasicDexFileNamer import lanchon.multidexlib2.DexIO import lanchon.multidexlib2.MultiDexIO @@ -18,20 +26,46 @@ import java.io.File val NAMER = BasicDexFileNamer() /** - * ReVanced Patcher. - * @param input The input file (an apk or any other multi dex container). + * 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( - input: File, + 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 ) { + val packageVersion: String + val packageName: String + private val patcherData: PatcherData private val opcodes: Opcodes private var signaturesResolved = false + private val androlib = Androlib() init { - val dexFile = MultiDexIO.readDexFile(true, input, NAMER, null, null) + // FIXME: only use androlib instead of ApkDecoder which is currently a temporal solution + val decoder = ApkDecoder(androlib) + + decoder.setApkFile(inputFile) + decoder.setDecodeSources(ApkDecoder.DECODE_SOURCES_NONE) + decoder.setForceDelete(true) + // decode resources to cache directory + decoder.setOutDir(File(resourceCacheDirectory)) + decoder.decode() + + // get package info + packageName = decoder.resTable.packageOriginal + packageVersion = decoder.resTable.versionInfo.versionName + + // read dex files + val dexFile = MultiDexIO.readDexFile(true, inputFile, NAMER, null, null) opcodes = dexFile.opcodes - patcherData = PatcherData(dexFile.classes.toMutableList()) + + // save to patcher data + patcherData = PatcherData(dexFile.classes.toMutableList(), resourceCacheDirectory) } /** @@ -48,18 +82,18 @@ class Patcher( for (file in files) { val dexFile = MultiDexIO.readDexFile(true, file, NAMER, null, null) for (classDef in dexFile.classes) { - val e = patcherData.classes.internalClasses.findIndexed { it.type == classDef.type } + 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.classes.internalClasses[idx] = classDef + patcherData.bytecodeData.classes.internalClasses[idx] = classDef } continue } - patcherData.classes.internalClasses.add(classDef) + patcherData.bytecodeData.classes.internalClasses.add(classDef) } } } @@ -70,8 +104,8 @@ class Patcher( fun save(): Map { val newDexFile = object : DexFile { override fun getClasses(): Set { - patcherData.classes.applyProxies() - return ListBackedSet(patcherData.classes.internalClasses) + patcherData.bytecodeData.classes.applyProxies() + return ListBackedSet(patcherData.bytecodeData.classes.internalClasses) } override fun getOpcodes(): Opcodes { @@ -79,6 +113,13 @@ class Patcher( } } + // build modified resources + if (patchResources) { + val extDir = ExtFile(resourceCacheDirectory) + androlib.buildResources(extDir, androlib.readMetaFile(extDir).usesFramework) + } + + // write dex modified files val output = mutableMapOf() MultiDexIO.writeDexFile( true, -1, // core count @@ -93,24 +134,25 @@ class Patcher( * Add a patch to the patcher. * @param patches The patches to add. */ - fun addPatches(patches: Iterable) { + fun addPatches(patches: Iterable>) { patcherData.patches.addAll(patches) } /** * Resolves all signatures. - * @throws IllegalStateException if signatures have already been resolved. */ fun resolveSignatures(): List { - if (signaturesResolved) { - throw IllegalStateException("Signatures have already been resolved.") + val signatures = buildList { + for (patch in patcherData.patches) { + if (patch !is BytecodePatch) continue + this.addAll(patch.signatures) + } + } + if (signatures.isEmpty()) { + return emptyList() } - val signatures = patcherData.patches.flatMap { it.signatures } - - if (signatures.isEmpty()) return emptyList() - - SignatureResolver(patcherData.classes.internalClasses, signatures).resolve(patcherData) + SignatureResolver(patcherData.bytecodeData.classes.internalClasses, signatures).resolve(patcherData) signaturesResolved = true return signatures } @@ -126,14 +168,24 @@ class Patcher( stopOnError: Boolean = false, callback: (String) -> Unit = {} ): Map> { - if (!signaturesResolved && patcherData.patches.isNotEmpty()) { + 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 pr = patch.execute(patcherData) + val data = if (resourcePatch) { + patcherData.resourceData + } else { + patcherData.bytecodeData + } + + val pr = patch.execute(data) + if (pr.isSuccess()) { Result.success(pr.success()!!) } else { 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/PatcherData.kt b/src/main/kotlin/app/revanced/patcher/data/implementation/BytecodeData.kt similarity index 79% rename from src/main/kotlin/app/revanced/patcher/PatcherData.kt rename to src/main/kotlin/app/revanced/patcher/data/implementation/BytecodeData.kt index 34bfe45..2c3e09a 100644 --- a/src/main/kotlin/app/revanced/patcher/PatcherData.kt +++ b/src/main/kotlin/app/revanced/patcher/data/implementation/BytecodeData.kt @@ -1,18 +1,22 @@ -package app.revanced.patcher +package app.revanced.patcher.data.implementation +import app.revanced.patcher.data.base.Data import app.revanced.patcher.methodWalker.MethodWalker -import app.revanced.patcher.patch.Patch +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 PatcherData( - internalClasses: MutableList, -) { +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) - internal val patches = mutableListOf() /** * Find a class by a given class name @@ -27,6 +31,7 @@ class PatcherData( 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 @@ -34,7 +39,6 @@ class PatcherData( 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) @@ -42,6 +46,7 @@ class PatcherData( } } + class MethodMap : LinkedHashMap() { override fun get(key: String): SignatureResolverResult { return super.get(key) ?: throw MethodNotFoundException("Method $key was not found in the method cache") @@ -59,7 +64,7 @@ internal inline fun Iterable.find(predicate: (T) -> Boolean): T? return null } -fun PatcherData.toMethodWalker(startMethod: Method): MethodWalker { +fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker { return MethodWalker(this, startMethod) } @@ -72,7 +77,7 @@ internal inline fun Iterable.findIndexed(predicate: (T) -> Boolean): Pair return null } -fun PatcherData.proxy(classDef: ClassDef): ClassProxy { +fun BytecodeData.proxy(classDef: ClassDef): ClassProxy { var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type } if (proxy == null) { proxy = ClassProxy(classDef) 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/methodWalker/MethodWalker.kt b/src/main/kotlin/app/revanced/patcher/methodWalker/MethodWalker.kt index 2a23aba..04ed0e7 100644 --- a/src/main/kotlin/app/revanced/patcher/methodWalker/MethodWalker.kt +++ b/src/main/kotlin/app/revanced/patcher/methodWalker/MethodWalker.kt @@ -1,7 +1,7 @@ package app.revanced.patcher.methodWalker -import app.revanced.patcher.MethodNotFoundException -import app.revanced.patcher.PatcherData +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 @@ -12,11 +12,11 @@ import org.jf.dexlib2.util.Preconditions /** * Find a method from another method via instruction offsets. - * @param patcherData The patcherData to use when resolving the next method reference. + * @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 patcherData: PatcherData, + private val bytecodeData: BytecodeData, private var currentMethod: Method ) { /** @@ -40,7 +40,7 @@ class MethodWalker internal constructor( Preconditions.checkFormat(instruction.opcode, Format.Format35c) val newMethod = (instruction as Instruction35c).reference as MethodReference - val proxy = patcherData.findClass(newMethod.definingClass)!! + val proxy = bytecodeData.findClass(newMethod.definingClass)!! val methods = if (walkMutable) proxy.resolve().methods else proxy.immutableClass.methods currentMethod = methods.first { it -> 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/Patch.kt b/src/main/kotlin/app/revanced/patcher/patch/implementation/metadata/PatchMetadata.kt similarity index 57% rename from src/main/kotlin/app/revanced/patcher/patch/Patch.kt rename to src/main/kotlin/app/revanced/patcher/patch/implementation/metadata/PatchMetadata.kt index c87bbc3..cfa8f83 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/Patch.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/implementation/metadata/PatchMetadata.kt @@ -1,23 +1,6 @@ -package app.revanced.patcher.patch +package app.revanced.patcher.patch.implementation.metadata -import app.revanced.patcher.PatcherData -import app.revanced.patcher.signature.MethodSignature - -/** - * Patch for the Patcher. - * @param metadata [PatchMetadata] for the patch. - * @param signatures A list of [MethodSignature] this patch relies on. - */ -abstract class Patch( - val metadata: PatchMetadata, - val signatures: Iterable -) { - - /** - * The main function of the [Patch] which the patcher will call. - */ - abstract fun execute(patcherData: PatcherData): PatchResult -} +import app.revanced.patcher.patch.base.Patch /** * Metadata about a [Patch]. @@ -43,4 +26,4 @@ data class PatchMetadata( 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/signature/MethodSignature.kt b/src/main/kotlin/app/revanced/patcher/signature/MethodSignature.kt index 7c88f9d..1e7b13a 100644 --- a/src/main/kotlin/app/revanced/patcher/signature/MethodSignature.kt +++ b/src/main/kotlin/app/revanced/patcher/signature/MethodSignature.kt @@ -1,7 +1,7 @@ package app.revanced.patcher.signature -import app.revanced.patcher.MethodNotFoundException -import app.revanced.patcher.patch.PackageMetadata +import app.revanced.patcher.data.implementation.MethodNotFoundException +import app.revanced.patcher.patch.implementation.metadata.PackageMetadata import org.jf.dexlib2.Opcode /** @@ -36,7 +36,8 @@ class MethodSignature( var resolved = false try { resolved = result != null - } catch (_: Exception) {} + } catch (_: Exception) { + } return resolved } } diff --git a/src/main/kotlin/app/revanced/patcher/signature/resolver/SignatureResolver.kt b/src/main/kotlin/app/revanced/patcher/signature/resolver/SignatureResolver.kt index 69e5c77..c7d2d0d 100644 --- a/src/main/kotlin/app/revanced/patcher/signature/resolver/SignatureResolver.kt +++ b/src/main/kotlin/app/revanced/patcher/signature/resolver/SignatureResolver.kt @@ -1,8 +1,8 @@ package app.revanced.patcher.signature.resolver -import app.revanced.patcher.PatcherData +import app.revanced.patcher.data.PatcherData +import app.revanced.patcher.data.implementation.proxy import app.revanced.patcher.extensions.parametersEqual -import app.revanced.patcher.proxy import app.revanced.patcher.proxy.ClassProxy import app.revanced.patcher.signature.MethodSignature import app.revanced.patcher.signature.PatternScanMethod @@ -26,7 +26,7 @@ internal class SignatureResolver( val patternScanData = compareSignatureToMethod(signature, method) ?: continue // create class proxy, in case a patch needs mutability - val classProxy = patcherData.proxy(classDef) + val classProxy = patcherData.bytecodeData.proxy(classDef) signature.result = SignatureResolverResult( classProxy, patternScanData, diff --git a/src/test/kotlin/app/revanced/patcher/PatcherTest.kt b/src/test/kotlin/app/revanced/patcher/PatcherTest.kt index 1632e6d..cc5d181 100644 --- a/src/test/kotlin/app/revanced/patcher/PatcherTest.kt +++ b/src/test/kotlin/app/revanced/patcher/PatcherTest.kt @@ -1,7 +1,8 @@ package app.revanced.patcher import app.revanced.patcher.signature.PatternScanMethod -import app.revanced.patcher.usage.ExamplePatch +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 @@ -9,8 +10,14 @@ import kotlin.test.assertTrue internal class PatcherTest { @Test fun testPatcher() { - val patcher = Patcher(File(PatcherTest::class.java.getResource("/test1.dex")!!.toURI())) - patcher.addPatches(listOf(ExamplePatch())) + val patcher = Patcher( + File(PatcherTest::class.java.getResource("/test1.dex")!!.toURI()), + "exampleCacheDirectory", + patchResources = true + ) + + patcher.addPatches(listOf(ExampleBytecodePatch(), ExampleResourcePatch())) + for (signature in patcher.resolveSignatures()) { if (!signature.resolved) { throw Exception("Signature ${signature.metadata.name} was not resolved!") diff --git a/src/test/kotlin/app/revanced/patcher/usage/ExamplePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt similarity index 93% rename from src/test/kotlin/app/revanced/patcher/usage/ExamplePatch.kt rename to src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt index 4451c97..18745c5 100644 --- a/src/test/kotlin/app/revanced/patcher/usage/ExamplePatch.kt +++ b/src/test/kotlin/app/revanced/patcher/usage/ExampleBytecodePatch.kt @@ -1,9 +1,13 @@ package app.revanced.patcher.usage -import app.revanced.patcher.PatcherData +import app.revanced.patcher.data.implementation.BytecodeData import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.or -import app.revanced.patcher.patch.* +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 @@ -35,7 +39,7 @@ val packageMetadata = listOf( ) ) -class ExamplePatch : Patch( +class ExampleBytecodePatch : BytecodePatch( PatchMetadata( "example-patch", "ReVanced example patch", @@ -71,7 +75,7 @@ class ExamplePatch : Patch( ) { // This function will be executed by the patcher. // You can treat it as a constructor - override fun execute(patcherData: PatcherData): PatchResult { + override fun execute(data: BytecodeData): PatchResult { // Get the resolved method for the signature from the resolver cache val result = signatures.first().result!! @@ -86,7 +90,7 @@ class ExamplePatch : Patch( implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.") // Get the class in which the method matching our signature is defined in. - val mainClass = patcherData.findClass { + val mainClass = data.findClass { it.type == result.definingClassProxy.immutableClass.type }!!.resolve() 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