You've already forked revanced-patcher
mirror of
https://github.com/revanced/revanced-patcher
synced 2025-10-01 11:30:50 +02:00
Compare commits
13 Commits
v1.0.0-dev
...
v1.0.0-dev
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b369a30dd5 | ||
![]() |
8442991290 | ||
![]() |
a06c0db6a7 | ||
![]() |
3f0c740200 | ||
![]() |
545c5c144d | ||
![]() |
0fa529fcdf | ||
![]() |
7573db2575 | ||
![]() |
70ca184cf9 | ||
![]() |
72f16b7785 | ||
![]() |
fc03639b26 | ||
![]() |
88a85f94e7 | ||
![]() |
45a167e785 | ||
![]() |
699d8abf59 |
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -1,5 +1,6 @@
|
|||||||
name: Release
|
name: Release
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,3 +1,37 @@
|
|||||||
|
# [1.0.0-dev.17](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.16...v1.0.0-dev.17) (2022-05-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* patch dependencies annotation and `PatcherOptions` ([8442991](https://github.com/revanced/revanced-patcher/commit/84429912900872405b44804943357dda8430a550))
|
||||||
|
|
||||||
|
# [1.0.0-dev.16](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.15...v1.0.0-dev.16) (2022-05-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* `JarPatchBundle` loading non-class files to class loader ([3f0c740](https://github.com/revanced/revanced-patcher/commit/3f0c740200dd91a060426638c2f8f516938b4c53))
|
||||||
|
* remove dependency to fork of Apktool ([0fa529f](https://github.com/revanced/revanced-patcher/commit/0fa529fcdf9a7b5ea9a361b9f9f32f3f3fce009f))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* migrate to `DexPatchBundle` and `JarPatchBundle` ([7573db2](https://github.com/revanced/revanced-patcher/commit/7573db25757de89824af4f3aea167e500120eabb))
|
||||||
|
|
||||||
|
# [1.0.0-dev.15](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.14...v1.0.0-dev.15) (2022-05-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* utility functions to get metadata of patch & sigs ([72f16b7](https://github.com/revanced/revanced-patcher/commit/72f16b778587c28d8f8e91da502f197e7dc35d6d))
|
||||||
|
|
||||||
|
# [1.0.0-dev.14](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.13...v1.0.0-dev.14) (2022-05-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* reformat (trigger release) ([45a167e](https://github.com/revanced/revanced-patcher/commit/45a167e7856da0306f796953775c7b7543d9bec0))
|
||||||
|
|
||||||
# [1.0.0-dev.13](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.12...v1.0.0-dev.13) (2022-05-24)
|
# [1.0.0-dev.13](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.12...v1.0.0-dev.13) (2022-05-24)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -15,22 +15,24 @@ repositories {
|
|||||||
// Instead, you should set them in:
|
// Instead, you should set them in:
|
||||||
// Windows: %homepath%\.gradle\gradle.properties
|
// Windows: %homepath%\.gradle\gradle.properties
|
||||||
// Linux: ~/.gradle/gradle.properties
|
// Linux: ~/.gradle/gradle.properties
|
||||||
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") // DO NOT CHANGE!
|
username =
|
||||||
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") // DO NOT CHANGE!
|
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 {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.6.21")
|
implementation(kotlin("stdlib"))
|
||||||
|
|
||||||
api("xpp3:xpp3:1.1.4c")
|
api("xpp3:xpp3:1.1.4c")
|
||||||
api("org.apktool:apktool-lib:2.6.1")
|
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")
|
||||||
|
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test:1.6.21")
|
testImplementation(kotlin("test"))
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.21")
|
implementation(kotlin("reflect"))
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 1.0.0-dev.13
|
version = 1.0.0-dev.17
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@@ -1,15 +1,17 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.annotation.Name
|
|
||||||
import app.revanced.patcher.data.PatcherData
|
import app.revanced.patcher.data.PatcherData
|
||||||
import app.revanced.patcher.data.base.Data
|
import app.revanced.patcher.data.base.Data
|
||||||
import app.revanced.patcher.data.implementation.findIndexed
|
import app.revanced.patcher.data.implementation.findIndexed
|
||||||
import app.revanced.patcher.extensions.findAnnotationRecursively
|
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
|
import app.revanced.patcher.extensions.nullOutputStream
|
||||||
import app.revanced.patcher.patch.base.Patch
|
import app.revanced.patcher.patch.base.Patch
|
||||||
import app.revanced.patcher.patch.implementation.BytecodePatch
|
import app.revanced.patcher.patch.implementation.BytecodePatch
|
||||||
import app.revanced.patcher.patch.implementation.ResourcePatch
|
import app.revanced.patcher.patch.implementation.ResourcePatch
|
||||||
|
import app.revanced.patcher.patch.implementation.misc.PatchResult
|
||||||
|
import app.revanced.patcher.patch.implementation.misc.PatchResultError
|
||||||
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
|
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
|
||||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
|
||||||
import app.revanced.patcher.signature.implementation.method.resolver.MethodSignatureResolver
|
import app.revanced.patcher.signature.implementation.method.resolver.MethodSignatureResolver
|
||||||
import app.revanced.patcher.util.ListBackedSet
|
import app.revanced.patcher.util.ListBackedSet
|
||||||
import brut.androlib.Androlib
|
import brut.androlib.Androlib
|
||||||
@@ -28,20 +30,15 @@ import org.jf.dexlib2.iface.ClassDef
|
|||||||
import org.jf.dexlib2.iface.DexFile
|
import org.jf.dexlib2.iface.DexFile
|
||||||
import org.jf.dexlib2.writer.io.MemoryDataStore
|
import org.jf.dexlib2.writer.io.MemoryDataStore
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
val NAMER = BasicDexFileNamer()
|
val NAMER = BasicDexFileNamer()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ReVanced Patcher.
|
* The ReVanced Patcher.
|
||||||
* @param inputFile The input file (usually an apk file).
|
* @param options The options for the patcher.
|
||||||
* @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(
|
||||||
inputFile: File,
|
private val options: PatcherOptions
|
||||||
// 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 packageVersion: String
|
||||||
val packageName: String
|
val packageName: String
|
||||||
@@ -49,12 +46,10 @@ class Patcher(
|
|||||||
private lateinit var usesFramework: UsesFramework
|
private lateinit var usesFramework: UsesFramework
|
||||||
private val patcherData: PatcherData
|
private val patcherData: PatcherData
|
||||||
private val opcodes: Opcodes
|
private val opcodes: Opcodes
|
||||||
private var signaturesResolved = false
|
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val extFileInput = ExtFile(inputFile)
|
val extFileInput = ExtFile(options.inputFile)
|
||||||
val outDir = File(resourceCacheDirectory)
|
val outDir = File(options.resourceCacheDirectory)
|
||||||
|
|
||||||
if (outDir.exists()) outDir.deleteRecursively()
|
if (outDir.exists()) outDir.deleteRecursively()
|
||||||
outDir.mkdir()
|
outDir.mkdir()
|
||||||
@@ -63,7 +58,7 @@ class Patcher(
|
|||||||
val androlib = Androlib()
|
val androlib = Androlib()
|
||||||
val resourceTable = androlib.getResTable(extFileInput, true)
|
val resourceTable = androlib.getResTable(extFileInput, true)
|
||||||
|
|
||||||
if (patchResources) {
|
if (options.patchResources) {
|
||||||
// 1. decode resources to cache directory
|
// 1. decode resources to cache directory
|
||||||
androlib.decodeManifestWithResources(extFileInput, outDir, resourceTable)
|
androlib.decodeManifestWithResources(extFileInput, outDir, resourceTable)
|
||||||
androlib.decodeResourcesFull(extFileInput, outDir, resourceTable)
|
androlib.decodeResourcesFull(extFileInput, outDir, resourceTable)
|
||||||
@@ -85,7 +80,7 @@ class Patcher(
|
|||||||
XmlPullStreamDecoder(
|
XmlPullStreamDecoder(
|
||||||
axmlParser, AndrolibResources().resXmlSerializer
|
axmlParser, AndrolibResources().resXmlSerializer
|
||||||
).decodeManifest(
|
).decodeManifest(
|
||||||
extFileInput.directory.getFileInput("AndroidManifest.xml"), OutputStream.nullOutputStream()
|
extFileInput.directory.getFileInput("AndroidManifest.xml"), nullOutputStream
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,11 +88,11 @@ class Patcher(
|
|||||||
packageVersion = resourceTable.versionInfo.versionName
|
packageVersion = resourceTable.versionInfo.versionName
|
||||||
packageName = resourceTable.currentResPackage.name
|
packageName = resourceTable.currentResPackage.name
|
||||||
// read dex files
|
// read dex files
|
||||||
val dexFile = MultiDexIO.readDexFile(true, inputFile, NAMER, null, null)
|
val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
|
||||||
opcodes = dexFile.opcodes
|
opcodes = dexFile.opcodes
|
||||||
|
|
||||||
// save to patcher data
|
// save to patcher data
|
||||||
patcherData = PatcherData(dexFile.classes.toMutableList(), resourceCacheDirectory)
|
patcherData = PatcherData(dexFile.classes.toMutableList(), options.resourceCacheDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -144,8 +139,8 @@ class Patcher(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// build modified resources
|
// build modified resources
|
||||||
if (patchResources) {
|
if (options.patchResources) {
|
||||||
val extDir = ExtFile(resourceCacheDirectory)
|
val extDir = ExtFile(options.resourceCacheDirectory)
|
||||||
|
|
||||||
// TODO: figure out why a new instance of Androlib is necessary here
|
// TODO: figure out why a new instance of Androlib is necessary here
|
||||||
Androlib().buildResources(extDir, usesFramework)
|
Androlib().buildResources(extDir, usesFramework)
|
||||||
@@ -161,33 +156,62 @@ class Patcher(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a patch to the patcher.
|
* Add [Patch]es to the patcher.
|
||||||
* @param patches The patches to add.
|
* @param patches [Patch]es The patches to add.
|
||||||
*/
|
*/
|
||||||
fun addPatches(patches: Iterable<Patch<Data>>) {
|
fun addPatches(patches: Iterable<Class<out Patch<Data>>>) {
|
||||||
patcherData.patches.addAll(patches)
|
patcherData.patches.addAll(patches)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves all signatures.
|
* Apply a [patch] and its dependencies recursively.
|
||||||
|
* @param patch The [patch] to apply.
|
||||||
|
* @param appliedPatches A list of [patch] names, to prevent applying [patch]es twice.
|
||||||
|
* @return The result of executing the [patch].
|
||||||
*/
|
*/
|
||||||
fun resolveSignatures(): List<MethodSignature> {
|
private fun applyPatch(
|
||||||
val signatures = buildList {
|
patch: Class<out Patch<Data>>, appliedPatches: MutableList<String>
|
||||||
for (patch in patcherData.patches) {
|
): PatchResult {
|
||||||
if (patch !is BytecodePatch) continue
|
val patchName = patch.patchName
|
||||||
this.addAll(patch.signatures)
|
|
||||||
}
|
// if the patch has already applied silently skip it
|
||||||
}
|
if (appliedPatches.contains(patchName)) return PatchResultSuccess()
|
||||||
if (signatures.isEmpty()) {
|
appliedPatches.add(patchName)
|
||||||
return emptyList()
|
|
||||||
|
// recursively apply all dependency patches
|
||||||
|
patch.dependencies?.forEach {
|
||||||
|
val patchDependency = it.java
|
||||||
|
|
||||||
|
val result = applyPatch(patchDependency, appliedPatches)
|
||||||
|
if (result.isSuccess()) return@forEach
|
||||||
|
|
||||||
|
val errorMessage = result.error()!!.message
|
||||||
|
return PatchResultError("$patchName depends on ${patchDependency.patchName} but the following error was raised: $errorMessage")
|
||||||
}
|
}
|
||||||
|
|
||||||
MethodSignatureResolver(patcherData.bytecodeData.classes.internalClasses, signatures).resolve(patcherData)
|
val patchInstance = patch.getDeclaredConstructor().newInstance()
|
||||||
signaturesResolved = true
|
|
||||||
return signatures
|
// if the current patch is a resource patch but resource patching is disabled, return an error
|
||||||
|
val isResourcePatch = patchInstance is ResourcePatch
|
||||||
|
if (!options.patchResources && isResourcePatch) return PatchResultError("$patchName is a resource patch, but resource patching is disabled.")
|
||||||
|
|
||||||
|
// TODO: find a solution for this
|
||||||
|
val data = if (isResourcePatch) {
|
||||||
|
patcherData.resourceData
|
||||||
|
} else {
|
||||||
|
MethodSignatureResolver(
|
||||||
|
patcherData.bytecodeData.classes.internalClasses, (patchInstance as BytecodePatch).signatures
|
||||||
|
).resolve(patcherData)
|
||||||
|
patcherData.bytecodeData
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
patchInstance.execute(data)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
PatchResultError(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply patches loaded into the patcher.
|
* Apply patches loaded into the patcher.
|
||||||
* @param stopOnError If true, the patches will stop on the first error.
|
* @param stopOnError If true, the patches will stop on the first error.
|
||||||
@@ -198,43 +222,22 @@ class Patcher(
|
|||||||
fun applyPatches(
|
fun applyPatches(
|
||||||
stopOnError: Boolean = false, callback: (String) -> Unit = {}
|
stopOnError: Boolean = false, callback: (String) -> Unit = {}
|
||||||
): Map<String, Result<PatchResultSuccess>> {
|
): Map<String, Result<PatchResultSuccess>> {
|
||||||
if (!signaturesResolved) {
|
val appliedPatches = mutableListOf<String>()
|
||||||
resolveSignatures()
|
|
||||||
}
|
|
||||||
return buildMap {
|
return buildMap {
|
||||||
for (patch in patcherData.patches) {
|
for (patch in patcherData.patches) {
|
||||||
val resourcePatch = patch is ResourcePatch
|
val result = applyPatch(patch, appliedPatches)
|
||||||
if (!patchResources && resourcePatch) continue
|
|
||||||
|
|
||||||
val patchNameAnnotation = patch::class.java.findAnnotationRecursively(Name::class.java)
|
val name = patch.patchName
|
||||||
|
callback(name)
|
||||||
|
|
||||||
patchNameAnnotation?.let {
|
this[name] = if (result.isSuccess()) {
|
||||||
callback(it.name)
|
Result.success(result.success()!!)
|
||||||
|
} else {
|
||||||
|
Result.failure(result.error()!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
val result: Result<PatchResultSuccess> = try {
|
if (stopOnError && result.isError()) break
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
patchNameAnnotation?.let {
|
|
||||||
this[patchNameAnnotation.name] = result
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.isFailure && stopOnError) break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
src/main/kotlin/app/revanced/patcher/PatcherOptions.kt
Normal file
15
src/main/kotlin/app/revanced/patcher/PatcherOptions.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
data class PatcherOptions(
|
||||||
|
internal val inputFile: File,
|
||||||
|
// TODO: maybe a file system in memory is better. Could cause high memory usage.
|
||||||
|
internal val resourceCacheDirectory: String,
|
||||||
|
internal val patchResources: Boolean = false
|
||||||
|
)
|
@@ -11,8 +11,8 @@ internal data class PatcherData(
|
|||||||
val internalClasses: MutableList<ClassDef>,
|
val internalClasses: MutableList<ClassDef>,
|
||||||
val resourceCacheDirectory: String
|
val resourceCacheDirectory: String
|
||||||
) {
|
) {
|
||||||
internal val patches = mutableListOf<Patch<Data>>()
|
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
||||||
|
|
||||||
internal val bytecodeData = BytecodeData(patches, internalClasses)
|
internal val bytecodeData = BytecodeData(internalClasses)
|
||||||
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
||||||
}
|
}
|
@@ -1,55 +1,33 @@
|
|||||||
package app.revanced.patcher.data.implementation
|
package app.revanced.patcher.data.implementation
|
||||||
|
|
||||||
import app.revanced.patcher.data.base.Data
|
import app.revanced.patcher.data.base.Data
|
||||||
import app.revanced.patcher.patch.base.Patch
|
|
||||||
import app.revanced.patcher.patch.implementation.BytecodePatch
|
|
||||||
import app.revanced.patcher.signature.implementation.method.resolver.SignatureResolverResult
|
|
||||||
import app.revanced.patcher.util.ProxyBackedClassList
|
import app.revanced.patcher.util.ProxyBackedClassList
|
||||||
import app.revanced.patcher.util.method.MethodWalker
|
import app.revanced.patcher.util.method.MethodWalker
|
||||||
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 BytecodeData(
|
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>
|
internalClasses: MutableList<ClassDef>
|
||||||
) : Data {
|
) : Data {
|
||||||
val classes = ProxyBackedClassList(internalClasses)
|
val classes = ProxyBackedClassList(internalClasses)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a class by a given class name
|
* Find a class by a given class name.
|
||||||
* @return A proxy for the first class that matches the class name
|
* @param className The name of the class.
|
||||||
|
* @return A proxy for the first class that matches the class name.
|
||||||
*/
|
*/
|
||||||
fun findClass(className: String) = findClass { it.type.contains(className) }
|
fun findClass(className: String) = findClass { it.type.contains(className) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a class by a given predicate
|
* Find a class by a given predicate.
|
||||||
* @return A proxy for the first class that matches the predicate
|
* @param predicate A predicate to match the class.
|
||||||
|
* @return A proxy for the first class that matches the predicate.
|
||||||
*/
|
*/
|
||||||
fun findClass(predicate: (ClassDef) -> Boolean): app.revanced.patcher.util.proxy.ClassProxy? {
|
fun findClass(predicate: (ClassDef) -> Boolean) =
|
||||||
// if we already proxied the class matching the predicate...
|
// if we already proxied the class matching the predicate...
|
||||||
for (patch in patches) {
|
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
||||||
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
|
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||||
return classes.find(predicate)?.let {
|
classes.find(predicate)?.let { proxy(it) }
|
||||||
proxy(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class MethodNotFoundException(s: String) : Exception(s)
|
internal class MethodNotFoundException(s: String) : Exception(s)
|
||||||
@@ -63,6 +41,11 @@ internal inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T?
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [MethodWalker] instance for the current [BytecodeData].
|
||||||
|
* @param startMethod The method to start at.
|
||||||
|
* @return A [MethodWalker] instance.
|
||||||
|
*/
|
||||||
fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker {
|
fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker {
|
||||||
return MethodWalker(this, startMethod)
|
return MethodWalker(this, startMethod)
|
||||||
}
|
}
|
||||||
@@ -80,7 +63,7 @@ fun BytecodeData.proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.Clas
|
|||||||
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 = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
||||||
this.classes.proxies.add(proxy)
|
this.classes.add(proxy)
|
||||||
}
|
}
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
@@ -0,0 +1,57 @@
|
|||||||
|
package app.revanced.patcher.extensions
|
||||||
|
|
||||||
|
import app.revanced.patcher.annotation.Compatibility
|
||||||
|
import app.revanced.patcher.annotation.Description
|
||||||
|
import app.revanced.patcher.annotation.Name
|
||||||
|
import app.revanced.patcher.annotation.Version
|
||||||
|
import app.revanced.patcher.data.base.Data
|
||||||
|
import app.revanced.patcher.patch.base.Patch
|
||||||
|
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
||||||
|
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
|
||||||
|
import app.revanced.patcher.signature.implementation.method.annotation.MatchingMethod
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively find a given annotation on a class.
|
||||||
|
* @param targetAnnotation The annotation to find.
|
||||||
|
* @return The annotation.
|
||||||
|
*/
|
||||||
|
private fun <T : Annotation> Class<*>.recursiveAnnotation(targetAnnotation: KClass<T>) =
|
||||||
|
this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
||||||
|
|
||||||
|
|
||||||
|
private fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
||||||
|
targetAnnotation: Class<T>, traversed: MutableSet<Annotation>
|
||||||
|
): T? {
|
||||||
|
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST") if (found != null) return found as T
|
||||||
|
|
||||||
|
for (annotation in this.annotations) {
|
||||||
|
if (traversed.contains(annotation)) continue
|
||||||
|
traversed.add(annotation)
|
||||||
|
|
||||||
|
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed)) ?: continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
object PatchExtensions {
|
||||||
|
val Class<out Patch<Data>>.patchName: String
|
||||||
|
get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||||
|
val Class<out Patch<Data>>.version get() = recursiveAnnotation(Version::class)?.version
|
||||||
|
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
|
||||||
|
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Patch::class)?.dependencies
|
||||||
|
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||||
|
}
|
||||||
|
|
||||||
|
object MethodSignatureExtensions {
|
||||||
|
val MethodSignature.name get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||||
|
val MethodSignature.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
||||||
|
val MethodSignature.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
||||||
|
val MethodSignature.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||||
|
val MethodSignature.matchingMethod get() = javaClass.recursiveAnnotation(MatchingMethod::class)
|
||||||
|
val MethodSignature.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(FuzzyPatternScanMethod::class)
|
||||||
|
val MethodSignature.fuzzyThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
|
||||||
|
}
|
@@ -9,33 +9,7 @@ import org.jf.dexlib2.iface.reference.MethodReference
|
|||||||
import org.jf.dexlib2.immutable.ImmutableMethod
|
import org.jf.dexlib2.immutable.ImmutableMethod
|
||||||
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
||||||
import org.jf.dexlib2.util.MethodUtil
|
import org.jf.dexlib2.util.MethodUtil
|
||||||
|
import java.io.OutputStream
|
||||||
/**
|
|
||||||
* Recursively find a given annotation on a class
|
|
||||||
* @param targetAnnotation The annotation to find
|
|
||||||
* @return The annotation
|
|
||||||
*/
|
|
||||||
fun <T : Annotation> Class<*>.findAnnotationRecursively(targetAnnotation: Class<T>) =
|
|
||||||
this.findAnnotationRecursively(targetAnnotation, mutableSetOf())
|
|
||||||
|
|
||||||
private fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
|
||||||
targetAnnotation: Class<T>,
|
|
||||||
traversed: MutableSet<Annotation>
|
|
||||||
): T? {
|
|
||||||
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
if (found != null) return found as T
|
|
||||||
|
|
||||||
for (annotation in this.annotations) {
|
|
||||||
if (traversed.contains(annotation)) continue
|
|
||||||
traversed.add(annotation)
|
|
||||||
|
|
||||||
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed)) ?: continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value
|
infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value
|
||||||
infix fun Int.or(other: AccessFlags) = this or other.value
|
infix fun Int.or(other: AccessFlags) = this or other.value
|
||||||
@@ -46,6 +20,19 @@ fun MutableMethodImplementation.addInstructions(index: Int, instructions: List<B
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare a method to another, considering constructors and parameters.
|
||||||
|
* @param otherMethod The method to compare against.
|
||||||
|
* @return True if the methods match given the conditions.
|
||||||
|
*/
|
||||||
|
fun Method.softCompareTo(
|
||||||
|
otherMethod: MethodReference
|
||||||
|
): Boolean {
|
||||||
|
if (MethodUtil.isConstructor(this) && !parametersEqual(this.parameterTypes, otherMethod.parameterTypes))
|
||||||
|
return false
|
||||||
|
return this.name == otherMethod.name
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clones the method.
|
* Clones the method.
|
||||||
* @param registerCount This parameter allows you to change the register count of the method.
|
* @param registerCount This parameter allows you to change the register count of the method.
|
||||||
@@ -85,14 +72,6 @@ internal fun Method.cloneMutable(
|
|||||||
registerCount: Int = 0,
|
registerCount: Int = 0,
|
||||||
) = clone(registerCount).toMutable()
|
) = 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
|
// FIXME: also check the order of parameters as different order equals different method overload
|
||||||
internal fun parametersEqual(
|
internal fun parametersEqual(
|
||||||
parameters1: Iterable<CharSequence>,
|
parameters1: Iterable<CharSequence>,
|
||||||
@@ -105,4 +84,9 @@ internal fun parametersEqual(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val nullOutputStream: OutputStream =
|
||||||
|
object : OutputStream() {
|
||||||
|
override fun write(b: Int) {}
|
||||||
|
}
|
@@ -1,9 +1,14 @@
|
|||||||
package app.revanced.patcher.patch.annotations
|
package app.revanced.patcher.patch.annotations
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.base.Data
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation to mark a Class as a patch.
|
* Annotation to mark a Class as a patch.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@MustBeDocumented
|
@MustBeDocumented
|
||||||
annotation class Patch
|
annotation class Patch(
|
||||||
|
val dependencies: Array<KClass<out app.revanced.patcher.patch.base.Patch<Data>>> = []
|
||||||
|
)
|
||||||
|
@@ -11,8 +11,9 @@ import app.revanced.patcher.patch.implementation.misc.PatchResult
|
|||||||
* Can either be a [ResourcePatch] or a [BytecodePatch].
|
* Can either be a [ResourcePatch] or a [BytecodePatch].
|
||||||
*/
|
*/
|
||||||
abstract class Patch<out T : Data> {
|
abstract class Patch<out T : Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main function of the [Patch] which the patcher will call.
|
* The main function of the [Patch] which the patcher will call.
|
||||||
*/
|
*/
|
||||||
abstract fun execute(data: @UnsafeVariance T): PatchResult // FIXME: remove the UnsafeVariance annotation
|
abstract fun execute(data: @UnsafeVariance T): PatchResult
|
||||||
}
|
}
|
@@ -24,10 +24,12 @@ interface PatchResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PatchResultError(private val errorMessage: String) : PatchResult {
|
class PatchResultError(
|
||||||
fun errorMessage(): String {
|
errorMessage: String?, cause: Exception?
|
||||||
return errorMessage
|
) : Exception(errorMessage, cause), PatchResult {
|
||||||
}
|
constructor(errorMessage: String) : this(errorMessage, null)
|
||||||
|
constructor(cause: Exception) : this(cause.message, cause)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PatchResultSuccess : PatchResult
|
class PatchResultSuccess : PatchResult
|
@@ -1,7 +1,7 @@
|
|||||||
package app.revanced.patcher.signature.implementation.method
|
package app.revanced.patcher.signature.implementation.method
|
||||||
|
|
||||||
import app.revanced.patcher.annotation.Name
|
|
||||||
import app.revanced.patcher.data.implementation.MethodNotFoundException
|
import app.revanced.patcher.data.implementation.MethodNotFoundException
|
||||||
|
import app.revanced.patcher.extensions.MethodSignatureExtensions.name
|
||||||
import app.revanced.patcher.signature.base.Signature
|
import app.revanced.patcher.signature.base.Signature
|
||||||
import app.revanced.patcher.signature.implementation.method.resolver.SignatureResolverResult
|
import app.revanced.patcher.signature.implementation.method.resolver.SignatureResolverResult
|
||||||
import org.jf.dexlib2.Opcode
|
import org.jf.dexlib2.Opcode
|
||||||
@@ -26,22 +26,8 @@ abstract class MethodSignature(
|
|||||||
* The result of the signature
|
* The result of the signature
|
||||||
*/
|
*/
|
||||||
var result: SignatureResolverResult? = null
|
var result: SignatureResolverResult? = null
|
||||||
|
@Throws(MethodNotFoundException::class)
|
||||||
get() {
|
get() {
|
||||||
return field ?: throw MethodNotFoundException(
|
return field ?: throw MethodNotFoundException("Could not resolve required signature ${this.name}")
|
||||||
"Could not resolve required signature ${
|
|
||||||
(this::class.annotations.find { it is Name }?.let {
|
|
||||||
(it as Name).name
|
|
||||||
})
|
|
||||||
}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val resolved: Boolean
|
|
||||||
get() {
|
|
||||||
var resolved = false
|
|
||||||
try {
|
|
||||||
resolved = result != null
|
|
||||||
} catch (_: Exception) {
|
|
||||||
}
|
|
||||||
return resolved
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -10,7 +10,7 @@ import app.revanced.patcher.signature.implementation.method.MethodSignature
|
|||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class MatchingMethod(
|
annotation class MatchingMethod(
|
||||||
val definingClass: String = "L<unspecified-class>",
|
val definingClass: String = "L<unspecified-class>;",
|
||||||
val name: String = "<unspecified-method>"
|
val name: String = "<unspecified-method>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -2,10 +2,9 @@ package app.revanced.patcher.signature.implementation.method.resolver
|
|||||||
|
|
||||||
import app.revanced.patcher.data.PatcherData
|
import app.revanced.patcher.data.PatcherData
|
||||||
import app.revanced.patcher.data.implementation.proxy
|
import app.revanced.patcher.data.implementation.proxy
|
||||||
import app.revanced.patcher.extensions.findAnnotationRecursively
|
import app.revanced.patcher.extensions.MethodSignatureExtensions.fuzzyThreshold
|
||||||
import app.revanced.patcher.extensions.parametersEqual
|
import app.revanced.patcher.extensions.parametersEqual
|
||||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
||||||
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
|
|
||||||
import org.jf.dexlib2.Opcode
|
import org.jf.dexlib2.Opcode
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
import org.jf.dexlib2.iface.Method
|
import org.jf.dexlib2.iface.Method
|
||||||
@@ -109,9 +108,7 @@ internal class MethodSignatureResolver(
|
|||||||
val pattern = signature.opcodes!!
|
val pattern = signature.opcodes!!
|
||||||
val size = pattern.count()
|
val size = pattern.count()
|
||||||
|
|
||||||
val threshold =
|
val threshold = signature.fuzzyThreshold
|
||||||
signature::class.java.findAnnotationRecursively(FuzzyPatternScanMethod::class.java)?.threshold
|
|
||||||
?: 0
|
|
||||||
|
|
||||||
for (instructionIndex in 0 until count) {
|
for (instructionIndex in 0 until count) {
|
||||||
var patternIndex = 0
|
var patternIndex = 0
|
||||||
|
@@ -1,24 +1,21 @@
|
|||||||
package app.revanced.patcher.util
|
package app.revanced.patcher.util
|
||||||
|
|
||||||
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
|
|
||||||
class ProxyBackedClassList(internal val internalClasses: MutableList<ClassDef>) : List<ClassDef> {
|
class ProxyBackedClassList(internal val internalClasses: MutableList<ClassDef>) : List<ClassDef> {
|
||||||
internal val proxies = mutableListOf<app.revanced.patcher.util.proxy.ClassProxy>()
|
private val internalProxies = mutableListOf<ClassProxy>()
|
||||||
|
internal val proxies: List<ClassProxy> = internalProxies
|
||||||
|
|
||||||
fun add(classDef: ClassDef) {
|
fun add(classDef: ClassDef) = internalClasses.add(classDef)
|
||||||
internalClasses.add(classDef)
|
fun add(classProxy: ClassProxy) = internalProxies.add(classProxy)
|
||||||
}
|
|
||||||
|
|
||||||
fun add(classProxy: app.revanced.patcher.util.proxy.ClassProxy) {
|
|
||||||
proxies.add(classProxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply all resolved classes into [internalClasses] and clean the [proxies] list.
|
* Apply all resolved classes into [internalClasses] and clean the [proxies] list.
|
||||||
*/
|
*/
|
||||||
fun applyProxies() {
|
internal fun applyProxies() {
|
||||||
// FIXME: check if this could cause issues when multiple patches use the same proxy
|
// FIXME: check if this could cause issues when multiple patches use the same proxy
|
||||||
proxies.removeIf { proxy ->
|
internalProxies.removeIf { proxy ->
|
||||||
// if the proxy is unused, keep it in the list
|
// if the proxy is unused, keep it in the list
|
||||||
if (!proxy.proxyUsed) return@removeIf false
|
if (!proxy.proxyUsed) return@removeIf false
|
||||||
|
|
||||||
|
@@ -33,7 +33,7 @@ class MethodWalker internal constructor(
|
|||||||
* @param walkMutable If this is true, the class of the method will be resolved mutably.
|
* @param walkMutable If this is true, the class of the method will be resolved mutably.
|
||||||
* The current method will be mutable.
|
* The current method will be mutable.
|
||||||
*/
|
*/
|
||||||
fun walk(offset: Int, walkMutable: Boolean = false): MethodWalker {
|
fun nextMethod(offset: Int, walkMutable: Boolean = false): MethodWalker {
|
||||||
currentMethod.implementation?.instructions?.let { instructions ->
|
currentMethod.implementation?.instructions?.let { instructions ->
|
||||||
val instruction = instructions.elementAt(offset)
|
val instruction = instructions.elementAt(offset)
|
||||||
|
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
package app.revanced.patcher.util.patch
|
|
||||||
|
|
||||||
import app.revanced.patcher.patch.base.Patch
|
|
||||||
import java.io.File
|
|
||||||
import java.net.URLClassLoader
|
|
||||||
import java.util.jar.JarFile
|
|
||||||
|
|
||||||
object PatchLoader {
|
|
||||||
/**
|
|
||||||
* This method loads patches from a given jar file containing [Patch]es
|
|
||||||
* @return the loaded patches represented as a list of [Patch] classes
|
|
||||||
*/
|
|
||||||
fun loadFromFile(patchesJar: File) = buildList {
|
|
||||||
val jarFile = JarFile(patchesJar)
|
|
||||||
val classLoader = URLClassLoader(arrayOf(patchesJar.toURI().toURL()))
|
|
||||||
|
|
||||||
val entries = jarFile.entries()
|
|
||||||
while (entries.hasMoreElements()) {
|
|
||||||
val entry = entries.nextElement()
|
|
||||||
if (!entry.name.endsWith(".class") || entry.name.contains("$")) continue
|
|
||||||
|
|
||||||
val clazz = classLoader.loadClass(entry.realName.replace('/', '.').replace(".class", ""))
|
|
||||||
|
|
||||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) continue
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val patch = clazz as Class<Patch<*>>
|
|
||||||
|
|
||||||
// TODO: include declared classes from patch
|
|
||||||
|
|
||||||
this.add(patch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,18 @@
|
|||||||
|
package app.revanced.patcher.util.patch.base
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.base.Data
|
||||||
|
import app.revanced.patcher.patch.base.Patch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param patchBundlePath The path to the patch bundle.
|
||||||
|
*/
|
||||||
|
abstract class PatchBundle(patchBundlePath: String) : File(patchBundlePath) {
|
||||||
|
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
||||||
|
classNames.forEach { className ->
|
||||||
|
val clazz = classLoader.loadClass(className)
|
||||||
|
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
|
||||||
|
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Data>>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.patcher.util.patch.implementation
|
||||||
|
|
||||||
|
import app.revanced.patcher.util.patch.base.PatchBundle
|
||||||
|
import app.revanced.patcher.util.patch.util.StringIterator
|
||||||
|
import org.jf.dexlib2.DexFileFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A patch bundle of the ReVanced [DexPatchBundle] format.
|
||||||
|
* @param patchBundlePath The path to a patch bundle of dex format.
|
||||||
|
* @param dexClassLoader The dex class loader.
|
||||||
|
*/
|
||||||
|
class DexPatchBundle(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
|
||||||
|
fun loadPatches() = loadPatches(dexClassLoader,
|
||||||
|
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
|
||||||
|
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||||
|
})
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package app.revanced.patcher.util.patch.implementation
|
||||||
|
|
||||||
|
import app.revanced.patcher.util.patch.base.PatchBundle
|
||||||
|
import app.revanced.patcher.util.patch.util.StringIterator
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.util.jar.JarFile
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A patch bundle of the ReVanced [JarPatchBundle] format.
|
||||||
|
* @param patchBundlePath The path to the patch bundle.
|
||||||
|
*/
|
||||||
|
class JarPatchBundle(patchBundlePath: String) : PatchBundle(patchBundlePath) {
|
||||||
|
fun loadPatches() = loadPatches(
|
||||||
|
URLClassLoader(
|
||||||
|
arrayOf(this.toURI().toURL()),
|
||||||
|
Thread.currentThread().contextClassLoader // TODO: find out why this is required
|
||||||
|
),
|
||||||
|
StringIterator(
|
||||||
|
JarFile(this)
|
||||||
|
.entries()
|
||||||
|
.toList() // TODO: find a cleaner solution than that to filter non class files
|
||||||
|
.filter {
|
||||||
|
it.name.endsWith(".class") && !it.name.contains("$")
|
||||||
|
}
|
||||||
|
.iterator()
|
||||||
|
) {
|
||||||
|
it.realName.replace('/', '.').replace(".class", "")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
package app.revanced.patcher.util.patch.util
|
||||||
|
|
||||||
|
internal class StringIterator<T, I : Iterator<T>>(
|
||||||
|
private val iterator: I,
|
||||||
|
private val _next: (T) -> String
|
||||||
|
) : Iterator<String> {
|
||||||
|
override fun hasNext() = iterator.hasNext()
|
||||||
|
|
||||||
|
override fun next() = _next(iterator.next())
|
||||||
|
}
|
Reference in New Issue
Block a user