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 {
|
dependencies {
|
||||||
implementation(kotlin("stdlib"))
|
implementation(kotlin("stdlib"))
|
||||||
|
|
||||||
|
api("org.apktool:apktool-lib:2.6.1")
|
||||||
api("app.revanced:multidexlib2:2.5.2.r2")
|
api("app.revanced:multidexlib2:2.5.2.r2")
|
||||||
api("org.smali:smali:2.5.2")
|
api("org.smali:smali:2.5.2")
|
||||||
|
|
||||||
@ -66,4 +67,4 @@ publishing {
|
|||||||
from(components["java"])
|
from(components["java"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,19 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.data.PatcherData
|
||||||
import app.revanced.patcher.patch.PatchMetadata
|
import app.revanced.patcher.data.base.Data
|
||||||
import app.revanced.patcher.patch.PatchResultSuccess
|
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.MethodSignature
|
||||||
import app.revanced.patcher.signature.resolver.SignatureResolver
|
import app.revanced.patcher.signature.resolver.SignatureResolver
|
||||||
import app.revanced.patcher.util.ListBackedSet
|
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.BasicDexFileNamer
|
||||||
import lanchon.multidexlib2.DexIO
|
import lanchon.multidexlib2.DexIO
|
||||||
import lanchon.multidexlib2.MultiDexIO
|
import lanchon.multidexlib2.MultiDexIO
|
||||||
@ -18,20 +26,46 @@ import java.io.File
|
|||||||
val NAMER = BasicDexFileNamer()
|
val NAMER = BasicDexFileNamer()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ReVanced Patcher.
|
* The ReVanced Patcher.
|
||||||
* @param input The input file (an apk or any other multi dex container).
|
* @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(
|
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 patcherData: PatcherData
|
||||||
private val opcodes: Opcodes
|
private val opcodes: Opcodes
|
||||||
private var signaturesResolved = false
|
private var signaturesResolved = false
|
||||||
|
private val androlib = Androlib()
|
||||||
|
|
||||||
init {
|
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
|
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) {
|
for (file in files) {
|
||||||
val dexFile = MultiDexIO.readDexFile(true, file, NAMER, null, null)
|
val dexFile = MultiDexIO.readDexFile(true, file, NAMER, null, null)
|
||||||
for (classDef in dexFile.classes) {
|
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 (e != null) {
|
||||||
if (throwOnDuplicates) {
|
if (throwOnDuplicates) {
|
||||||
throw Exception("Class ${classDef.type} has already been added to the patcher.")
|
throw Exception("Class ${classDef.type} has already been added to the patcher.")
|
||||||
}
|
}
|
||||||
val (_, idx) = e
|
val (_, idx) = e
|
||||||
if (allowedOverwrites.contains(classDef.type)) {
|
if (allowedOverwrites.contains(classDef.type)) {
|
||||||
patcherData.classes.internalClasses[idx] = classDef
|
patcherData.bytecodeData.classes.internalClasses[idx] = classDef
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
patcherData.classes.internalClasses.add(classDef)
|
patcherData.bytecodeData.classes.internalClasses.add(classDef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,8 +104,8 @@ class Patcher(
|
|||||||
fun save(): Map<String, MemoryDataStore> {
|
fun save(): Map<String, MemoryDataStore> {
|
||||||
val newDexFile = object : DexFile {
|
val newDexFile = object : DexFile {
|
||||||
override fun getClasses(): Set<ClassDef> {
|
override fun getClasses(): Set<ClassDef> {
|
||||||
patcherData.classes.applyProxies()
|
patcherData.bytecodeData.classes.applyProxies()
|
||||||
return ListBackedSet(patcherData.classes.internalClasses)
|
return ListBackedSet(patcherData.bytecodeData.classes.internalClasses)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOpcodes(): Opcodes {
|
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>()
|
val output = mutableMapOf<String, MemoryDataStore>()
|
||||||
MultiDexIO.writeDexFile(
|
MultiDexIO.writeDexFile(
|
||||||
true, -1, // core count
|
true, -1, // core count
|
||||||
@ -93,24 +134,25 @@ class Patcher(
|
|||||||
* Add a patch to the patcher.
|
* Add a patch to the patcher.
|
||||||
* @param patches The patches to add.
|
* @param patches The patches to add.
|
||||||
*/
|
*/
|
||||||
fun addPatches(patches: Iterable<Patch>) {
|
fun addPatches(patches: Iterable<Patch<Data>>) {
|
||||||
patcherData.patches.addAll(patches)
|
patcherData.patches.addAll(patches)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves all signatures.
|
* Resolves all signatures.
|
||||||
* @throws IllegalStateException if signatures have already been resolved.
|
|
||||||
*/
|
*/
|
||||||
fun resolveSignatures(): List<MethodSignature> {
|
fun resolveSignatures(): List<MethodSignature> {
|
||||||
if (signaturesResolved) {
|
val signatures = buildList {
|
||||||
throw IllegalStateException("Signatures have already been resolved.")
|
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 }
|
SignatureResolver(patcherData.bytecodeData.classes.internalClasses, signatures).resolve(patcherData)
|
||||||
|
|
||||||
if (signatures.isEmpty()) return emptyList()
|
|
||||||
|
|
||||||
SignatureResolver(patcherData.classes.internalClasses, signatures).resolve(patcherData)
|
|
||||||
signaturesResolved = true
|
signaturesResolved = true
|
||||||
return signatures
|
return signatures
|
||||||
}
|
}
|
||||||
@ -126,14 +168,24 @@ class Patcher(
|
|||||||
stopOnError: Boolean = false,
|
stopOnError: Boolean = false,
|
||||||
callback: (String) -> Unit = {}
|
callback: (String) -> Unit = {}
|
||||||
): Map<PatchMetadata, Result<PatchResultSuccess>> {
|
): Map<PatchMetadata, Result<PatchResultSuccess>> {
|
||||||
if (!signaturesResolved && patcherData.patches.isNotEmpty()) {
|
if (!signaturesResolved) {
|
||||||
resolveSignatures()
|
resolveSignatures()
|
||||||
}
|
}
|
||||||
return buildMap {
|
return buildMap {
|
||||||
for (patch in patcherData.patches) {
|
for (patch in patcherData.patches) {
|
||||||
|
val resourcePatch = patch is ResourcePatch
|
||||||
|
if (!patchResources && resourcePatch) continue
|
||||||
|
|
||||||
callback(patch.metadata.shortName)
|
callback(patch.metadata.shortName)
|
||||||
val result: Result<PatchResultSuccess> = try {
|
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()) {
|
if (pr.isSuccess()) {
|
||||||
Result.success(pr.success()!!)
|
Result.success(pr.success()!!)
|
||||||
} else {
|
} 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.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.proxy.ClassProxy
|
||||||
import app.revanced.patcher.signature.SignatureResolverResult
|
import app.revanced.patcher.signature.SignatureResolverResult
|
||||||
import app.revanced.patcher.util.ProxyBackedClassList
|
import app.revanced.patcher.util.ProxyBackedClassList
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
import org.jf.dexlib2.iface.Method
|
import org.jf.dexlib2.iface.Method
|
||||||
|
|
||||||
class PatcherData(
|
class BytecodeData(
|
||||||
internalClasses: MutableList<ClassDef>,
|
// 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)
|
val classes = ProxyBackedClassList(internalClasses)
|
||||||
internal val patches = mutableListOf<Patch>()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a class by a given class name
|
* Find a class by a given class name
|
||||||
@ -27,6 +31,7 @@ class PatcherData(
|
|||||||
fun findClass(predicate: (ClassDef) -> Boolean): ClassProxy? {
|
fun findClass(predicate: (ClassDef) -> Boolean): ClassProxy? {
|
||||||
// if we already proxied the class matching the predicate...
|
// if we already proxied the class matching the predicate...
|
||||||
for (patch in patches) {
|
for (patch in patches) {
|
||||||
|
if (patch !is BytecodePatch) continue
|
||||||
for (signature in patch.signatures) {
|
for (signature in patch.signatures) {
|
||||||
val result = signature.result
|
val result = signature.result
|
||||||
result ?: continue
|
result ?: continue
|
||||||
@ -34,7 +39,6 @@ class PatcherData(
|
|||||||
if (predicate(result.definingClassProxy.immutableClass)) return result.definingClassProxy // ...then return that proxy
|
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
|
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||||
return classes.find(predicate)?.let {
|
return classes.find(predicate)?.let {
|
||||||
proxy(it)
|
proxy(it)
|
||||||
@ -42,6 +46,7 @@ class PatcherData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MethodMap : LinkedHashMap<String, SignatureResolverResult>() {
|
class MethodMap : LinkedHashMap<String, SignatureResolverResult>() {
|
||||||
override fun get(key: String): SignatureResolverResult {
|
override fun get(key: String): SignatureResolverResult {
|
||||||
return super.get(key) ?: throw MethodNotFoundException("Method $key was not found in the method cache")
|
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
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun PatcherData.toMethodWalker(startMethod: Method): MethodWalker {
|
fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker {
|
||||||
return MethodWalker(this, startMethod)
|
return MethodWalker(this, startMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +77,7 @@ internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair
|
|||||||
return null
|
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 }
|
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
||||||
if (proxy == null) {
|
if (proxy == null) {
|
||||||
proxy = ClassProxy(classDef)
|
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
|
package app.revanced.patcher.methodWalker
|
||||||
|
|
||||||
import app.revanced.patcher.MethodNotFoundException
|
import app.revanced.patcher.data.implementation.BytecodeData
|
||||||
import app.revanced.patcher.PatcherData
|
import app.revanced.patcher.data.implementation.MethodNotFoundException
|
||||||
import app.revanced.patcher.extensions.softCompareTo
|
import app.revanced.patcher.extensions.softCompareTo
|
||||||
import app.revanced.patcher.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.proxy.mutableTypes.MutableMethod
|
||||||
import org.jf.dexlib2.Format
|
import org.jf.dexlib2.Format
|
||||||
@ -12,11 +12,11 @@ import org.jf.dexlib2.util.Preconditions
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a method from another method via instruction offsets.
|
* 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.
|
* @param currentMethod The method to start from.
|
||||||
*/
|
*/
|
||||||
class MethodWalker internal constructor(
|
class MethodWalker internal constructor(
|
||||||
private val patcherData: PatcherData,
|
private val bytecodeData: BytecodeData,
|
||||||
private var currentMethod: Method
|
private var currentMethod: Method
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
@ -40,7 +40,7 @@ class MethodWalker internal constructor(
|
|||||||
Preconditions.checkFormat(instruction.opcode, Format.Format35c)
|
Preconditions.checkFormat(instruction.opcode, Format.Format35c)
|
||||||
|
|
||||||
val newMethod = (instruction as Instruction35c).reference as MethodReference
|
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
|
val methods = if (walkMutable) proxy.resolve().methods else proxy.immutableClass.methods
|
||||||
currentMethod = methods.first { it ->
|
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.patch.base.Patch
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata about a [Patch].
|
* Metadata about a [Patch].
|
||||||
@ -43,4 +26,4 @@ data class PatchMetadata(
|
|||||||
data class PackageMetadata(
|
data class PackageMetadata(
|
||||||
val name: String,
|
val name: String,
|
||||||
val versions: Iterable<String>
|
val versions: Iterable<String>
|
||||||
)
|
)
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.patcher.patch
|
package app.revanced.patcher.patch.implementation.misc
|
||||||
|
|
||||||
interface PatchResult {
|
interface PatchResult {
|
||||||
fun error(): PatchResultError? {
|
fun error(): PatchResultError? {
|
@ -1,7 +1,7 @@
|
|||||||
package app.revanced.patcher.signature
|
package app.revanced.patcher.signature
|
||||||
|
|
||||||
import app.revanced.patcher.MethodNotFoundException
|
import app.revanced.patcher.data.implementation.MethodNotFoundException
|
||||||
import app.revanced.patcher.patch.PackageMetadata
|
import app.revanced.patcher.patch.implementation.metadata.PackageMetadata
|
||||||
import org.jf.dexlib2.Opcode
|
import org.jf.dexlib2.Opcode
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,7 +36,8 @@ class MethodSignature(
|
|||||||
var resolved = false
|
var resolved = false
|
||||||
try {
|
try {
|
||||||
resolved = result != null
|
resolved = result != null
|
||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
return resolved
|
return resolved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package app.revanced.patcher.signature.resolver
|
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.extensions.parametersEqual
|
||||||
import app.revanced.patcher.proxy
|
|
||||||
import app.revanced.patcher.proxy.ClassProxy
|
import app.revanced.patcher.proxy.ClassProxy
|
||||||
import app.revanced.patcher.signature.MethodSignature
|
import app.revanced.patcher.signature.MethodSignature
|
||||||
import app.revanced.patcher.signature.PatternScanMethod
|
import app.revanced.patcher.signature.PatternScanMethod
|
||||||
@ -26,7 +26,7 @@ internal class SignatureResolver(
|
|||||||
val patternScanData = compareSignatureToMethod(signature, method) ?: continue
|
val patternScanData = compareSignatureToMethod(signature, method) ?: continue
|
||||||
|
|
||||||
// create class proxy, in case a patch needs mutability
|
// create class proxy, in case a patch needs mutability
|
||||||
val classProxy = patcherData.proxy(classDef)
|
val classProxy = patcherData.bytecodeData.proxy(classDef)
|
||||||
signature.result = SignatureResolverResult(
|
signature.result = SignatureResolverResult(
|
||||||
classProxy,
|
classProxy,
|
||||||
patternScanData,
|
patternScanData,
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.signature.PatternScanMethod
|
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 org.junit.jupiter.api.Test
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@ -9,8 +10,14 @@ import kotlin.test.assertTrue
|
|||||||
internal class PatcherTest {
|
internal class PatcherTest {
|
||||||
@Test
|
@Test
|
||||||
fun testPatcher() {
|
fun testPatcher() {
|
||||||
val patcher = Patcher(File(PatcherTest::class.java.getResource("/test1.dex")!!.toURI()))
|
val patcher = Patcher(
|
||||||
patcher.addPatches(listOf(ExamplePatch()))
|
File(PatcherTest::class.java.getResource("/test1.dex")!!.toURI()),
|
||||||
|
"exampleCacheDirectory",
|
||||||
|
patchResources = true
|
||||||
|
)
|
||||||
|
|
||||||
|
patcher.addPatches(listOf(ExampleBytecodePatch(), ExampleResourcePatch()))
|
||||||
|
|
||||||
for (signature in patcher.resolveSignatures()) {
|
for (signature in patcher.resolveSignatures()) {
|
||||||
if (!signature.resolved) {
|
if (!signature.resolved) {
|
||||||
throw Exception("Signature ${signature.metadata.name} was not resolved!")
|
throw Exception("Signature ${signature.metadata.name} was not resolved!")
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package app.revanced.patcher.usage
|
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.addInstructions
|
||||||
import app.revanced.patcher.extensions.or
|
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.MutableField.Companion.toMutable
|
||||||
import app.revanced.patcher.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import app.revanced.patcher.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
import app.revanced.patcher.signature.MethodMetadata
|
import app.revanced.patcher.signature.MethodMetadata
|
||||||
@ -35,7 +39,7 @@ val packageMetadata = listOf(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
class ExamplePatch : Patch(
|
class ExampleBytecodePatch : BytecodePatch(
|
||||||
PatchMetadata(
|
PatchMetadata(
|
||||||
"example-patch",
|
"example-patch",
|
||||||
"ReVanced example patch",
|
"ReVanced example patch",
|
||||||
@ -71,7 +75,7 @@ class ExamplePatch : Patch(
|
|||||||
) {
|
) {
|
||||||
// This function will be executed by the patcher.
|
// This function will be executed by the patcher.
|
||||||
// You can treat it as a constructor
|
// 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
|
// Get the resolved method for the signature from the resolver cache
|
||||||
val result = signatures.first().result!!
|
val result = signatures.first().result!!
|
||||||
|
|
||||||
@ -86,7 +90,7 @@ class ExamplePatch : Patch(
|
|||||||
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
|
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
|
||||||
|
|
||||||
// Get the class in which the method matching our signature is defined in.
|
// 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
|
it.type == result.definingClassProxy.immutableClass.type
|
||||||
}!!.resolve()
|
}!!.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