mirror of
https://github.com/revanced/revanced-patcher.git
synced 2025-05-03 14:14:26 +02:00
add: resource patcher
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
parent
c459beb5f8
commit
99319e63da
@ -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")
|
||||
|
||||
|
@ -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<String, MemoryDataStore> {
|
||||
val newDexFile = object : DexFile {
|
||||
override fun getClasses(): Set<ClassDef> {
|
||||
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<String, MemoryDataStore>()
|
||||
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<Patch>) {
|
||||
fun addPatches(patches: Iterable<Patch<Data>>) {
|
||||
patcherData.patches.addAll(patches)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves all signatures.
|
||||
* @throws IllegalStateException if signatures have already been resolved.
|
||||
*/
|
||||
fun resolveSignatures(): List<MethodSignature> {
|
||||
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<PatchMetadata, Result<PatchResultSuccess>> {
|
||||
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<PatchResultSuccess> = 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 {
|
||||
|
18
src/main/kotlin/app/revanced/patcher/data/PatcherData.kt
Normal file
18
src/main/kotlin/app/revanced/patcher/data/PatcherData.kt
Normal file
@ -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<ClassDef>,
|
||||
val resourceCacheDirectory: String
|
||||
) {
|
||||
internal val patches = mutableListOf<Patch<Data>>()
|
||||
|
||||
internal val bytecodeData = BytecodeData(patches, internalClasses)
|
||||
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
||||
}
|
9
src/main/kotlin/app/revanced/patcher/data/base/Data.kt
Normal file
9
src/main/kotlin/app/revanced/patcher/data/base/Data.kt
Normal file
@ -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
|
@ -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<ClassDef>,
|
||||
) {
|
||||
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<Patch<Data>>,
|
||||
internalClasses: MutableList<ClassDef>
|
||||
) : Data {
|
||||
val classes = ProxyBackedClassList(internalClasses)
|
||||
internal val patches = mutableListOf<Patch>()
|
||||
|
||||
/**
|
||||
* 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<String, SignatureResolverResult>() {
|
||||
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 <reified T> Iterable<T>.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 <T> Iterable<T>.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)
|
@ -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()))
|
||||
}
|
@ -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 ->
|
||||
|
22
src/main/kotlin/app/revanced/patcher/patch/base/Patch.kt
Normal file
22
src/main/kotlin/app/revanced/patcher/patch/base/Patch.kt
Normal file
@ -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<out T : Data>(
|
||||
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
|
||||
|
||||
}
|
@ -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<MethodSignature>
|
||||
) : Patch<BytecodeData>(metadata)
|
@ -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<ResourceData>(metadata)
|
@ -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<MethodSignature>
|
||||
) {
|
||||
|
||||
/**
|
||||
* 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].
|
@ -1,4 +1,4 @@
|
||||
package app.revanced.patcher.patch
|
||||
package app.revanced.patcher.patch.implementation.misc
|
||||
|
||||
interface PatchResult {
|
||||
fun error(): PatchResultError? {
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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!")
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user