mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-19 09:23:56 +00:00
Compare commits
62 Commits
v1.3.4
...
v2.2.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f00d33f4e | ||
|
|
83187c9edd | ||
|
|
79d70cff4b | ||
|
|
6f72c4c4c0 | ||
|
|
c8eedac4d9 | ||
|
|
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 | ||
|
|
1f4bc5079f | ||
|
|
caf2745805 | ||
|
|
a4529c3fee | ||
|
|
835c0f9f7a | ||
|
|
34f607aa24 | ||
|
|
0f38b94701 | ||
|
|
39bb1b25dc | ||
|
|
4fc63a4d8a | ||
|
|
6037397bc2 | ||
|
|
273dd86b65 | ||
|
|
e8488b3e86 | ||
|
|
c13361823d | ||
|
|
8f66f9f606 | ||
|
|
a123026f46 | ||
|
|
9c39c9efdb | ||
|
|
3ee1c01430 | ||
|
|
64bae884dc | ||
|
|
e94a706949 | ||
|
|
89bb43066b | ||
|
|
68174bbd6b |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -28,8 +28,6 @@ jobs:
|
|||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
- name: Make gradlew executable
|
|
||||||
run: chmod +x gradlew
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
152
CHANGELOG.md
152
CHANGELOG.md
@@ -1,3 +1,155 @@
|
|||||||
|
# [2.2.0-dev.3](https://github.com/revanced/revanced-patcher/compare/v2.2.0-dev.2...v2.2.0-dev.3) (2022-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* DomFileEditor opening in- and output streams on the same file ([83187c9](https://github.com/revanced/revanced-patcher/commit/83187c9edd7b088bc18960c5eb9a2042ca536b5f))
|
||||||
|
|
||||||
|
# [2.2.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v2.2.0-dev.1...v2.2.0-dev.2) (2022-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* streams overload for `XmlFileHolder` ([6f72c4c](https://github.com/revanced/revanced-patcher/commit/6f72c4c4c051e48c8d03d2a7b2cfc1c53028ed86))
|
||||||
|
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* logging class ([caf2745](https://github.com/revanced/revanced-patcher/commit/caf2745805ffd4b59fa81e79cc489b1a1a5c5d89))
|
||||||
|
|
||||||
|
## [1.7.2](https://github.com/revanced/revanced-patcher/compare/v1.7.1...v1.7.2) (2022-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add execute permission to `./gradlew` file ([#46](https://github.com/revanced/revanced-patcher/issues/46)) ([34f607a](https://github.com/revanced/revanced-patcher/commit/34f607aa24d89a777d906cc887203f343ce3fd07))
|
||||||
|
|
||||||
|
## [1.7.1](https://github.com/revanced/revanced-patcher/compare/v1.7.0...v1.7.1) (2022-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
* revert "feat: use of `java.util.logging.Logger`" ([e8488b3](https://github.com/revanced/revanced-patcher/commit/e8488b3e86e0132011824f8ecba29e64f8db0573))
|
||||||
|
|
||||||
|
# [1.7.0](https://github.com/revanced/revanced-patcher/compare/v1.6.0...v1.7.0) (2022-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* migrate logger to `slf4j` ([8f66f9f](https://github.com/revanced/revanced-patcher/commit/8f66f9f606a785ac947b0e553822877f211d82df))
|
||||||
|
|
||||||
|
# [1.6.0](https://github.com/revanced/revanced-patcher/compare/v1.5.0...v1.6.0) (2022-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* use of `java.util.logging.Logger` ([9c39c9e](https://github.com/revanced/revanced-patcher/commit/9c39c9efdb5d48ddaffce7f711c275e732b0b2d9))
|
||||||
|
|
||||||
|
# [1.5.0](https://github.com/revanced/revanced-patcher/compare/v1.4.0...v1.5.0) (2022-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* use streams to write the dex files ([64bae88](https://github.com/revanced/revanced-patcher/commit/64bae884dcb72550a3218e149f3ca0fd0ca03aaf))
|
||||||
|
|
||||||
|
# [1.4.0](https://github.com/revanced/revanced-patcher/compare/v1.3.4...v1.4.0) (2022-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* return a `File` instance instead of `ExtFile` ([68174bb](https://github.com/revanced/revanced-patcher/commit/68174bbd6b4df47a91b610c2b97dbae55b594163))
|
||||||
|
|
||||||
## [1.3.4](https://github.com/revanced/revanced-patcher/compare/v1.3.3...v1.3.4) (2022-06-21)
|
## [1.3.4](https://github.com/revanced/revanced-patcher/compare/v1.3.3...v1.3.4) (2022-06-21)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,13 +21,10 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(kotlin("stdlib"))
|
implementation("xpp3:xpp3:1.1.4c")
|
||||||
implementation(kotlin("reflect"))
|
implementation("org.smali:smali:2.5.2")
|
||||||
|
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
||||||
api("xpp3:xpp3:1.1.4c")
|
implementation("org.apktool:apktool-lib:2.6.9-SNAPSHOT")
|
||||||
api("app.revanced:multidexlib2:2.5.2.r2")
|
|
||||||
api("org.smali:smali:2.5.2")
|
|
||||||
api("org.apktool:apktool-lib:2.6.5-SNAPSHOT")
|
|
||||||
|
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
}
|
}
|
||||||
@@ -47,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")
|
||||||
@@ -55,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.3.4
|
version = 2.2.0-dev.3
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -42,13 +41,18 @@ val NAMER = BasicDexFileNamer()
|
|||||||
* @param options The options for the patcher.
|
* @param options The options for the patcher.
|
||||||
*/
|
*/
|
||||||
class Patcher(private val options: PatcherOptions) {
|
class Patcher(private val options: PatcherOptions) {
|
||||||
val data: PatcherData
|
private val logger = options.logger
|
||||||
private val opcodes: Opcodes
|
private val opcodes: Opcodes
|
||||||
|
|
||||||
|
val data: PatcherData
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val extInputFile = ExtFile(options.inputFile)
|
val extInputFile = ExtFile(options.inputFile)
|
||||||
val outDir = File(options.resourceCacheDirectory)
|
val outDir = File(options.resourceCacheDirectory)
|
||||||
if (outDir.exists()) outDir.deleteRecursively()
|
if (outDir.exists()) {
|
||||||
|
logger.info("Deleting existing resource cache directory")
|
||||||
|
outDir.deleteRecursively()
|
||||||
|
}
|
||||||
outDir.mkdirs()
|
outDir.mkdirs()
|
||||||
|
|
||||||
val androlib = Androlib(BuildOptions().also { it.setBuildOptions(options) })
|
val androlib = Androlib(BuildOptions().also { it.setBuildOptions(options) })
|
||||||
@@ -57,6 +61,8 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
val packageMetadata = PackageMetadata()
|
val packageMetadata = PackageMetadata()
|
||||||
|
|
||||||
if (options.patchResources) {
|
if (options.patchResources) {
|
||||||
|
logger.info("Decoding resources")
|
||||||
|
|
||||||
// decode resources to cache directory
|
// decode resources to cache directory
|
||||||
androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable)
|
androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable)
|
||||||
androlib.decodeResourcesFull(extInputFile, outDir, resourceTable)
|
androlib.decodeResourcesFull(extInputFile, outDir, resourceTable)
|
||||||
@@ -71,6 +77,8 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
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()
|
||||||
decoder.currentPackage = ResPackage(resourceTable, 0, null)
|
decoder.currentPackage = ResPackage(resourceTable, 0, null)
|
||||||
@@ -93,6 +101,8 @@ 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 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)
|
||||||
// get the opcodes
|
// get the opcodes
|
||||||
@@ -111,23 +121,36 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
* @param throwOnDuplicates If this is set to true, the patcher will throw an exception if a duplicate class has been found.
|
* @param throwOnDuplicates If this is set to true, the patcher will throw an exception if a duplicate class has been found.
|
||||||
*/
|
*/
|
||||||
fun addFiles(
|
fun addFiles(
|
||||||
files: List<File>, allowedOverwrites: Iterable<String> = emptyList(), throwOnDuplicates: Boolean = false
|
files: List<File>,
|
||||||
|
allowedOverwrites: Iterable<String> = emptyList(),
|
||||||
|
throwOnDuplicates: Boolean = false,
|
||||||
|
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 e = data.bytecodeData.classes.internalClasses.findIndexed { it.type == classDef.type }
|
val type = classDef.type
|
||||||
if (e != null) {
|
|
||||||
if (throwOnDuplicates) {
|
val existingClass = data.bytecodeData.classes.internalClasses.findIndexed { it.type == type }
|
||||||
throw Exception("Class ${classDef.type} has already been added to the patcher.")
|
if (existingClass == null) {
|
||||||
}
|
if (throwOnDuplicates) throw Exception("Class $type has already been added to the patcher")
|
||||||
val (_, idx) = e
|
|
||||||
if (allowedOverwrites.contains(classDef.type)) {
|
logger.trace("Merging $type")
|
||||||
data.bytecodeData.classes.internalClasses[idx] = classDef
|
data.bytecodeData.classes.internalClasses.add(classDef)
|
||||||
}
|
modified = true
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
data.bytecodeData.classes.internalClasses.add(classDef)
|
|
||||||
|
if (!allowedOverwrites.contains(type)) continue
|
||||||
|
|
||||||
|
logger.trace("Overwriting $type")
|
||||||
|
|
||||||
|
val index = existingClass.second
|
||||||
|
data.bytecodeData.classes.internalClasses[index] = classDef
|
||||||
|
modified = true
|
||||||
}
|
}
|
||||||
|
if (modified) callback(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +160,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
fun save(): PatcherResult {
|
fun save(): PatcherResult {
|
||||||
val packageMetadata = data.packageMetadata
|
val packageMetadata = data.packageMetadata
|
||||||
val metaInfo = packageMetadata.metaInfo
|
val metaInfo = packageMetadata.metaInfo
|
||||||
var resourceFile: ExtFile? = null
|
var resourceFile: File? = null
|
||||||
|
|
||||||
if (options.patchResources) {
|
if (options.patchResources) {
|
||||||
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
|
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
|
||||||
@@ -168,19 +191,19 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
val resDirectory = cacheDirectory.resolve("res")
|
val resDirectory = cacheDirectory.resolve("res")
|
||||||
val includedFiles = metaInfo.usesFramework.ids.map { id ->
|
val includedFiles = metaInfo.usesFramework.ids.map { id ->
|
||||||
androlibResources.getFrameworkApk(
|
androlibResources.getFrameworkApk(
|
||||||
id,
|
id, metaInfo.usesFramework.tag
|
||||||
metaInfo.usesFramework.tag
|
|
||||||
)
|
)
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
|
|
||||||
|
logger.info("Compiling resources")
|
||||||
androlibResources.aaptPackage(
|
androlibResources.aaptPackage(
|
||||||
aaptFile, manifestFile, resDirectory, null,
|
aaptFile, manifestFile, resDirectory, null, null, includedFiles
|
||||||
null, includedFiles
|
|
||||||
)
|
)
|
||||||
|
|
||||||
resourceFile = ExtFile(aaptFile)
|
resourceFile = aaptFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.trace("Creating new dex file")
|
||||||
val newDexFile = object : DexFile {
|
val newDexFile = object : DexFile {
|
||||||
override fun getClasses(): Set<ClassDef> {
|
override fun getClasses(): Set<ClassDef> {
|
||||||
data.bytecodeData.classes.applyProxies()
|
data.bytecodeData.classes.applyProxies()
|
||||||
@@ -192,7 +215,8 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// write dex modified files
|
// write modified dex files
|
||||||
|
logger.info("Writing modified dex files")
|
||||||
val dexFiles = mutableMapOf<String, MemoryDataStore>()
|
val dexFiles = mutableMapOf<String, MemoryDataStore>()
|
||||||
MultiDexIO.writeDexFile(
|
MultiDexIO.writeDexFile(
|
||||||
true, -1, // core count
|
true, -1, // core count
|
||||||
@@ -201,10 +225,8 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
|
|
||||||
return PatcherResult(
|
return PatcherResult(
|
||||||
dexFiles.map {
|
dexFiles.map {
|
||||||
app.revanced.patcher.util.dex.DexFile(it.key, it.value)
|
app.revanced.patcher.util.dex.DexFile(it.key, it.value.readAt(0))
|
||||||
},
|
}, metaInfo.doNotCompress.toList(), resourceFile
|
||||||
metaInfo.doNotCompress.toList(),
|
|
||||||
resourceFile
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,48 +241,62 @@ 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
|
||||||
|
|
||||||
// if the patch has already applied silently skip it
|
// if the patch has already applied silently skip it
|
||||||
if (appliedPatches.contains(patchName)) return PatchResultSuccess()
|
if (appliedPatches.contains(patchName)) {
|
||||||
appliedPatches.add(patchName)
|
if (!appliedPatches[patchName]!!)
|
||||||
|
return PatchResultError("'$patchName' did not succeed previously")
|
||||||
|
|
||||||
|
logger.trace("Skipping '$patchName' because it has already been applied")
|
||||||
|
|
||||||
|
return PatchResultSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
// 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()
|
||||||
|
|
||||||
// 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) return PatchResultError("$patchName is a resource patch, but resource patching is disabled.")
|
if (!options.patchResources && isResourcePatch) {
|
||||||
|
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 '$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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,30 +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: (String) -> Unit = {}
|
logger.trace("Applying all patches")
|
||||||
): Map<String, Result<PatchResultSuccess>> {
|
val appliedPatches = mutableMapOf<String, Boolean>() // first is success, second is name
|
||||||
val appliedPatches = mutableListOf<String>()
|
|
||||||
|
|
||||||
return buildMap {
|
for (patch in data.patches) {
|
||||||
for (patch in data.patches) {
|
val patchResult = applyPatch(patch, appliedPatches)
|
||||||
val result = applyPatch(patch, appliedPatches)
|
|
||||||
|
|
||||||
val name = patch.patchName
|
val result = if (patchResult.isSuccess()) {
|
||||||
callback(name)
|
Result.success(patchResult.success()!!)
|
||||||
|
} else {
|
||||||
this[name] = if (result.isSuccess()) {
|
Result.failure(patchResult.error()!!)
|
||||||
Result.success(result.success()!!)
|
|
||||||
} else {
|
|
||||||
Result.failure(result.error()!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stopOnError && result.isError()) break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yield(patch.patchName to result)
|
||||||
|
if (stopOnError && patchResult.isError()) break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,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,19 +1,23 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.logging.Logger
|
||||||
|
import app.revanced.patcher.logging.impl.NopLogger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for a patcher.
|
* Options for the [Patcher].
|
||||||
* @param inputFile The input file (usually an apk file).
|
* @param inputFile The input file (usually an apk file).
|
||||||
* @param resourceCacheDirectory Directory to cache resources.
|
* @param resourceCacheDirectory Directory to cache resources.
|
||||||
* @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
|
* @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
|
||||||
* @param aaptPath Optional path to a custom aapt binary.
|
* @param aaptPath Optional path to a custom aapt binary.
|
||||||
* @param frameworkFolderLocation Optional path to a custom framework folder.
|
* @param frameworkFolderLocation Optional path to a custom framework folder.
|
||||||
|
* @param logger Custom logger implementation for the [Patcher].
|
||||||
*/
|
*/
|
||||||
data class PatcherOptions(
|
data class PatcherOptions(
|
||||||
internal val inputFile: File,
|
internal val inputFile: File,
|
||||||
internal val resourceCacheDirectory: String,
|
internal val resourceCacheDirectory: String,
|
||||||
internal val patchResources: Boolean = false,
|
internal val patchResources: Boolean = false,
|
||||||
internal val aaptPath: String = "",
|
internal val aaptPath: String = "",
|
||||||
internal val frameworkFolderLocation: String? = null
|
internal val frameworkFolderLocation: String? = null,
|
||||||
|
internal val logger: Logger = NopLogger
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.util.dex.DexFile
|
import app.revanced.patcher.util.dex.DexFile
|
||||||
import brut.directory.ExtFile
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The result of a patcher.
|
* The result of a patcher.
|
||||||
* @param dexFiles The patched dex files.
|
* @param dexFiles The patched dex files.
|
||||||
* @param doNotCompress List of relative paths to files to exclude from compressing.
|
* @param doNotCompress List of relative paths to files to exclude from compressing.
|
||||||
* @param resourceFile ExtFile containing resources that need to be extracted into the APK.
|
* @param resourceFile File containing resources that need to be extracted into the APK.
|
||||||
*/
|
*/
|
||||||
data class PatcherResult(
|
data class PatcherResult(
|
||||||
val dexFiles: List<DexFile>,
|
val dexFiles: List<DexFile>,
|
||||||
val doNotCompress: List<String>? = null,
|
val doNotCompress: List<String>? = null,
|
||||||
val resourceFile: ExtFile?
|
val resourceFile: 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,42 @@
|
|||||||
|
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 java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
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> {
|
||||||
|
val xmlEditor = XmlFileHolder()
|
||||||
|
|
||||||
|
operator fun get(path: String) = resourceCacheDirectory.resolve(path)
|
||||||
|
|
||||||
|
override fun iterator() = resourceCacheDirectory.walkTopDown().iterator()
|
||||||
|
|
||||||
|
inner class XmlFileHolder {
|
||||||
|
operator fun get(inputStream: InputStream, outputStream: OutputStream) =
|
||||||
|
DomFileEditor(inputStream, lazyOf(outputStream))
|
||||||
|
|
||||||
|
operator fun get(path: String) = DomFileEditor(this@ResourceData[path])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomFileEditor internal constructor(inputStream: InputStream, private val outputStream: Lazy<OutputStream>) : Closeable {
|
||||||
|
|
||||||
|
// lazily open an output stream
|
||||||
|
// this is required because when constructing a DomFileEditor the output stream is created along with the input stream, which is not allowed
|
||||||
|
// the workaround is to lazily create the output stream. This way it would be used after the input stream is closed, which happens in the constructor
|
||||||
|
constructor(file: File) : this(file.inputStream(), lazy { file.outputStream() })
|
||||||
|
|
||||||
|
val file: Document =
|
||||||
|
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream).also(Document::normalize)
|
||||||
|
|
||||||
|
override fun close() =
|
||||||
|
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), StreamResult(outputStream.value))
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
8
src/main/kotlin/app/revanced/patcher/logging/Logger.kt
Normal file
8
src/main/kotlin/app/revanced/patcher/logging/Logger.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package app.revanced.patcher.logging
|
||||||
|
|
||||||
|
interface Logger {
|
||||||
|
fun error(msg: String) {}
|
||||||
|
fun warn(msg: String) {}
|
||||||
|
fun info(msg: String) {}
|
||||||
|
fun trace(msg: String) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package app.revanced.patcher.logging.impl
|
||||||
|
|
||||||
|
import app.revanced.patcher.logging.Logger
|
||||||
|
|
||||||
|
object NopLogger : Logger
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
package app.revanced.patcher.patch.base
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
import app.revanced.patcher.data.base.Data
|
import app.revanced.patcher.data.Data
|
||||||
import app.revanced.patcher.patch.implementation.BytecodePatch
|
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||||
import app.revanced.patcher.patch.implementation.ResourcePatch
|
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||||
import app.revanced.patcher.patch.implementation.misc.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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package app.revanced.patcher.util.dex
|
package app.revanced.patcher.util.dex
|
||||||
|
|
||||||
import org.jf.dexlib2.writer.io.MemoryDataStore
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for dex files.
|
* Wrapper for dex files.
|
||||||
* @param name The original name of the dex file
|
* @param name The original name of the dex file.
|
||||||
* @param memoryDataStore The data store for the dex file.
|
* @param dexFileInputStream The dex file as [InputStream].
|
||||||
*/
|
*/
|
||||||
data class DexFile(val name: String, val memoryDataStore: MemoryDataStore)
|
data class DexFile(val name: String, val dexFileInputStream: InputStream)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package app.revanced.patcher.util.method
|
package app.revanced.patcher.util.method
|
||||||
|
|
||||||
import app.revanced.patcher.data.implementation.BytecodeData
|
import app.revanced.patcher.data.impl.BytecodeData
|
||||||
import app.revanced.patcher.data.implementation.MethodNotFoundException
|
import app.revanced.patcher.data.impl.MethodNotFoundException
|
||||||
import app.revanced.patcher.extensions.softCompareTo
|
import app.revanced.patcher.extensions.softCompareTo
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
import org.jf.dexlib2.Format
|
import org.jf.dexlib2.Format
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package app.revanced.patcher.util.patch.base
|
package app.revanced.patcher.util.patch.base
|
||||||
|
|
||||||
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 java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package app.revanced.patcher.usage.bytecode.signatures
|
package app.revanced.patcher.usage.bytecode.fingerprints
|
||||||
|
|
||||||
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.extensions.or
|
import app.revanced.patcher.extensions.or
|
||||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||||
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
|
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
|
||||||
import app.revanced.patcher.signature.implementation.method.annotation.MatchingMethod
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
import app.revanced.patcher.usage.bytecode.annotation.ExampleBytecodeCompatibility
|
import app.revanced.patcher.usage.bytecode.annotation.ExampleBytecodeCompatibility
|
||||||
import org.jf.dexlib2.AccessFlags
|
import org.jf.dexlib2.AccessFlags
|
||||||
import org.jf.dexlib2.Opcode
|
import org.jf.dexlib2.Opcode
|
||||||
|
|
||||||
@Name("example-signature")
|
@Name("example-fingerprint")
|
||||||
@MatchingMethod(
|
@MatchingMethod(
|
||||||
"LexampleClass;",
|
"LexampleClass;",
|
||||||
"exampleMehod"
|
"exampleMehod"
|
||||||
@@ -18,7 +18,7 @@ import org.jf.dexlib2.Opcode
|
|||||||
@FuzzyPatternScanMethod(2)
|
@FuzzyPatternScanMethod(2)
|
||||||
@ExampleBytecodeCompatibility
|
@ExampleBytecodeCompatibility
|
||||||
@Version("0.0.1")
|
@Version("0.0.1")
|
||||||
object ExampleSignature : MethodSignature(
|
object ExampleFingerprint : MethodFingerprint(
|
||||||
"V",
|
"V",
|
||||||
AccessFlags.PUBLIC or AccessFlags.STATIC,
|
AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||||
listOf("[L"),
|
listOf("[L"),
|
||||||
@@ -3,14 +3,14 @@ package app.revanced.patcher.usage.bytecode.patch
|
|||||||
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.implementation.BytecodeData
|
import app.revanced.patcher.data.impl.BytecodeData
|
||||||
import app.revanced.patcher.extensions.addInstructions
|
import app.revanced.patcher.extensions.addInstructions
|
||||||
import app.revanced.patcher.extensions.or
|
import app.revanced.patcher.extensions.or
|
||||||
|
import app.revanced.patcher.patch.PatchResult
|
||||||
|
import app.revanced.patcher.patch.PatchResultSuccess
|
||||||
import app.revanced.patcher.patch.annotations.Patch
|
import app.revanced.patcher.patch.annotations.Patch
|
||||||
import app.revanced.patcher.patch.implementation.BytecodePatch
|
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||||
import app.revanced.patcher.patch.implementation.misc.PatchResult
|
import app.revanced.patcher.usage.bytecode.fingerprints.ExampleFingerprint
|
||||||
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
|
|
||||||
import app.revanced.patcher.usage.bytecode.signatures.ExampleSignature
|
|
||||||
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
@@ -36,37 +36,33 @@ import org.jf.dexlib2.util.Preconditions
|
|||||||
@Description("Example demonstration of a bytecode patch.")
|
@Description("Example demonstration of a bytecode patch.")
|
||||||
@ExampleResourceCompatibility
|
@ExampleResourceCompatibility
|
||||||
@Version("0.0.1")
|
@Version("0.0.1")
|
||||||
class ExampleBytecodePatch : BytecodePatch(
|
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||||
|
|
||||||
listOf(
|
|
||||||
ExampleSignature
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// This function will be executed by the patcher.
|
// This function will be executed by the patcher.
|
||||||
// You can treat it as a constructor
|
// You can treat it as a constructor
|
||||||
override fun execute(data: BytecodeData): PatchResult {
|
override fun execute(data: BytecodeData): PatchResult {
|
||||||
// Get the resolved method for the signature from the resolver cache
|
// Get the resolved method by its fingerprint from the resolver cache
|
||||||
val result = ExampleSignature.result!!
|
val result = ExampleFingerprint.result!!
|
||||||
|
|
||||||
// Get the implementation for the resolved method
|
// Get the implementation for the resolved method
|
||||||
val implementation = result.method.implementation!!
|
val method = result.mutableMethod
|
||||||
|
val implementation = method.implementation!!
|
||||||
|
|
||||||
// Let's modify it, so it prints "Hello, ReVanced! Editing bytecode."
|
// Let's modify it, so it prints "Hello, ReVanced! Editing bytecode."
|
||||||
// Get the start index of our opcode pattern.
|
// Get the start index of our opcode pattern.
|
||||||
// This will be the index of the instruction with the opcode CONST_STRING.
|
// This will be the index of the instruction with the opcode CONST_STRING.
|
||||||
val startIndex = result.scanResult.startIndex
|
val startIndex = result.patternScanResult!!.startIndex
|
||||||
|
|
||||||
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
|
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
|
||||||
|
|
||||||
// Get the class in which the method matching our signature is defined in.
|
// Get the class in which the method matching our fingerprint is defined in.
|
||||||
val mainClass = data.findClass {
|
val mainClass = data.findClass {
|
||||||
it.type == result.definingClassProxy.immutableClass.type
|
it.type == result.classDef.type
|
||||||
}!!.resolve()
|
}!!.resolve()
|
||||||
|
|
||||||
// Add a new method returning a string
|
// Add a new method returning a string
|
||||||
mainClass.methods.add(
|
mainClass.methods.add(
|
||||||
ImmutableMethod(
|
ImmutableMethod(
|
||||||
result.definingClassProxy.immutableClass.type,
|
result.classDef.type,
|
||||||
"returnHello",
|
"returnHello",
|
||||||
null,
|
null,
|
||||||
"Ljava/lang/String;",
|
"Ljava/lang/String;",
|
||||||
@@ -126,7 +122,7 @@ class ExampleBytecodePatch : BytecodePatch(
|
|||||||
move-result-object v1
|
move-result-object v1
|
||||||
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||||
"""
|
"""
|
||||||
result.method.addInstructions(startIndex + 2, instructions)
|
method.addInstructions(startIndex + 2, instructions)
|
||||||
|
|
||||||
// Finally, tell the patcher that this patch was a success.
|
// Finally, tell the patcher that this patch was a success.
|
||||||
// You can also return PatchResultError with a message.
|
// You can also return PatchResultError with a message.
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package app.revanced.patcher.usage.resource.patch
|
|||||||
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.implementation.ResourceData
|
import app.revanced.patcher.data.impl.ResourceData
|
||||||
|
import app.revanced.patcher.patch.PatchResult
|
||||||
|
import app.revanced.patcher.patch.PatchResultSuccess
|
||||||
import app.revanced.patcher.patch.annotations.Patch
|
import app.revanced.patcher.patch.annotations.Patch
|
||||||
import app.revanced.patcher.patch.implementation.ResourcePatch
|
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||||
import app.revanced.patcher.patch.implementation.misc.PatchResult
|
|
||||||
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
|
|
||||||
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
@@ -18,8 +18,8 @@ import org.w3c.dom.Element
|
|||||||
@Version("0.0.1")
|
@Version("0.0.1")
|
||||||
class ExampleResourcePatch : ResourcePatch() {
|
class ExampleResourcePatch : ResourcePatch() {
|
||||||
override fun execute(data: ResourceData): PatchResult {
|
override fun execute(data: ResourceData): PatchResult {
|
||||||
data.getXmlEditor("AndroidManifest.xml").use { domFileEditor ->
|
data.xmlEditor["AndroidManifest.xml"].use { editor ->
|
||||||
val element = domFileEditor // regular DomFileEditor
|
val element = editor // regular DomFileEditor
|
||||||
.file
|
.file
|
||||||
.getElementsByTagName("application")
|
.getElementsByTagName("application")
|
||||||
.item(0) as Element
|
.item(0) as Element
|
||||||
@@ -30,18 +30,6 @@ class ExampleResourcePatch : ResourcePatch() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate through all available resources
|
|
||||||
data.forEach {
|
|
||||||
if (it.extension.lowercase() != "xml") return@forEach
|
|
||||||
|
|
||||||
data.replace(
|
|
||||||
it.path,
|
|
||||||
"\\ddip", // regex supported
|
|
||||||
"0dip",
|
|
||||||
true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return PatchResultSuccess()
|
return PatchResultSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user