You've already forked revanced-patcher
mirror of
https://github.com/revanced/revanced-patcher
synced 2025-09-06 16:38:50 +02:00
Compare commits
37 Commits
v1.8.0
...
v2.2.0-dev
Author | SHA1 | Date | |
---|---|---|---|
![]() |
60a8278ae8 | ||
![]() |
ada5a033de | ||
![]() |
109b8a296d | ||
![]() |
e2faf4ca9b | ||
![]() |
2134182a0e | ||
![]() |
d8b5b8bb7c | ||
![]() |
49970b5926 | ||
![]() |
c1fbd8cf8c | ||
![]() |
a467fbb704 | ||
![]() |
5a4bd7a76e | ||
![]() |
e5ca86fac6 | ||
![]() |
68d9e9f02c | ||
![]() |
494a9a09ac | ||
![]() |
06a88839de | ||
![]() |
4c41b955df | ||
![]() |
614e555f4c | ||
![]() |
d6ed06a327 | ||
![]() |
2fc4ec4021 | ||
![]() |
f565c4f6a7 | ||
![]() |
35749454ab | ||
![]() |
2b492e7a5e | ||
![]() |
081a5a6849 | ||
![]() |
852ae7d8d1 | ||
![]() |
52f8a6a2eb | ||
![]() |
efa8ea1445 | ||
![]() |
c828fa2a27 | ||
![]() |
92ac5e4dc2 | ||
![]() |
070c09cf71 | ||
![]() |
b738dcd7ea | ||
![]() |
8efcf329bb | ||
![]() |
0e87ef56c4 | ||
![]() |
e3bf367ad6 | ||
![]() |
3d61dacbda | ||
![]() |
c20dfe12d5 | ||
![]() |
78663cde88 | ||
![]() |
dde5385232 | ||
![]() |
930768dfb3 |
@@ -7,7 +7,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@semantic-release/commit-analyzer",
|
["@semantic-release/commit-analyzer", {
|
||||||
|
"releaseRules": [
|
||||||
|
{"type": "build", "release": "patch"}
|
||||||
|
]
|
||||||
|
}],
|
||||||
"@semantic-release/release-notes-generator",
|
"@semantic-release/release-notes-generator",
|
||||||
"@semantic-release/changelog",
|
"@semantic-release/changelog",
|
||||||
"gradle-semantic-release-plugin",
|
"gradle-semantic-release-plugin",
|
||||||
|
89
CHANGELOG.md
89
CHANGELOG.md
@@ -1,3 +1,92 @@
|
|||||||
|
# [2.2.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v2.1.2...v2.2.0-dev.1) (2022-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* remove deprecated functions ([ada5a03](https://github.com/revanced/revanced-patcher/commit/ada5a033de3cf94e7255ec2d522520f86431f001))
|
||||||
|
|
||||||
|
## [2.1.2](https://github.com/revanced/revanced-patcher/compare/v2.1.1...v2.1.2) (2022-06-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* invert fingerprint resolution condition of `customFingerprint` ([e2faf4c](https://github.com/revanced/revanced-patcher/commit/e2faf4ca9b6de23300b20ab471ee9dc365b04339))
|
||||||
|
|
||||||
|
## [2.1.1](https://github.com/revanced/revanced-patcher/compare/v2.1.0...v2.1.1) (2022-06-28)
|
||||||
|
|
||||||
|
# [2.1.0](https://github.com/revanced/revanced-patcher/compare/v2.0.4...v2.1.0) (2022-06-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* log failed patches due to failed dependencies ([a467fbb](https://github.com/revanced/revanced-patcher/commit/a467fbb704eebe812cdec14025398dab2af43959))
|
||||||
|
|
||||||
|
## [2.0.4](https://github.com/revanced/revanced-patcher/compare/v2.0.3...v2.0.4) (2022-06-27)
|
||||||
|
|
||||||
|
## [2.0.3](https://github.com/revanced/revanced-patcher/compare/v2.0.2...v2.0.3) (2022-06-27)
|
||||||
|
|
||||||
|
## [2.0.2](https://github.com/revanced/revanced-patcher/compare/v2.0.1...v2.0.2) (2022-06-27)
|
||||||
|
|
||||||
|
## [2.0.1](https://github.com/revanced/revanced-patcher/compare/v2.0.0...v2.0.1) (2022-06-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* use `Exception` instead of `MethodNotFoundException` ([2fc4ec4](https://github.com/revanced/revanced-patcher/commit/2fc4ec40217a917ea6106ddc87be332f725aa13c))
|
||||||
|
|
||||||
|
# [2.0.0](https://github.com/revanced/revanced-patcher/compare/v1.11.0...v2.0.0) (2022-06-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* migrate from `Signature` to `Fingerprint` ([efa8ea1](https://github.com/revanced/revanced-patcher/commit/efa8ea144528fcff588e782468845c315a7d6abd))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* Not backwards compatible, since a lot of classes where renamed.
|
||||||
|
|
||||||
|
# [1.11.0](https://github.com/revanced/revanced-patcher/compare/v1.10.2...v1.11.0) (2022-06-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add replace and remove extensions ([#50](https://github.com/revanced/revanced-patcher/issues/50)) ([92ac5e4](https://github.com/revanced/revanced-patcher/commit/92ac5e4dc25f612856e2b5e528cf5fd48a5f20af))
|
||||||
|
|
||||||
|
## [1.10.2](https://github.com/revanced/revanced-patcher/compare/v1.10.1...v1.10.2) (2022-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* dexlib must be propagated ([b738dcd](https://github.com/revanced/revanced-patcher/commit/b738dcd7ea04f5fe56e66af46fb11541fe54f6af))
|
||||||
|
|
||||||
|
## [1.10.1](https://github.com/revanced/revanced-patcher/compare/v1.10.0...v1.10.1) (2022-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* callback only when inteded ([e3bf367](https://github.com/revanced/revanced-patcher/commit/e3bf367ad6615b30b06027d65f906b2588567a7f))
|
||||||
|
* mutability of local variable `modified` ([0e87ef5](https://github.com/revanced/revanced-patcher/commit/0e87ef56c418d5c37d58abb9b27f85e25fd44f81))
|
||||||
|
|
||||||
|
# [1.10.0](https://github.com/revanced/revanced-patcher/compare/v1.9.0...v1.10.0) (2022-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* improve logging ([c20dfe1](https://github.com/revanced/revanced-patcher/commit/c20dfe12d5c737264b844e6634de11bf1e1629f0))
|
||||||
|
|
||||||
|
# [1.9.0](https://github.com/revanced/revanced-patcher/compare/v1.8.0...v1.9.0) (2022-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* callback for each file instead of class ([930768d](https://github.com/revanced/revanced-patcher/commit/930768dfb31dc5fa6c248050b08ac117c40ee0a3))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* yield the patch result ([dde5385](https://github.com/revanced/revanced-patcher/commit/dde5385232abddc8a85d6e9a939549b71dd9130e))
|
||||||
|
|
||||||
# [1.8.0](https://github.com/revanced/revanced-patcher/compare/v1.7.2...v1.8.0) (2022-06-22)
|
# [1.8.0](https://github.com/revanced/revanced-patcher/compare/v1.7.2...v1.8.0) (2022-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -22,9 +22,9 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("xpp3:xpp3:1.1.4c")
|
implementation("xpp3:xpp3:1.1.4c")
|
||||||
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
|
||||||
implementation("org.smali:smali:2.5.2")
|
implementation("org.smali:smali:2.5.2")
|
||||||
implementation("org.apktool:apktool-lib:2.6.5-SNAPSHOT")
|
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
||||||
|
implementation("org.apktool:apktool-lib:2.6.9-SNAPSHOT")
|
||||||
|
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,7 @@ java {
|
|||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
repositories {
|
repositories {
|
||||||
|
if (System.getenv("GITHUB_ACTOR") != null)
|
||||||
maven {
|
maven {
|
||||||
name = "GitHubPackages"
|
name = "GitHubPackages"
|
||||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
||||||
@@ -52,6 +53,8 @@ publishing {
|
|||||||
password = System.getenv("GITHUB_TOKEN")
|
password = System.getenv("GITHUB_TOKEN")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
mavenLocal()
|
||||||
}
|
}
|
||||||
publications {
|
publications {
|
||||||
register<MavenPublication>("gpr") {
|
register<MavenPublication>("gpr") {
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 1.8.0
|
version = 2.2.0-dev.1
|
||||||
|
@@ -1,19 +1,18 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.Data
|
||||||
import app.revanced.patcher.data.PackageMetadata
|
import app.revanced.patcher.data.PackageMetadata
|
||||||
import app.revanced.patcher.data.PatcherData
|
import app.revanced.patcher.data.impl.findIndexed
|
||||||
import app.revanced.patcher.data.base.Data
|
|
||||||
import app.revanced.patcher.data.implementation.findIndexed
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import app.revanced.patcher.extensions.nullOutputStream
|
import app.revanced.patcher.extensions.nullOutputStream
|
||||||
import app.revanced.patcher.patch.base.Patch
|
import app.revanced.patcher.fingerprint.method.utils.MethodFingerprintUtils.resolve
|
||||||
import app.revanced.patcher.patch.implementation.BytecodePatch
|
import app.revanced.patcher.patch.Patch
|
||||||
import app.revanced.patcher.patch.implementation.ResourcePatch
|
import app.revanced.patcher.patch.PatchResult
|
||||||
import app.revanced.patcher.patch.implementation.misc.PatchResult
|
import app.revanced.patcher.patch.PatchResultError
|
||||||
import app.revanced.patcher.patch.implementation.misc.PatchResultError
|
import app.revanced.patcher.patch.PatchResultSuccess
|
||||||
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
|
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||||
import app.revanced.patcher.signature.implementation.method.resolver.MethodSignatureResolver
|
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||||
import app.revanced.patcher.util.ListBackedSet
|
import app.revanced.patcher.util.ListBackedSet
|
||||||
import brut.androlib.Androlib
|
import brut.androlib.Androlib
|
||||||
import brut.androlib.meta.UsesFramework
|
import brut.androlib.meta.UsesFramework
|
||||||
@@ -62,7 +61,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
val packageMetadata = PackageMetadata()
|
val packageMetadata = PackageMetadata()
|
||||||
|
|
||||||
if (options.patchResources) {
|
if (options.patchResources) {
|
||||||
logger.info("Decoding resources using Androlib, this may take a long time...")
|
logger.info("Decoding resources")
|
||||||
|
|
||||||
// decode resources to cache directory
|
// decode resources to cache directory
|
||||||
androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable)
|
androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable)
|
||||||
@@ -78,7 +77,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
logger.info("Decoding AndroidManifest.xml manually because resource patching is disabled")
|
logger.info("Only decoding AndroidManifest.xml because resource patching is disabled")
|
||||||
|
|
||||||
// create decoder for the resource table
|
// create decoder for the resource table
|
||||||
val decoder = ResAttrDecoder()
|
val decoder = ResAttrDecoder()
|
||||||
@@ -102,7 +101,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
packageMetadata.metaInfo.versionInfo = resourceTable.versionInfo
|
packageMetadata.metaInfo.versionInfo = resourceTable.versionInfo
|
||||||
packageMetadata.metaInfo.sdkInfo = resourceTable.sdkInfo
|
packageMetadata.metaInfo.sdkInfo = resourceTable.sdkInfo
|
||||||
|
|
||||||
logger.info("Reading input as dex file")
|
logger.info("Reading dex files")
|
||||||
|
|
||||||
// read dex files
|
// read dex files
|
||||||
val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
|
val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
|
||||||
@@ -128,6 +127,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
callback: (File) -> Unit
|
callback: (File) -> Unit
|
||||||
) {
|
) {
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
|
var modified = false
|
||||||
for (classDef in MultiDexIO.readDexFile(true, file, NAMER, null, null).classes) {
|
for (classDef in MultiDexIO.readDexFile(true, file, NAMER, null, null).classes) {
|
||||||
val type = classDef.type
|
val type = classDef.type
|
||||||
|
|
||||||
@@ -137,8 +137,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
|
|
||||||
logger.trace("Merging $type")
|
logger.trace("Merging $type")
|
||||||
data.bytecodeData.classes.internalClasses.add(classDef)
|
data.bytecodeData.classes.internalClasses.add(classDef)
|
||||||
|
modified = true
|
||||||
callback(file)
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -149,9 +148,9 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
|
|
||||||
val index = existingClass.second
|
val index = existingClass.second
|
||||||
data.bytecodeData.classes.internalClasses[index] = classDef
|
data.bytecodeData.classes.internalClasses[index] = classDef
|
||||||
|
modified = true
|
||||||
callback(file)
|
|
||||||
}
|
}
|
||||||
|
if (modified) callback(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +163,6 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
var resourceFile: File? = null
|
var resourceFile: File? = null
|
||||||
|
|
||||||
if (options.patchResources) {
|
if (options.patchResources) {
|
||||||
logger.info("Patching resources")
|
|
||||||
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
|
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
|
||||||
|
|
||||||
val androlibResources = AndrolibResources().also { resources ->
|
val androlibResources = AndrolibResources().also { resources ->
|
||||||
@@ -197,7 +195,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
)
|
)
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
|
|
||||||
logger.trace("Packaging using aapt")
|
logger.info("Compiling resources")
|
||||||
androlibResources.aaptPackage(
|
androlibResources.aaptPackage(
|
||||||
aaptFile, manifestFile, resDirectory, null, null, includedFiles
|
aaptFile, manifestFile, resDirectory, null, null, includedFiles
|
||||||
)
|
)
|
||||||
@@ -243,33 +241,35 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
/**
|
/**
|
||||||
* Apply a [patch] and its dependencies recursively.
|
* Apply a [patch] and its dependencies recursively.
|
||||||
* @param patch The [patch] to apply.
|
* @param patch The [patch] to apply.
|
||||||
* @param appliedPatches A list of [patch] names, to prevent applying [patch]es twice.
|
* @param appliedPatches A map of [patch]es paired to a boolean indicating their success, to prevent infinite recursion.
|
||||||
* @return The result of executing the [patch].
|
* @return The result of executing the [patch].
|
||||||
*/
|
*/
|
||||||
private fun applyPatch(
|
private fun applyPatch(
|
||||||
patch: Class<out Patch<Data>>, appliedPatches: MutableList<String>
|
patch: Class<out Patch<Data>>,
|
||||||
|
appliedPatches: MutableMap<String, Boolean>
|
||||||
): PatchResult {
|
): PatchResult {
|
||||||
val patchName = patch.patchName
|
val patchName = patch.patchName
|
||||||
|
|
||||||
logger.trace("Applying patch $patchName")
|
|
||||||
|
|
||||||
// if the patch has already applied silently skip it
|
// if the patch has already applied silently skip it
|
||||||
if (appliedPatches.contains(patchName)) {
|
if (appliedPatches.contains(patchName)) {
|
||||||
logger.trace("Skipping patch $patchName because it has already been applied")
|
if (!appliedPatches[patchName]!!)
|
||||||
|
return PatchResultError("'$patchName' did not succeed previously")
|
||||||
|
|
||||||
|
logger.trace("Skipping '$patchName' because it has already been applied")
|
||||||
|
|
||||||
return PatchResultSuccess()
|
return PatchResultSuccess()
|
||||||
}
|
}
|
||||||
appliedPatches.add(patchName)
|
|
||||||
|
|
||||||
// recursively apply all dependency patches
|
// recursively apply all dependency patches
|
||||||
patch.dependencies?.forEach {
|
patch.dependencies?.forEach {
|
||||||
val patchDependency = it.java
|
val patchDependency = it.java
|
||||||
|
|
||||||
val result = applyPatch(patchDependency, appliedPatches)
|
val result = applyPatch(patchDependency, appliedPatches)
|
||||||
|
|
||||||
if (result.isSuccess()) return@forEach
|
if (result.isSuccess()) return@forEach
|
||||||
|
|
||||||
val errorMessage = result.error()!!.message
|
val errorMessage = result.error()!!.message
|
||||||
return PatchResultError("$patchName depends on ${patchDependency.patchName} but the following error was raised: $errorMessage")
|
return PatchResultError("'$patchName' depends on '${patchDependency.patchName}' but the following error was raised: $errorMessage")
|
||||||
}
|
}
|
||||||
|
|
||||||
val patchInstance = patch.getDeclaredConstructor().newInstance()
|
val patchInstance = patch.getDeclaredConstructor().newInstance()
|
||||||
@@ -277,24 +277,26 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
// if the current patch is a resource patch but resource patching is disabled, return an error
|
// if the current patch is a resource patch but resource patching is disabled, return an error
|
||||||
val isResourcePatch = patchInstance is ResourcePatch
|
val isResourcePatch = patchInstance is ResourcePatch
|
||||||
if (!options.patchResources && isResourcePatch) {
|
if (!options.patchResources && isResourcePatch) {
|
||||||
return PatchResultError("$patchName is a resource patch, but resource patching is disabled.")
|
return PatchResultError("'$patchName' is a resource patch, but resource patching is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: find a solution for this
|
// TODO: find a solution for this
|
||||||
val data = if (isResourcePatch) {
|
val data = if (isResourcePatch) {
|
||||||
data.resourceData
|
data.resourceData
|
||||||
} else {
|
} else {
|
||||||
MethodSignatureResolver(
|
val bytecodeData = data.bytecodeData
|
||||||
data.bytecodeData.classes.internalClasses, (patchInstance as BytecodePatch).signatures
|
(patchInstance as BytecodePatch).fingerprints.resolve(bytecodeData, bytecodeData.classes.internalClasses)
|
||||||
).resolve(data)
|
bytecodeData
|
||||||
data.bytecodeData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace("Executing patch $patchName of type: ${if (isResourcePatch) "resource" else "bytecode"}")
|
logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}")
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
patchInstance.execute(data)
|
val result = patchInstance.execute(data)
|
||||||
|
appliedPatches[patchName] = result.isSuccess()
|
||||||
|
result
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
appliedPatches[patchName] = false
|
||||||
PatchResultError(e)
|
PatchResultError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,29 +304,23 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
/**
|
/**
|
||||||
* 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.
|
||||||
* @return A map of [PatchResultSuccess]. If the [Patch] was successfully applied,
|
* @return A pair of the name of the [Patch] and its [PatchResult].
|
||||||
* [PatchResultSuccess] will always be returned to the wrapping Result object.
|
|
||||||
* If the [Patch] failed to apply, an Exception will always be returned to the wrapping Result object.
|
|
||||||
*/
|
*/
|
||||||
fun applyPatches(
|
fun applyPatches(stopOnError: Boolean = false) = sequence {
|
||||||
stopOnError: Boolean = false, callback: (Class<out Patch<Data>>, Boolean) -> Unit = { _, _ -> }
|
|
||||||
): Map<String, Result<PatchResultSuccess>> {
|
|
||||||
logger.trace("Applying all patches")
|
logger.trace("Applying all patches")
|
||||||
val appliedPatches = mutableListOf<String>()
|
val appliedPatches = mutableMapOf<String, Boolean>() // first is success, second is name
|
||||||
|
|
||||||
return buildMap {
|
for (patch in data.patches) {
|
||||||
for (patch in data.patches) {
|
val patchResult = applyPatch(patch, appliedPatches)
|
||||||
val result = applyPatch(patch, appliedPatches)
|
|
||||||
|
|
||||||
this[patch.patchName] = if (result.isSuccess()) {
|
val result = if (patchResult.isSuccess()) {
|
||||||
Result.success(result.success()!!)
|
Result.success(patchResult.success()!!)
|
||||||
} else {
|
} else {
|
||||||
Result.failure(result.error()!!)
|
Result.failure(patchResult.error()!!)
|
||||||
}
|
|
||||||
|
|
||||||
callback(patch, result.isSuccess())
|
|
||||||
if (stopOnError && result.isError()) break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yield(patch.patchName to result)
|
||||||
|
if (stopOnError && patchResult.isError()) break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,4 +329,4 @@ private fun BuildOptions.setBuildOptions(options: PatcherOptions) {
|
|||||||
this.aaptPath = options.aaptPath
|
this.aaptPath = options.aaptPath
|
||||||
this.useAapt2 = true
|
this.useAapt2 = true
|
||||||
this.frameworkFolderLocation = options.frameworkFolderLocation
|
this.frameworkFolderLocation = options.frameworkFolderLocation
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
package app.revanced.patcher.data
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.data.base.Data
|
import app.revanced.patcher.data.Data
|
||||||
import app.revanced.patcher.data.implementation.BytecodeData
|
import app.revanced.patcher.data.PackageMetadata
|
||||||
import app.revanced.patcher.data.implementation.ResourceData
|
import app.revanced.patcher.data.impl.BytecodeData
|
||||||
import app.revanced.patcher.patch.base.Patch
|
import app.revanced.patcher.data.impl.ResourceData
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -12,9 +13,7 @@ data class PatcherData(
|
|||||||
internal val resourceCacheDirectory: String,
|
internal val resourceCacheDirectory: String,
|
||||||
val packageMetadata: PackageMetadata
|
val packageMetadata: PackageMetadata
|
||||||
) {
|
) {
|
||||||
|
|
||||||
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
||||||
|
|
||||||
internal val bytecodeData = BytecodeData(internalClasses)
|
internal val bytecodeData = BytecodeData(internalClasses)
|
||||||
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
||||||
}
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.logging.impl.NopLogger
|
|
||||||
import app.revanced.patcher.logging.Logger
|
import app.revanced.patcher.logging.Logger
|
||||||
|
import app.revanced.patcher.logging.impl.NopLogger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
package app.revanced.patcher.annotation
|
package app.revanced.patcher.annotation
|
||||||
|
|
||||||
import app.revanced.patcher.patch.base.Patch
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
import app.revanced.patcher.patch.Patch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation to constrain a [Patch] or [MethodSignature] to compatible packages.
|
* Annotation to constrain a [Patch] or [MethodFingerprint] to compatible packages.
|
||||||
* @param compatiblePackages A list of packages a [Patch] or [MethodSignature] is compatible with.
|
* @param compatiblePackages A list of packages a [Patch] or [MethodFingerprint] is compatible with.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@@ -17,7 +17,7 @@ annotation class Compatibility(
|
|||||||
/**
|
/**
|
||||||
* Annotation to represent packages a patch can be compatible with.
|
* Annotation to represent packages a patch can be compatible with.
|
||||||
* @param name The package identifier name.
|
* @param name The package identifier name.
|
||||||
* @param versions The versions of the package the [Patch] or [MethodSignature]is compatible with.
|
* @param versions The versions of the package the [Patch] or [MethodFingerprint]is compatible with.
|
||||||
*/
|
*/
|
||||||
@Target()
|
@Target()
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
package app.revanced.patcher.annotation
|
package app.revanced.patcher.annotation
|
||||||
|
|
||||||
import app.revanced.patcher.patch.base.Patch
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
import app.revanced.patcher.patch.Patch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation to name a [Patch] or [MethodSignature].
|
* Annotation to name a [Patch] or [MethodFingerprint].
|
||||||
* @param name A suggestive name for the [Patch] or [MethodSignature].
|
* @param name A suggestive name for the [Patch] or [MethodFingerprint].
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@@ -15,8 +15,8 @@ annotation class Name(
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation to describe a [Patch] or [MethodSignature].
|
* Annotation to describe a [Patch] or [MethodFingerprint].
|
||||||
* @param description A description for the [Patch] or [MethodSignature].
|
* @param description A description for the [Patch] or [MethodFingerprint].
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@@ -27,8 +27,8 @@ annotation class Description(
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation to version a [Patch] or [MethodSignature].
|
* Annotation to version a [Patch] or [MethodFingerprint].
|
||||||
* @param version The version of a [Patch] or [MethodSignature].
|
* @param version The version of a [Patch] or [MethodFingerprint].
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
9
src/main/kotlin/app/revanced/patcher/data/Data.kt
Normal file
9
src/main/kotlin/app/revanced/patcher/data/Data.kt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.patcher.data
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.impl.BytecodeData
|
||||||
|
import app.revanced.patcher.data.impl.ResourceData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constraint interface for [BytecodeData] and [ResourceData]
|
||||||
|
*/
|
||||||
|
interface Data
|
@@ -1,9 +0,0 @@
|
|||||||
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,6 +1,6 @@
|
|||||||
package app.revanced.patcher.data.implementation
|
package app.revanced.patcher.data.impl
|
||||||
|
|
||||||
import app.revanced.patcher.data.base.Data
|
import app.revanced.patcher.data.Data
|
||||||
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
|
||||||
@@ -28,6 +28,15 @@ class BytecodeData(
|
|||||||
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
||||||
// 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
|
||||||
classes.find(predicate)?.let { proxy(it) }
|
classes.find(predicate)?.let { proxy(it) }
|
||||||
|
|
||||||
|
fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
||||||
|
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
||||||
|
if (proxy == null) {
|
||||||
|
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
||||||
|
this.classes.add(proxy)
|
||||||
|
}
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class MethodNotFoundException(s: String) : Exception(s)
|
internal class MethodNotFoundException(s: String) : Exception(s)
|
||||||
@@ -57,13 +66,4 @@ internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
|
||||||
|
|
||||||
fun BytecodeData.proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
|
||||||
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
|
||||||
if (proxy == null) {
|
|
||||||
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
|
||||||
this.classes.add(proxy)
|
|
||||||
}
|
|
||||||
return proxy
|
|
||||||
}
|
}
|
@@ -0,0 +1,28 @@
|
|||||||
|
package app.revanced.patcher.data.impl
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.Data
|
||||||
|
import org.w3c.dom.Document
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.File
|
||||||
|
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, Iterable<File> {
|
||||||
|
operator fun get(path: String) = resourceCacheDirectory.resolve(path)
|
||||||
|
val xmlEditor = XmlFileHolder()
|
||||||
|
override fun iterator() = resourceCacheDirectory.walkTopDown().iterator()
|
||||||
|
|
||||||
|
inner class XmlFileHolder {
|
||||||
|
operator fun get(path: String) = DomFileEditor(this@ResourceData[path])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomFileEditor internal constructor(private val domFile: File) : Closeable {
|
||||||
|
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||||
|
.parse(domFile).also(Document::normalize)
|
||||||
|
|
||||||
|
override fun close() = TransformerFactory.newInstance().newTransformer()
|
||||||
|
.transform(DOMSource(file), StreamResult(domFile.outputStream()))
|
||||||
|
}
|
@@ -1,47 +0,0 @@
|
|||||||
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 get(path: String) = resolve(path)
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
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()))
|
|
||||||
}
|
|
@@ -4,11 +4,9 @@ import app.revanced.patcher.annotation.Compatibility
|
|||||||
import app.revanced.patcher.annotation.Description
|
import app.revanced.patcher.annotation.Description
|
||||||
import app.revanced.patcher.annotation.Name
|
import app.revanced.patcher.annotation.Name
|
||||||
import app.revanced.patcher.annotation.Version
|
import app.revanced.patcher.annotation.Version
|
||||||
import app.revanced.patcher.data.base.Data
|
import app.revanced.patcher.data.Data
|
||||||
import app.revanced.patcher.patch.base.Patch
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
import app.revanced.patcher.patch.Patch
|
||||||
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
|
|
||||||
import app.revanced.patcher.signature.implementation.method.annotation.MatchingMethod
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,13 +45,13 @@ object PatchExtensions {
|
|||||||
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||||
}
|
}
|
||||||
|
|
||||||
object MethodSignatureExtensions {
|
object MethodFingerprintExtensions {
|
||||||
val MethodSignature.name: String
|
val MethodFingerprint.name: String
|
||||||
get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||||
val MethodSignature.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
val MethodFingerprint.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
||||||
val MethodSignature.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
val MethodFingerprint.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
||||||
val MethodSignature.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
val MethodFingerprint.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||||
val MethodSignature.matchingMethod get() = javaClass.recursiveAnnotation(MatchingMethod::class)
|
val MethodFingerprint.matchingMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.MatchingMethod::class)
|
||||||
val MethodSignature.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(FuzzyPatternScanMethod::class)
|
val MethodFingerprint.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod::class)
|
||||||
val MethodSignature.fuzzyThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
|
val MethodFingerprint.fuzzyScanThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
|
||||||
}
|
}
|
@@ -23,6 +23,18 @@ fun MutableMethodImplementation.addInstructions(index: Int, instructions: List<B
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) {
|
||||||
|
for (i in instructions.lastIndex downTo 0) {
|
||||||
|
this.replaceInstruction(index + i, instructions[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) {
|
||||||
|
for (i in count downTo 0) {
|
||||||
|
this.removeInstruction(index + i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare a method to another, considering constructors and parameters.
|
* Compare a method to another, considering constructors and parameters.
|
||||||
* @param otherMethod The method to compare against.
|
* @param otherMethod The method to compare against.
|
||||||
@@ -66,13 +78,35 @@ internal fun Method.clone(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add smali instructions to the method.
|
* Add a smali instruction to the method.
|
||||||
* @param index The index to insert the instructions at.
|
* @param instruction The smali instruction to add.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.addInstruction(instruction: String) =
|
||||||
|
this.implementation!!.addInstruction(instruction.toInstruction(this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a smali instruction to the method.
|
||||||
|
* @param index The index to insert the instruction at.
|
||||||
* @param instruction The smali instruction to add.
|
* @param instruction The smali instruction to add.
|
||||||
*/
|
*/
|
||||||
fun MutableMethod.addInstruction(index: Int, instruction: String) =
|
fun MutableMethod.addInstruction(index: Int, instruction: String) =
|
||||||
this.implementation!!.addInstruction(index, instruction.toInstruction(this))
|
this.implementation!!.addInstruction(index, instruction.toInstruction(this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace a smali instruction within the method.
|
||||||
|
* @param index The index to replace the instruction at.
|
||||||
|
* @param instruction The smali instruction to place.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.replaceInstruction(index: Int, instruction: String) =
|
||||||
|
this.implementation!!.replaceInstruction(index, instruction.toInstruction(this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a smali instruction within the method.
|
||||||
|
* @param index The index to delete the instruction at.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.removeInstruction(index: Int) =
|
||||||
|
this.implementation!!.removeInstruction(index)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add smali instructions to the method.
|
* Add smali instructions to the method.
|
||||||
* @param index The index to insert the instructions at.
|
* @param index The index to insert the instructions at.
|
||||||
@@ -81,6 +115,22 @@ fun MutableMethod.addInstruction(index: Int, instruction: String) =
|
|||||||
fun MutableMethod.addInstructions(index: Int, instructions: String) =
|
fun MutableMethod.addInstructions(index: Int, instructions: String) =
|
||||||
this.implementation!!.addInstructions(index, instructions.toInstructions(this))
|
this.implementation!!.addInstructions(index, instructions.toInstructions(this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace smali instructions within the method.
|
||||||
|
* @param index The index to replace the instructions at.
|
||||||
|
* @param instructions The smali instructions to place.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.replaceInstructions(index: Int, instructions: String) =
|
||||||
|
this.implementation!!.replaceInstructions(index, instructions.toInstructions(this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove smali instructions from the method.
|
||||||
|
* @param index The index to remove the instructions at.
|
||||||
|
* @param count The amount of instructions to remove.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.removeInstructions(index: Int, count: Int) =
|
||||||
|
this.implementation!!.removeInstructions(index, count)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -108,4 +158,21 @@ internal fun parametersEqual(
|
|||||||
internal val nullOutputStream: OutputStream =
|
internal val nullOutputStream: OutputStream =
|
||||||
object : OutputStream() {
|
object : OutputStream() {
|
||||||
override fun write(b: Int) {}
|
override fun write(b: Int) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be used to parse a list of parameters represented by their first letter,
|
||||||
|
* or in the case of arrays prefixed with an unspecified amount of '[' character.
|
||||||
|
*/
|
||||||
|
internal fun String.parseParameters(): List<String> {
|
||||||
|
val parameters = mutableListOf<String>()
|
||||||
|
var parameter = ""
|
||||||
|
for (char in this.toCharArray()) {
|
||||||
|
parameter += char
|
||||||
|
if (char == '[') continue
|
||||||
|
|
||||||
|
parameters.add(parameter)
|
||||||
|
parameter = ""
|
||||||
|
}
|
||||||
|
return parameters
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ReVanced fingerprint.
|
||||||
|
* Can be a [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
interface Fingerprint
|
@@ -1,11 +1,11 @@
|
|||||||
package app.revanced.patcher.signature.implementation.method.annotation
|
package app.revanced.patcher.fingerprint.method.annotation
|
||||||
|
|
||||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotations for a method which matches to a [MethodSignature].
|
* Annotations for a method which matches to a [MethodFingerprint].
|
||||||
* @param definingClass The defining class name of the method.
|
* @param definingClass The defining class name of the method.
|
||||||
* @param name A suggestive name for the method which the [MethodSignature] was created for.
|
* @param name A suggestive name for the method which the [MethodFingerprint] was created for.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@@ -15,7 +15,7 @@ annotation class MatchingMethod(
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotations to scan a pattern [MethodSignature] with fuzzy algorithm.
|
* Annotations to scan a pattern [MethodFingerprint] with fuzzy algorithm.
|
||||||
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@@ -25,7 +25,7 @@ annotation class FuzzyPatternScanMethod(
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotations to scan a pattern [MethodSignature] directly.
|
* Annotations to scan a pattern [MethodFingerprint] directly.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
@@ -0,0 +1,98 @@
|
|||||||
|
package app.revanced.patcher.fingerprint.method.impl
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.impl.BytecodeData
|
||||||
|
import app.revanced.patcher.data.impl.MethodNotFoundException
|
||||||
|
import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
|
||||||
|
import app.revanced.patcher.extensions.softCompareTo
|
||||||
|
import app.revanced.patcher.fingerprint.Fingerprint
|
||||||
|
import app.revanced.patcher.fingerprint.method.utils.MethodFingerprintUtils
|
||||||
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
|
import org.jf.dexlib2.Opcode
|
||||||
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
|
import org.jf.dexlib2.iface.Method
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the [MethodFingerprint] for a method.
|
||||||
|
* @param returnType The return type of the method.
|
||||||
|
* @param access The access flags of the method.
|
||||||
|
* @param parameters The parameters of the method.
|
||||||
|
* @param opcodes The list of opcodes of the method.
|
||||||
|
* @param strings A list of strings which a method contains.
|
||||||
|
* @param customFingerprint A custom condition for this fingerprint.
|
||||||
|
* A `null` opcode is equals to an unknown opcode.
|
||||||
|
*/
|
||||||
|
abstract class MethodFingerprint(
|
||||||
|
internal val returnType: String?,
|
||||||
|
internal val access: Int?,
|
||||||
|
internal val parameters: Iterable<String>?,
|
||||||
|
internal val opcodes: Iterable<Opcode?>?,
|
||||||
|
internal val strings: Iterable<String>? = null,
|
||||||
|
internal val customFingerprint: ((methodDef: Method) -> Boolean)? = null
|
||||||
|
) : Fingerprint {
|
||||||
|
/**
|
||||||
|
* The result of the [MethodFingerprint] the [Method].
|
||||||
|
* @throws MethodNotFoundException If the resolution of the [Method] has not happened.
|
||||||
|
*/
|
||||||
|
var result: MethodFingerprintResult? = null
|
||||||
|
get() = field ?: throw Exception("${this.name} has not been resolved yet.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the result of a [MethodFingerprintUtils].
|
||||||
|
* @param method The matching method.
|
||||||
|
* @param classDef The [ClassDef] that contains the matching [method].
|
||||||
|
* @param patternScanResult Opcodes pattern scan result.
|
||||||
|
* @param data The [BytecodeData] this [MethodFingerprintResult] is attached to, to create proxies.
|
||||||
|
*/
|
||||||
|
data class MethodFingerprintResult(
|
||||||
|
val method: Method,
|
||||||
|
val classDef: ClassDef,
|
||||||
|
val patternScanResult: PatternScanResult?,
|
||||||
|
internal val data: BytecodeData
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Returns a mutable clone of [classDef]
|
||||||
|
*
|
||||||
|
* Please note, this method allocates a [ClassProxy].
|
||||||
|
* Use [classDef] where possible.
|
||||||
|
*/
|
||||||
|
val mutableClass by lazy { data.proxy(classDef).resolve() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a mutable clone of [method]
|
||||||
|
*
|
||||||
|
* Please note, this method allocates a [ClassProxy].
|
||||||
|
* Use [method] where possible.
|
||||||
|
*/
|
||||||
|
val mutableMethod by lazy {
|
||||||
|
mutableClass.methods.first {
|
||||||
|
it.softCompareTo(this.method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a pattern scan.
|
||||||
|
* @param startIndex The start index of the instructions where to which this pattern matches.
|
||||||
|
* @param endIndex The end index of the instructions where to which this pattern matches.
|
||||||
|
* @param warnings A list of warnings considering this [PatternScanResult].
|
||||||
|
*/
|
||||||
|
data class PatternScanResult(
|
||||||
|
val startIndex: Int,
|
||||||
|
val endIndex: Int,
|
||||||
|
var warnings: List<Warning>? = null
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Represents warnings of the pattern scan.
|
||||||
|
* @param correctOpcode The opcode the instruction list has.
|
||||||
|
* @param wrongOpcode The opcode the pattern list of the signature currently has.
|
||||||
|
* @param instructionIndex The index of the opcode relative to the instruction list.
|
||||||
|
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
|
||||||
|
*/
|
||||||
|
data class Warning(
|
||||||
|
val correctOpcode: Opcode,
|
||||||
|
val wrongOpcode: Opcode,
|
||||||
|
val instructionIndex: Int,
|
||||||
|
val patternIndex: Int,
|
||||||
|
)
|
||||||
|
}
|
@@ -0,0 +1,158 @@
|
|||||||
|
package app.revanced.patcher.fingerprint.method.utils
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.impl.BytecodeData
|
||||||
|
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
||||||
|
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold
|
||||||
|
import app.revanced.patcher.extensions.parametersEqual
|
||||||
|
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.PatternScanResult
|
||||||
|
import org.jf.dexlib2.Opcode
|
||||||
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
|
import org.jf.dexlib2.iface.Method
|
||||||
|
import org.jf.dexlib2.iface.instruction.Instruction
|
||||||
|
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
|
import org.jf.dexlib2.iface.reference.StringReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for [MethodFingerprint]
|
||||||
|
*/
|
||||||
|
object MethodFingerprintUtils {
|
||||||
|
/**
|
||||||
|
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
||||||
|
* @param context The classes on which to resolve the [MethodFingerprint].
|
||||||
|
* @param forData The [BytecodeData] to host proxies.
|
||||||
|
* @return True if the resolution was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun Iterable<MethodFingerprint>.resolve(forData: BytecodeData, context: Iterable<ClassDef>) {
|
||||||
|
for (fingerprint in this) // For each fingerprint
|
||||||
|
classes@ for (classDef in context) // search through all classes for the fingerprint
|
||||||
|
if (fingerprint.resolve(forData, classDef))
|
||||||
|
break@classes // if the resolution succeeded, continue with the next fingerprint
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] against a [ClassDef].
|
||||||
|
* @param context The class on which to resolve the [MethodFingerprint].
|
||||||
|
* @param forData The [BytecodeData] to host proxies.
|
||||||
|
* @return True if the resolution was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.resolve(forData: BytecodeData, context: ClassDef): Boolean {
|
||||||
|
for (method in context.methods)
|
||||||
|
if (this.resolve(forData, method, context))
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] against a [Method].
|
||||||
|
* @param context The context on which to resolve the [MethodFingerprint].
|
||||||
|
* @param classDef The class of the matching [Method].
|
||||||
|
* @param forData The [BytecodeData] to host proxies.
|
||||||
|
* @return True if the resolution was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.resolve(forData: BytecodeData, context: Method, classDef: ClassDef): Boolean {
|
||||||
|
val methodFingerprint = this
|
||||||
|
|
||||||
|
if (methodFingerprint.returnType != null && !context.returnType.startsWith(methodFingerprint.returnType))
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (methodFingerprint.access != null && methodFingerprint.access != context.accessFlags)
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
if (methodFingerprint.parameters != null && !parametersEqual(
|
||||||
|
methodFingerprint.parameters, // TODO: parseParameters()
|
||||||
|
context.parameterTypes
|
||||||
|
)
|
||||||
|
) return false
|
||||||
|
|
||||||
|
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(context))
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (methodFingerprint.strings != null) {
|
||||||
|
val implementation = context.implementation ?: return false
|
||||||
|
|
||||||
|
val stringsList = methodFingerprint.strings.toMutableList()
|
||||||
|
|
||||||
|
implementation.instructions.forEach { instruction ->
|
||||||
|
if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEach
|
||||||
|
|
||||||
|
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||||
|
val index = stringsList.indexOfFirst { it == string }
|
||||||
|
if (index != -1) stringsList.removeAt(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stringsList.isNotEmpty()) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
||||||
|
context.implementation?.instructions ?: return false
|
||||||
|
|
||||||
|
context.patternScan(methodFingerprint) ?: return false
|
||||||
|
} else null
|
||||||
|
|
||||||
|
methodFingerprint.result = MethodFingerprintResult(context, classDef, patternScanResult, forData)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Method.patternScan(
|
||||||
|
fingerprint: MethodFingerprint
|
||||||
|
): PatternScanResult? {
|
||||||
|
val instructions = this.implementation!!.instructions
|
||||||
|
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyScanThreshold
|
||||||
|
|
||||||
|
val pattern = fingerprint.opcodes!!
|
||||||
|
val instructionLength = instructions.count()
|
||||||
|
val patternLength = pattern.count()
|
||||||
|
|
||||||
|
for (index in 0 until instructionLength) {
|
||||||
|
var patternIndex = 0
|
||||||
|
var threshold = fingerprintFuzzyPatternScanThreshold
|
||||||
|
|
||||||
|
while (index + patternIndex < instructionLength) {
|
||||||
|
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
||||||
|
val patternOpcode = pattern.elementAt(patternIndex)
|
||||||
|
|
||||||
|
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
||||||
|
// reaching maximum threshold (0) means,
|
||||||
|
// the pattern does not match to the current instructions
|
||||||
|
if (threshold-- == 0) break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patternIndex < patternLength - 1) {
|
||||||
|
// if the entire pattern has not been scanned yet
|
||||||
|
// continue the scan
|
||||||
|
patternIndex++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
|
||||||
|
val result = PatternScanResult(index, index + patternIndex)
|
||||||
|
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
|
||||||
|
result.warnings = result.createWarnings(pattern, instructions)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PatternScanResult.createWarnings(
|
||||||
|
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
|
||||||
|
) = buildList {
|
||||||
|
for ((patternIndex, instructionIndex) in (this@createWarnings.startIndex until this@createWarnings.endIndex).withIndex()) {
|
||||||
|
val originalOpcode = instructions.elementAt(instructionIndex).opcode
|
||||||
|
val patternOpcode = pattern.elementAt(patternIndex)
|
||||||
|
|
||||||
|
if (patternOpcode == null || patternOpcode.ordinal == originalOpcode.ordinal) continue
|
||||||
|
|
||||||
|
this.add(PatternScanResult.Warning(originalOpcode, patternOpcode, instructionIndex, patternIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private operator fun ClassDef.component1() = this
|
||||||
|
private operator fun ClassDef.component2() = this.methods
|
18
src/main/kotlin/app/revanced/patcher/patch/Patch.kt
Normal file
18
src/main/kotlin/app/revanced/patcher/patch/Patch.kt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.Data
|
||||||
|
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||||
|
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ReVanced patch.
|
||||||
|
* Can either be a [ResourcePatch] or a [BytecodePatch].
|
||||||
|
*/
|
||||||
|
abstract class Patch<out T : Data> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main function of the [Patch] which the patcher will call.
|
||||||
|
*/
|
||||||
|
abstract fun execute(data: @UnsafeVariance T): PatchResult
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package app.revanced.patcher.patch.implementation.misc
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
interface PatchResult {
|
interface PatchResult {
|
||||||
fun error(): PatchResultError? {
|
fun error(): PatchResultError? {
|
@@ -1,7 +1,7 @@
|
|||||||
package app.revanced.patcher.patch.annotations
|
package app.revanced.patcher.patch.annotations
|
||||||
|
|
||||||
import app.revanced.patcher.data.base.Data
|
import app.revanced.patcher.data.Data
|
||||||
import app.revanced.patcher.patch.base.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,19 +0,0 @@
|
|||||||
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.misc.PatchResult
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A ReVanced patch.
|
|
||||||
* Can either be a [ResourcePatch] or a [BytecodePatch].
|
|
||||||
*/
|
|
||||||
abstract class Patch<out T : Data> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main function of the [Patch] which the patcher will call.
|
|
||||||
*/
|
|
||||||
abstract fun execute(data: @UnsafeVariance T): PatchResult
|
|
||||||
}
|
|
@@ -0,0 +1,13 @@
|
|||||||
|
package app.revanced.patcher.patch.impl
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.impl.BytecodeData
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bytecode patch for the Patcher.
|
||||||
|
* @param fingerprints A list of [MethodFingerprint] this patch relies on.
|
||||||
|
*/
|
||||||
|
abstract class BytecodePatch(
|
||||||
|
internal val fingerprints: Iterable<MethodFingerprint>
|
||||||
|
) : Patch<BytecodeData>()
|
@@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.patcher.patch.impl
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.impl.ResourceData
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource patch for the Patcher.
|
||||||
|
*/
|
||||||
|
abstract class ResourcePatch : Patch<ResourceData>()
|
@@ -1,13 +0,0 @@
|
|||||||
package app.revanced.patcher.patch.implementation
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.implementation.BytecodeData
|
|
||||||
import app.revanced.patcher.patch.base.Patch
|
|
||||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bytecode patch for the Patcher.
|
|
||||||
* @param signatures A list of [MethodSignature] this patch relies on.
|
|
||||||
*/
|
|
||||||
abstract class BytecodePatch(
|
|
||||||
internal val signatures: Iterable<MethodSignature>
|
|
||||||
) : Patch<BytecodeData>()
|
|
@@ -1,9 +0,0 @@
|
|||||||
package app.revanced.patcher.patch.implementation
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.implementation.ResourceData
|
|
||||||
import app.revanced.patcher.patch.base.Patch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource patch for the Patcher.
|
|
||||||
*/
|
|
||||||
abstract class ResourcePatch : Patch<ResourceData>()
|
|
@@ -1,9 +0,0 @@
|
|||||||
package app.revanced.patcher.signature.base
|
|
||||||
|
|
||||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A ReVanced signature.
|
|
||||||
* Can be a [MethodSignature].
|
|
||||||
*/
|
|
||||||
interface Signature
|
|
@@ -1,33 +0,0 @@
|
|||||||
package app.revanced.patcher.signature.implementation.method
|
|
||||||
|
|
||||||
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.implementation.method.resolver.SignatureResolverResult
|
|
||||||
import org.jf.dexlib2.Opcode
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the [MethodSignature] for a method.
|
|
||||||
* @param returnType The return type of the method.
|
|
||||||
* @param accessFlags The access flags of the method.
|
|
||||||
* @param methodParameters The parameters of the method.
|
|
||||||
* @param opcodes The list of opcodes of the method.
|
|
||||||
* @param strings A list of strings which a method contains.
|
|
||||||
* A `null` opcode is equals to an unknown opcode.
|
|
||||||
*/
|
|
||||||
abstract class MethodSignature(
|
|
||||||
internal val returnType: String?,
|
|
||||||
internal val accessFlags: Int?,
|
|
||||||
internal val methodParameters: Iterable<String>?,
|
|
||||||
internal val opcodes: Iterable<Opcode?>?,
|
|
||||||
internal val strings: Iterable<String>? = null
|
|
||||||
) : Signature {
|
|
||||||
/**
|
|
||||||
* The result of the signature
|
|
||||||
*/
|
|
||||||
var result: SignatureResolverResult? = null
|
|
||||||
@Throws(MethodNotFoundException::class)
|
|
||||||
get() {
|
|
||||||
return field ?: throw MethodNotFoundException("Could not resolve required signature ${this.name}")
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,162 +0,0 @@
|
|||||||
package app.revanced.patcher.signature.implementation.method.resolver
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.PatcherData
|
|
||||||
import app.revanced.patcher.data.implementation.proxy
|
|
||||||
import app.revanced.patcher.extensions.MethodSignatureExtensions.fuzzyThreshold
|
|
||||||
import app.revanced.patcher.extensions.parametersEqual
|
|
||||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
|
||||||
import org.jf.dexlib2.Opcode
|
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
|
||||||
import org.jf.dexlib2.iface.Method
|
|
||||||
import org.jf.dexlib2.iface.instruction.Instruction
|
|
||||||
import org.jf.dexlib2.iface.instruction.formats.Instruction21c
|
|
||||||
import org.jf.dexlib2.iface.reference.StringReference
|
|
||||||
|
|
||||||
internal class MethodSignatureResolver(
|
|
||||||
private val classes: List<ClassDef>,
|
|
||||||
private val methodSignatures: Iterable<MethodSignature>
|
|
||||||
) {
|
|
||||||
fun resolve(patcherData: PatcherData) {
|
|
||||||
for (signature in methodSignatures) {
|
|
||||||
for (classDef in classes) {
|
|
||||||
for (method in classDef.methods) {
|
|
||||||
val patternScanData = compareSignatureToMethod(signature, method) ?: continue
|
|
||||||
|
|
||||||
// create class proxy, in case a patch needs mutability
|
|
||||||
val classProxy = patcherData.bytecodeData.proxy(classDef)
|
|
||||||
signature.result = SignatureResolverResult(
|
|
||||||
classProxy,
|
|
||||||
patternScanData,
|
|
||||||
method,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// These functions do not require the constructor values, so they can be static.
|
|
||||||
companion object {
|
|
||||||
fun resolveFromProxy(
|
|
||||||
classProxy: app.revanced.patcher.util.proxy.ClassProxy,
|
|
||||||
signature: MethodSignature
|
|
||||||
): SignatureResolverResult? {
|
|
||||||
for (method in classProxy.immutableClass.methods) {
|
|
||||||
val result = compareSignatureToMethod(signature, method) ?: continue
|
|
||||||
return SignatureResolverResult(
|
|
||||||
classProxy,
|
|
||||||
result,
|
|
||||||
method,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun compareSignatureToMethod(
|
|
||||||
signature: MethodSignature,
|
|
||||||
method: Method
|
|
||||||
): PatternScanResult? {
|
|
||||||
signature.returnType?.let {
|
|
||||||
if (!method.returnType.startsWith(signature.returnType)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
signature.accessFlags?.let {
|
|
||||||
if (signature.accessFlags != method.accessFlags) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
signature.methodParameters?.let {
|
|
||||||
if (!parametersEqual(signature.methodParameters, method.parameterTypes)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
signature.strings?.let { strings ->
|
|
||||||
method.implementation ?: return null
|
|
||||||
|
|
||||||
val stringsList = strings.toMutableList()
|
|
||||||
|
|
||||||
for (instruction in method.implementation!!.instructions) {
|
|
||||||
if (instruction.opcode != Opcode.CONST_STRING) continue
|
|
||||||
|
|
||||||
val string = ((instruction as Instruction21c).reference as StringReference).string
|
|
||||||
val i = stringsList.indexOfFirst { it == string }
|
|
||||||
if (i != -1) stringsList.removeAt(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stringsList.isNotEmpty()) return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (signature.opcodes == null) {
|
|
||||||
PatternScanResult(0, 0)
|
|
||||||
} else {
|
|
||||||
method.implementation?.instructions?.let {
|
|
||||||
compareOpcodes(signature, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun compareOpcodes(
|
|
||||||
signature: MethodSignature,
|
|
||||||
instructions: Iterable<Instruction>
|
|
||||||
): PatternScanResult? {
|
|
||||||
val count = instructions.count()
|
|
||||||
val pattern = signature.opcodes!!
|
|
||||||
val size = pattern.count()
|
|
||||||
|
|
||||||
val threshold = signature.fuzzyThreshold
|
|
||||||
|
|
||||||
for (instructionIndex in 0 until count) {
|
|
||||||
var patternIndex = 0
|
|
||||||
var currentThreshold = threshold
|
|
||||||
while (instructionIndex + patternIndex < count) {
|
|
||||||
val originalOpcode = instructions.elementAt(instructionIndex + patternIndex).opcode
|
|
||||||
val patternOpcode = pattern.elementAt(patternIndex)
|
|
||||||
if (
|
|
||||||
patternOpcode != null && // unknown opcode
|
|
||||||
originalOpcode != patternOpcode &&
|
|
||||||
currentThreshold-- == 0
|
|
||||||
) break
|
|
||||||
if (++patternIndex < size) continue
|
|
||||||
patternIndex-- // fix pattern offset
|
|
||||||
|
|
||||||
val result = PatternScanResult(instructionIndex, instructionIndex + patternIndex)
|
|
||||||
|
|
||||||
result.warnings = generateWarnings(signature, instructions, result)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateWarnings(
|
|
||||||
signature: MethodSignature,
|
|
||||||
instructions: Iterable<Instruction>,
|
|
||||||
scanResult: PatternScanResult,
|
|
||||||
) = buildList {
|
|
||||||
val pattern = signature.opcodes!!
|
|
||||||
for ((patternIndex, instructionIndex) in (scanResult.startIndex until scanResult.endIndex).withIndex()) {
|
|
||||||
val correctOpcode = instructions.elementAt(instructionIndex).opcode
|
|
||||||
val patternOpcode = pattern.elementAt(patternIndex)
|
|
||||||
if (
|
|
||||||
patternOpcode != null && // unknown opcode
|
|
||||||
correctOpcode != patternOpcode
|
|
||||||
) {
|
|
||||||
this.add(
|
|
||||||
PatternScanResult.Warning(
|
|
||||||
correctOpcode, patternOpcode,
|
|
||||||
instructionIndex, patternIndex,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private operator fun ClassDef.component1() = this
|
|
||||||
private operator fun ClassDef.component2() = this.methods
|
|
@@ -1,75 +0,0 @@
|
|||||||
package app.revanced.patcher.signature.implementation.method.resolver
|
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.softCompareTo
|
|
||||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
|
||||||
import app.revanced.patcher.util.proxy.ClassProxy
|
|
||||||
import org.jf.dexlib2.Opcode
|
|
||||||
import org.jf.dexlib2.iface.Method
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the result of a [MethodSignatureResolver].
|
|
||||||
* @param definingClassProxy The [ClassProxy] that the matching method was found in.
|
|
||||||
* @param resolvedMethod The actual matching method.
|
|
||||||
* @param scanResult Opcodes pattern scan result.
|
|
||||||
*/
|
|
||||||
data class SignatureResolverResult(
|
|
||||||
val definingClassProxy: ClassProxy,
|
|
||||||
val scanResult: PatternScanResult,
|
|
||||||
private val resolvedMethod: Method,
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Returns the **mutable** method by the [resolvedMethod] from the [definingClassProxy].
|
|
||||||
*
|
|
||||||
* Please note, this method allocates a [ClassProxy].
|
|
||||||
* Use [immutableMethod] where possible.
|
|
||||||
*/
|
|
||||||
val method
|
|
||||||
get() = definingClassProxy.resolve().methods.first {
|
|
||||||
it.softCompareTo(resolvedMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the **immutable** method by the [resolvedMethod] from the [definingClassProxy].
|
|
||||||
*
|
|
||||||
* If you need to modify the method, use [method] instead.
|
|
||||||
*/
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
|
||||||
val immutableMethod: Method
|
|
||||||
get() = definingClassProxy.immutableClass.methods.first {
|
|
||||||
it.softCompareTo(resolvedMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findParentMethod(signature: MethodSignature): SignatureResolverResult? {
|
|
||||||
return MethodSignatureResolver.resolveFromProxy(definingClassProxy, signature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class PatternScanResult(
|
|
||||||
val startIndex: Int,
|
|
||||||
val endIndex: Int
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* A list of warnings the resolver found.
|
|
||||||
*
|
|
||||||
* This list will be allocated when the signature has been found.
|
|
||||||
* Meaning, if the signature was not found,
|
|
||||||
* or the signature was not yet resolved,
|
|
||||||
* the list will be null.
|
|
||||||
*/
|
|
||||||
var warnings: List<Warning>? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a resolver warning.
|
|
||||||
* @param correctOpcode The opcode the instruction list has.
|
|
||||||
* @param wrongOpcode The opcode the pattern list of the signature currently has.
|
|
||||||
* @param instructionIndex The index of the opcode relative to the instruction list.
|
|
||||||
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
|
|
||||||
*/
|
|
||||||
data class Warning(
|
|
||||||
val correctOpcode: Opcode,
|
|
||||||
val wrongOpcode: Opcode,
|
|
||||||
val instructionIndex: Int,
|
|
||||||
val patternIndex: Int,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user