Compare commits

..

2 Commits

Author SHA1 Message Date
semantic-release-bot
540d7d5286 chore(release): 12.0.0-dev.2 [skip ci]
# [12.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v12.0.0-dev.1...v12.0.0-dev.2) (2023-07-28)

### Features

* Deprecate `Version` annotation ([400442f](400442f70e))
2023-07-28 18:15:22 +00:00
oSumAtrIX
400442f70e feat: Deprecate Version annotation 2023-07-28 20:13:45 +02:00
73 changed files with 999 additions and 1279 deletions

View File

@@ -10,7 +10,7 @@
[ [
"@semantic-release/commit-analyzer", { "@semantic-release/commit-analyzer", {
"releaseRules": [ "releaseRules": [
{ "type": "build", "scope": "Needs bump", "release": "patch" } { "type": "build", "scope": "needs-bump", "release": "patch" }
] ]
} }
], ],

View File

@@ -1,202 +1,3 @@
# [14.2.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v14.2.0-dev.1...v14.2.0-dev.2) (2023-08-26)
### Features
* log when merging integrations ([983563e](https://github.com/ReVanced/revanced-patcher/commit/983563efb6d7c8d289464b8bf71a016b8a735630))
# [14.2.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v14.1.0...v14.2.0-dev.1) (2023-08-25)
### Features
* load patches in lexicographical order ([e8f2087](https://github.com/ReVanced/revanced-patcher/commit/e8f2087a6ffa6077fb3a6a69e29f3aec72e2fc1b))
# [14.1.0](https://github.com/ReVanced/revanced-patcher/compare/v14.0.0...v14.1.0) (2023-08-24)
### Bug Fixes
* move version properties file to correct package ([e985676](https://github.com/ReVanced/revanced-patcher/commit/e985676c2d8e5d6cb907d371de30428caaa6da43))
### Features
* properly make use of logging facade ([ba56a6a](https://github.com/ReVanced/revanced-patcher/commit/ba56a6a2eef503c0d6cdd846ddce2e1474d8ed1a))
# [14.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v14.0.1-dev.1...v14.1.0-dev.1) (2023-08-24)
### Features
* properly make use of logging facade ([ba56a6a](https://github.com/ReVanced/revanced-patcher/commit/ba56a6a2eef503c0d6cdd846ddce2e1474d8ed1a))
## [14.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v14.0.0...v14.0.1-dev.1) (2023-08-23)
### Bug Fixes
* move version properties file to correct package ([e985676](https://github.com/ReVanced/revanced-patcher/commit/e985676c2d8e5d6cb907d371de30428caaa6da43))
# [14.0.0](https://github.com/ReVanced/revanced-patcher/compare/v13.0.0...v14.0.0) (2023-08-22)
### Bug Fixes
* log decoding resources after logging deleting resource cache directory ([db62a16](https://github.com/ReVanced/revanced-patcher/commit/db62a1607b4a9d6256b5f5153decb088d9680553))
* only emit closed patches that did not throw an exception with the `@Patch` annotation ([5938f6b](https://github.com/ReVanced/revanced-patcher/commit/5938f6b7ea25103a0a1b56ceebe49139bc80c6f5))
* supply the parent classloader to `DexClassLoader` ([0f15077](https://github.com/ReVanced/revanced-patcher/commit/0f15077225600b65200022c1a318e504deb472b9))
### Code Refactoring
* improve structure and public API ([6b8977f](https://github.com/ReVanced/revanced-patcher/commit/6b8977f17854ef0344d868e6391cb18134eceadc))
### Features
* do not log instantiation of ReVanced Patcher ([273dd8d](https://github.com/ReVanced/revanced-patcher/commit/273dd8d388f8e9b7436c6d6145a94c12c1fabe55))
### BREAKING CHANGES
* Various public APIs have been changed. The `Version` annotation has been removed. Patches do not return anything anymore and instead throw `PatchException`. Multiple patch bundles can now be loaded in a single ClassLoader to bypass class loader isolation.
# [14.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v14.0.0-dev.3...v14.0.0-dev.4) (2023-08-22)
### Bug Fixes
* only emit closed patches that did not throw an exception with the `@Patch` annotation ([5938f6b](https://github.com/ReVanced/revanced-patcher/commit/5938f6b7ea25103a0a1b56ceebe49139bc80c6f5))
# [14.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v14.0.0-dev.2...v14.0.0-dev.3) (2023-08-20)
### Bug Fixes
* supply the parent classloader to `DexClassLoader` ([0f15077](https://github.com/ReVanced/revanced-patcher/commit/0f15077225600b65200022c1a318e504deb472b9))
### Features
* do not log instantiation of ReVanced Patcher ([273dd8d](https://github.com/ReVanced/revanced-patcher/commit/273dd8d388f8e9b7436c6d6145a94c12c1fabe55))
# [14.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v14.0.0-dev.1...v14.0.0-dev.2) (2023-08-19)
# [14.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v13.0.0...v14.0.0-dev.1) (2023-08-18)
### Bug Fixes
* log decoding resources after logging deleting resource cache directory ([db62a16](https://github.com/ReVanced/revanced-patcher/commit/db62a1607b4a9d6256b5f5153decb088d9680553))
### Code Refactoring
* improve structure and public API ([6b8977f](https://github.com/ReVanced/revanced-patcher/commit/6b8977f17854ef0344d868e6391cb18134eceadc))
### BREAKING CHANGES
* Various public APIs have been changed. The `Version` annotation has been removed. Patches do not return anything anymore and instead throw `PatchException`. Multiple patch bundles can now be loaded in a single ClassLoader to bypass class loader isolation.
# [13.0.0](https://github.com/ReVanced/revanced-patcher/compare/v12.1.1...v13.0.0) (2023-08-14)
### Bug Fixes
* decode in correct order ([8fb2f2d](https://github.com/ReVanced/revanced-patcher/commit/8fb2f2dc1d3b9b1e9fd13b39485985d2886d52ae))
* disable correct loggers ([c2d89c6](https://github.com/ReVanced/revanced-patcher/commit/c2d89c622e06e58e5042e1a00ef67cee8a246e53))
* get framework ids to compile resources ([f2cb7ee](https://github.com/ReVanced/revanced-patcher/commit/f2cb7ee7dffa573c31df497cf235a3f5d120f91f))
* only enable logging for ReVanced ([783ccf8](https://github.com/ReVanced/revanced-patcher/commit/783ccf8529f5d16aa463982da6977328306232bb))
* set package metadata correctly ([02d6ff1](https://github.com/ReVanced/revanced-patcher/commit/02d6ff15fe87c2352de29749610e9d72db8ba418))
* build(Needs bump)!: Bump dependencies ([d5f89a9](https://github.com/ReVanced/revanced-patcher/commit/d5f89a903f019c199bdb27a50287124fc4b4978e))
### BREAKING CHANGES
* This bump updates smali, a crucial dependency
# [13.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v13.0.0-dev.2...v13.0.0-dev.3) (2023-08-14)
### Bug Fixes
* decode in correct order ([8fb2f2d](https://github.com/ReVanced/revanced-patcher/commit/8fb2f2dc1d3b9b1e9fd13b39485985d2886d52ae))
* only enable logging for ReVanced ([783ccf8](https://github.com/ReVanced/revanced-patcher/commit/783ccf8529f5d16aa463982da6977328306232bb))
# [13.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v13.0.0-dev.1...v13.0.0-dev.2) (2023-08-12)
### Bug Fixes
* disable correct loggers ([c2d89c6](https://github.com/ReVanced/revanced-patcher/commit/c2d89c622e06e58e5042e1a00ef67cee8a246e53))
* get framework ids to compile resources ([f2cb7ee](https://github.com/ReVanced/revanced-patcher/commit/f2cb7ee7dffa573c31df497cf235a3f5d120f91f))
* set package metadata correctly ([02d6ff1](https://github.com/ReVanced/revanced-patcher/commit/02d6ff15fe87c2352de29749610e9d72db8ba418))
# [13.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v12.1.1...v13.0.0-dev.1) (2023-08-11)
* build(Needs bump)!: Bump dependencies ([d5f89a9](https://github.com/ReVanced/revanced-patcher/commit/d5f89a903f019c199bdb27a50287124fc4b4978e))
### BREAKING CHANGES
* This bump updates smali, a crucial dependency
## [12.1.1](https://github.com/ReVanced/revanced-patcher/compare/v12.1.0...v12.1.1) (2023-08-03)
### Bug Fixes
* clear method lookup maps before initializing them ([#210](https://github.com/ReVanced/revanced-patcher/issues/210)) ([746544f](https://github.com/ReVanced/revanced-patcher/commit/746544f9d51d1013bb160075709cd26bffd425b3))
## [12.1.1-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v12.1.1-dev.1...v12.1.1-dev.2) (2023-08-03)
## [12.1.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v12.1.0...v12.1.1-dev.1) (2023-08-03)
### Bug Fixes
* clear method lookup maps before initializing them ([#210](https://github.com/ReVanced/revanced-patcher/issues/210)) ([746544f](https://github.com/ReVanced/revanced-patcher/commit/746544f9d51d1013bb160075709cd26bffd425b3))
# [12.1.0](https://github.com/ReVanced/revanced-patcher/compare/v12.0.0...v12.1.0) (2023-08-03)
### Features
* add `MutableMethod.getInstructions` extension function ([fae4029](https://github.com/ReVanced/revanced-patcher/commit/fae4029cfccfad7aa3dd8f7fbef1c63ee26b85b3))
# [12.1.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v12.1.0-dev.1...v12.1.0-dev.2) (2023-08-03)
# [12.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v12.0.0...v12.1.0-dev.1) (2023-08-01)
### Features
* add `MutableMethod.getInstructions` extension function ([fae4029](https://github.com/ReVanced/revanced-patcher/commit/fae4029cfccfad7aa3dd8f7fbef1c63ee26b85b3))
# [12.0.0](https://github.com/ReVanced/revanced-patcher/compare/v11.0.4...v12.0.0) (2023-07-30)
### Bug Fixes
* correct access flags of `PackageMetadata` ([416d691](https://github.com/ReVanced/revanced-patcher/commit/416d69142f50dab49c9ea3f027e9d53e4777f257))
* set resource table via resource decoder ([e0f8e1b](https://github.com/ReVanced/revanced-patcher/commit/e0f8e1b71a295948b610029c89a48f52762396b6))
### Features
* Deprecate `Version` annotation ([c9bbcf2](https://github.com/ReVanced/revanced-patcher/commit/c9bbcf2bf2b0f50ab9100380a3a66c6346ad42ac))
* remove `Path` option ([#202](https://github.com/ReVanced/revanced-patcher/issues/202)) ([69e4a49](https://github.com/ReVanced/revanced-patcher/commit/69e4a490659ebc4fb4bf46148634f4b064ef1713))
### BREAKING CHANGES
* This removes the previously available `Path` option
# [12.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v12.0.0-dev.1...v12.0.0-dev.2) (2023-07-28) # [12.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v12.0.0-dev.1...v12.0.0-dev.2) (2023-07-28)

View File

@@ -5,17 +5,28 @@ plugins {
group = "app.revanced" group = "app.revanced"
val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
repositories {
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/revanced/multidexlib2")
credentials {
username = githubUsername
password = githubPassword
}
}
}
dependencies { dependencies {
implementation(libs.kotlinx.coroutines.core) implementation("xpp3:xpp3:1.1.4c")
implementation(libs.xpp3) implementation("app.revanced:smali:2.5.3-a3836654")
implementation(libs.smali) implementation("app.revanced:multidexlib2:2.5.3-a3836654")
implementation(libs.multidexlib2) implementation("app.revanced:apktool-lib:2.8.2")
implementation(libs.apktool.lib)
implementation(libs.kotlin.reflect)
compileOnly(libs.android) implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20-RC")
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
testImplementation(libs.kotlin.test)
} }
tasks { tasks {
@@ -25,32 +36,35 @@ tasks {
events("PASSED", "SKIPPED", "FAILED") events("PASSED", "SKIPPED", "FAILED")
} }
} }
processResources { processResources {
expand("projectVersion" to project.version) expand("projectVersion" to project.version)
} }
} }
kotlin { jvmToolchain(11) }
java { java {
withSourcesJar() withSourcesJar()
} }
kotlin {
jvmToolchain(11)
}
publishing { publishing {
repositories { repositories {
mavenLocal() 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")
credentials { credentials {
username = System.getenv("GITHUB_ACTOR") username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN") password = System.getenv("GITHUB_TOKEN")
}
} }
} else
mavenLocal()
} }
publications { publications {
create<MavenPublication>("gpr") { register<MavenPublication>("gpr") {
from(components["java"]) from(components["java"])
} }
} }

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true org.gradle.parallel = true
org.gradle.caching = true org.gradle.caching = true
kotlin.code.style = official kotlin.code.style = official
version = 14.2.0-dev.2 version = 12.0.0-dev.2

View File

@@ -1,21 +0,0 @@
[versions]
android = "4.1.1.4"
kotlin-reflect = "1.9.0"
apktool-lib = "2.8.2-5"
kotlin-test = "1.8.20-RC"
kotlinx-coroutines-core = "1.7.1"
multidexlib2 = "3.0.3.r2"
shadow = "8.1.1"
smali = "3.0.3"
xpp3 = "1.1.4c"
[libraries]
android = { module = "com.google.android:android", version.ref = "android" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
apktool-lib = { module = "app.revanced:apktool-lib", version.ref = "apktool-lib" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
multidexlib2 = { module = "app.revanced:multidexlib2", version.ref = "multidexlib2" }
smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
xpp3 = { module = "xpp3:xpp3", version.ref = "xpp3" }

View File

@@ -1,22 +1 @@
val githubUsername: String = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR")
val githubPassword: String = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN")
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
google()
mavenLocal()
listOf("multidexlib2", "apktool").forEach { repo ->
maven {
url = uri("https://maven.pkg.github.com/revanced/$repo")
credentials {
username = githubUsername
password = githubPassword
}
}
}
}
}
rootProject.name = "revanced-patcher" rootProject.name = "revanced-patcher"

View File

@@ -1,8 +0,0 @@
package app.revanced.patcher
import java.io.File
@FunctionalInterface
interface IntegrationsConsumer {
fun acceptIntegrations(integrations: List<File>)
}

View File

@@ -1,74 +0,0 @@
@file:Suppress("unused")
package app.revanced.patcher
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchClass
import dalvik.system.DexClassLoader
import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.MultiDexIO
import java.io.File
import java.net.URLClassLoader
import java.util.jar.JarFile
/**
* A patch bundle.
*
* @param fromClasses The classes to get [Patch]es from.
*/
sealed class PatchBundleLoader private constructor(
fromClasses: Iterable<Class<*>>
) : MutableList<PatchClass> by mutableListOf() {
init {
fromClasses.filter {
if (it.isAnnotation) return@filter false
it.findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class) != null
}.map {
@Suppress("UNCHECKED_CAST")
it as PatchClass
}.sortedBy {
it.patchName
}.let { addAll(it) }
}
/**
* A [PatchBundleLoader] for JAR files.
*
* @param patchBundles The path to patch bundles of JAR format.
*/
class Jar(vararg patchBundles: File) :
PatchBundleLoader(with(URLClassLoader(patchBundles.map { it.toURI().toURL() }.toTypedArray())) {
patchBundles.flatMap { patchBundle ->
// Get the names of all classes in the DEX file.
JarFile(patchBundle).entries().asSequence()
.filter { it.name.endsWith(".class") }
.map { it.name.replace('/', '.').replace(".class", "") }
.map { loadClass(it) }
}
})
/**
* A [PatchBundleLoader] for [Dex] files.
*
* @param patchBundles The path to patch bundles of DEX format.
*/
class Dex(vararg patchBundles: File) : PatchBundleLoader(with(
DexClassLoader(
patchBundles.joinToString(File.pathSeparator) { it.absolutePath },
null,
null,
PatchBundleLoader::class.java.classLoader
)
) {
patchBundles
.flatMap {
MultiDexIO.readDexFile(true, it, BasicDexFileNamer(), null, null).classes
}
.map { classDef -> classDef.type.substring(1, classDef.length - 1) }
.map { loadClass(it) }
})
}

View File

@@ -1,8 +0,0 @@
package app.revanced.patcher
import app.revanced.patcher.patch.PatchResult
import kotlinx.coroutines.flow.Flow
import java.util.function.Function
@FunctionalInterface
interface PatchExecutorFunction : Function<Boolean, Flow<PatchResult>>

View File

@@ -1,67 +1,162 @@
package app.revanced.patcher package app.revanced.patcher
import app.revanced.patcher.data.Context import app.revanced.patcher.data.Context
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
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.PatchExtensions.requiresIntegrations import app.revanced.patcher.extensions.PatchExtensions.requiresIntegrations
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolveUsingLookupMap import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolveUsingLookupMap
import app.revanced.patcher.patch.* import app.revanced.patcher.patch.*
import kotlinx.coroutines.flow.flow import brut.androlib.AaptInvoker
import brut.androlib.ApkDecoder
import brut.androlib.Config
import brut.androlib.res.Framework
import brut.androlib.res.ResourcesDecoder
import brut.androlib.res.decoder.AndroidManifestResourceParser
import brut.androlib.res.decoder.ResAttrDecoder
import brut.androlib.res.decoder.XmlPullStreamDecoder
import brut.androlib.res.util.ExtMXSerializer
import brut.androlib.res.util.ExtXmlSerializer
import brut.androlib.res.xml.ResXmlPatcher
import brut.directory.ExtFile
import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.DexIO
import lanchon.multidexlib2.MultiDexIO
import org.jf.dexlib2.Opcodes
import org.jf.dexlib2.iface.DexFile
import org.jf.dexlib2.writer.io.MemoryDataStore
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
import java.util.function.Supplier import java.io.OutputStream
import java.util.logging.Level import java.nio.file.Files
import java.util.logging.LogManager
import java.util.logging.Logger internal val NAMER = BasicDexFileNamer()
/** /**
* ReVanced Patcher. * The ReVanced Patcher.
*
* @param options The options for the patcher. * @param options The options for the patcher.
*/ */
class Patcher( class Patcher(private val options: PatcherOptions) {
private val options: PatcherOptions private val logger = options.logger
) : PatchExecutorFunction, PatchesConsumer, IntegrationsConsumer, Supplier<PatcherResult>, Closeable { private val opcodes: Opcodes
private val logger = Logger.getLogger(Patcher::class.java.name) private var resourceDecodingMode = ResourceDecodingMode.MANIFEST_ONLY
private var mergeIntegrations = false
val context: PatcherContext
/** private val config = Config.getDefaultConfig().apply {
* The context of ReVanced [Patcher]. aaptPath = options.aaptPath
* This holds the current state of the patcher. frameworkDirectory = options.frameworkDirectory
*/
val context = PatcherContext(options)
init {
LogManager.getLogManager().let { manager ->
// Disable root logger.
manager.getLogger("").level = Level.OFF
// Enable ReVanced logging only.
manager.loggerNames
.toList()
.filter { it.startsWith("app.revanced") }
.map { manager.getLogger(it) }
.forEach { it.level = Level.INFO }
}
context.resourceContext.decodeResources(ResourceContext.ResourceDecodingMode.MANIFEST_ONLY)
} }
override fun acceptPatches(patches: List<PatchClass>) { init {
logger.info("Reading dex files")
// read dex files
val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
// get the opcodes
opcodes = dexFile.opcodes
// finally create patcher context
context = PatcherContext(dexFile.classes.toMutableList(), File(options.resourceCacheDirectory))
// decode manifest file
decodeResources(ResourceDecodingMode.MANIFEST_ONLY)
}
/**
* Add integrations to be merged by the patcher.
* The integrations will only be merged, if necessary.
*
* @param integrations The integrations, must be dex files or dex file container such as ZIP, APK or DEX files.
* @param callback The callback for [integrations] which are being added.
*/
fun addIntegrations(
integrations: List<File>,
callback: (File) -> Unit
) {
context.integrations.apply integrations@{
add(integrations)
this@integrations.callback = callback
}
}
/**
* Save the patched dex file.
*/
fun save(): PatcherResult {
var resourceFile: File? = null
when (resourceDecodingMode) {
ResourceDecodingMode.FULL -> {
logger.info("Compiling resources")
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
val aaptFile = cacheDirectory.resolve("aapt_temp_file").also {
Files.deleteIfExists(it.toPath())
}.also { resourceFile = it }
try {
AaptInvoker(
config,
context.packageMetadata.apkInfo
).invokeAapt(
aaptFile,
cacheDirectory.resolve("AndroidManifest.xml").also {
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(it)
},
cacheDirectory.resolve("res"),
null,
null,
context.packageMetadata.apkInfo.usesFramework.let { usesFramework ->
usesFramework.ids.map { id ->
Framework(config).getFrameworkApk(id, usesFramework.tag)
}.toTypedArray()
}
)
} finally {
cacheDirectory.close()
}
}
else -> logger.info("Not compiling resources because resource patching is not required")
}
logger.trace("Creating new dex file")
val newDexFile = object : DexFile {
override fun getClasses() = context.bytecodeContext.classes.also { it.replaceClasses() }
override fun getOpcodes() = this@Patcher.opcodes
}
// write modified dex files
logger.info("Writing modified dex files")
val dexFiles = mutableMapOf<String, MemoryDataStore>()
MultiDexIO.writeDexFile(
true, -1, // core count
dexFiles, NAMER, newDexFile, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
)
return PatcherResult(
dexFiles.map {
app.revanced.patcher.util.dex.DexFile(it.key, it.value.readAt(0))
},
context.packageMetadata.apkInfo.doNotCompress?.toList(),
resourceFile
)
}
/**
* Add [Patch]es to the patcher.
* @param patches [Patch]es The patches to add.
*/
fun addPatches(patches: Iterable<Class<out Patch<Context>>>) {
/** /**
* Returns true if at least one patches or its dependencies matches the given predicate. * Returns true if at least one patches or its dependencies matches the given predicate.
*/ */
fun PatchClass.anyRecursively(predicate: (PatchClass) -> Boolean): Boolean = fun Class<out Patch<Context>>.anyRecursively(predicate: (Class<out Patch<Context>>) -> Boolean): Boolean =
predicate(this) || dependencies?.any { dependency -> predicate(this) || dependencies?.any { it.java.anyRecursively(predicate) } == true
dependency.java.anyRecursively(predicate)
} ?: false
// Determine if resource patching is required. // Determine if resource patching is required.
for (patch in patches) { for (patch in patches) {
if (patch.anyRecursively { ResourcePatch::class.java.isAssignableFrom(it) }) { if (patch.anyRecursively { ResourcePatch::class.java.isAssignableFrom(it) }) {
options.resourceDecodingMode = ResourceContext.ResourceDecodingMode.FULL resourceDecodingMode = ResourceDecodingMode.FULL
break break
} }
} }
@@ -69,7 +164,7 @@ class Patcher(
// Determine if merging integrations is required. // Determine if merging integrations is required.
for (patch in patches) { for (patch in patches) {
if (patch.anyRecursively { it.requiresIntegrations }) { if (patch.anyRecursively { it.requiresIntegrations }) {
context.bytecodeContext.integrations.merge = true mergeIntegrations = true
break break
} }
} }
@@ -78,159 +173,223 @@ class Patcher(
} }
/** /**
* Add integrations to the [Patcher]. * Decode resources for the patcher.
* *
* @param integrations The integrations to add. Must be a DEX file or container of DEX files. * @param mode The [ResourceDecodingMode] to use when decoding.
*/ */
override fun acceptIntegrations(integrations: List<File>) { private fun decodeResources(mode: ResourceDecodingMode) {
context.bytecodeContext.integrations.addAll(integrations) val extInputFile = ExtFile(options.inputFile)
try {
val resourcesDecoder = ResourcesDecoder(config, extInputFile)
when (mode) {
ResourceDecodingMode.FULL -> {
val outDir = File(options.resourceCacheDirectory)
if (outDir.exists()) {
logger.info("Deleting existing resource cache directory")
if (!outDir.deleteRecursively()) logger.error("Failed to delete existing resource cache directory")
}
outDir.mkdirs()
logger.info("Decoding resources")
resourcesDecoder.decodeManifest(outDir)
resourcesDecoder.decodeResources(outDir)
context.packageMetadata.also {
it.apkInfo = resourcesDecoder.apkInfo
}.apkInfo.doNotCompress = ApkDecoder(config, extInputFile).recordUncompressedFiles(
context.packageMetadata.apkInfo, resourcesDecoder.resFileMapping
)
}
ResourceDecodingMode.MANIFEST_ONLY -> {
logger.info("Decoding AndroidManifest.xml only, because resources are not needed")
// Instead of using resourceDecoder.decodeManifest which decodes the whole file
// use the XmlPullStreamDecoder in order to get necessary information from the manifest
// used below.
XmlPullStreamDecoder(AndroidManifestResourceParser().apply {
attrDecoder = ResAttrDecoder().apply { this.resTable = resourcesDecoder.resTable }
}, ExtMXSerializer().apply {
setProperty(
ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION, " "
)
setProperty(
ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR,
System.getProperty("line.separator")
)
setProperty(
ExtXmlSerializer.PROPERTY_DEFAULT_ENCODING,
"utf-8"
)
setDisabledAttrEscape(true)
}
).decodeManifest(
extInputFile.directory.getFileInput("AndroidManifest.xml"),
// Older Android versions do not support OutputStream.nullOutputStream()
object : OutputStream() { override fun write(b: Int) { /* do nothing */ } }
)
}
}
// Get the package name and version from the manifest using the XmlPullStreamDecoder.
// XmlPullStreamDecoder.decodeManifest() sets metadata.apkInfo.
context.packageMetadata.let { metadata ->
metadata.apkInfo = resourcesDecoder.apkInfo
metadata.packageName = resourcesDecoder.resTable.currentResPackage.name
resourcesDecoder.apkInfo.versionInfo.let {
metadata.packageVersion = it.versionName ?: it.versionCode
}
}
} finally {
extInputFile.close()
}
} }
/** /**
* Execute [Patch]es that were added to ReVanced [Patcher]. * Execute patches added the patcher.
* *
* @param returnOnError If true, ReVanced [Patcher] will return immediately if a [Patch] fails. * @param stopOnError If true, the patches will stop on the first error.
* @return A pair of the name of the [Patch] and its [PatchResult]. * @return A pair of the name of the [Patch] and its [PatchResult].
*/ */
override fun apply(returnOnError: Boolean) = flow { fun executePatches(stopOnError: Boolean = false): Sequence<Pair<String, Result<PatchResultSuccess>>> {
class ExecutedPatch(val patchInstance: Patch<Context<*>>, val patchResult: PatchResult)
/** /**
* Execute a [Patch] and its dependencies recursively. * Execute a [Patch] and its dependencies recursively.
* *
* @param patchClass The [Patch] to execute. * @param patchClass The [Patch] to execute.
* @param executedPatches A map to prevent [Patch]es from being executed twice due to dependencies. * @param executedPatches 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].
*/ */
fun executePatch( fun executePatch(
patchClass: PatchClass, patchClass: Class<out Patch<Context>>,
executedPatches: LinkedHashMap<String, ExecutedPatch> executedPatches: LinkedHashMap<String, ExecutedPatch>
): PatchResult { ): PatchResult {
val patchName = patchClass.patchName val patchName = patchClass.patchName
executedPatches[patchName]?.let { executedPatch -> // if the patch has already applied silently skip it
executedPatch.patchResult.exception ?: return executedPatch.patchResult if (executedPatches.contains(patchName)) {
if (!executedPatches[patchName]!!.success)
return PatchResultError("'$patchName' did not succeed previously")
// Return a new result with an exception indicating that the patch was not executed previously, logger.trace("Skipping '$patchName' because it has already been applied")
// because it is a dependency of another patch that failed.
return PatchResult(patchName, PatchException("'$patchName' did not succeed previously")) return PatchResultSuccess()
} }
// Recursively execute all dependency patches. // recursively execute all dependency patches
patchClass.dependencies?.forEach { dependencyClass -> patchClass.dependencies?.forEach { dependencyClass ->
val dependency = dependencyClass.java val dependency = dependencyClass.java
val result = executePatch(dependency, executedPatches) val result = executePatch(dependency, executedPatches)
if (result.isSuccess()) return@forEach
result.exception?.let { return PatchResultError(
return PatchResult( "'$patchName' depends on '${dependency.patchName}' but the following error was raised: " +
patchName, result.error()!!.let { it.cause?.stackTraceToString() ?: it.message }
PatchException( )
"'$patchName' depends on '${dependency.patchName}' that raised an exception: $it"
)
)
}
} }
// TODO: Implement this in a more polymorphic way. val isResourcePatch = ResourcePatch::class.java.isAssignableFrom(patchClass)
val patchInstance = patchClass.getDeclaredConstructor().newInstance() val patchInstance = patchClass.getDeclaredConstructor().newInstance()
val patchContext = if (patchInstance is BytecodePatch) { // TODO: implement this in a more polymorphic way
patchInstance.fingerprints?.resolveUsingLookupMap(context.bytecodeContext) val patchContext = if (isResourcePatch) {
context.bytecodeContext
} else {
context.resourceContext context.resourceContext
} else {
context.bytecodeContext.also { context ->
(patchInstance as BytecodePatch).fingerprints?.resolveUsingLookupMap(context)
}
} }
logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}")
return try { return try {
patchInstance.execute(patchContext) patchInstance.execute(patchContext).also {
executedPatches[patchName] = ExecutedPatch(patchInstance, it.isSuccess())
PatchResult(patchName) }
} catch (exception: PatchException) { } catch (e: Exception) {
PatchResult(patchName, exception) PatchResultError(e).also {
} catch (exception: Exception) { executedPatches[patchName] = ExecutedPatch(patchInstance, false)
PatchResult(patchName, PatchException(exception)) }
}.also { executedPatches[patchName] = ExecutedPatch(patchInstance, it) }
}
if (context.bytecodeContext.integrations.merge) context.bytecodeContext.integrations.flush()
MethodFingerprint.initializeFingerprintResolutionLookupMaps(context.bytecodeContext)
// Prevent from decoding the app manifest twice if it is not needed.
if (options.resourceDecodingMode == ResourceContext.ResourceDecodingMode.FULL)
context.resourceContext.decodeResources(ResourceContext.ResourceDecodingMode.FULL)
logger.info("Executing patches")
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // Key is name.
context.patches.forEach { patch ->
val result = executePatch(patch, executedPatches)
// If the patch failed, or if the patch is not closeable, emit the result.
// Results of patches that are closeable will be emitted later.
result.exception?.let {
emit(result)
if (returnOnError) return@flow
} ?: run {
if (executedPatches[result.patchName]!!.patchInstance is Closeable) return@run
emit(result)
} }
} }
executedPatches.values return sequence {
.filter { it.patchResult.exception == null } if (mergeIntegrations) context.integrations.merge(logger)
.filter { it.patchInstance is Closeable }.asReversed().forEach { executedPatch ->
val patchName = executedPatch.patchResult.patchName
val result = try { logger.trace("Initialize lookup maps for method MethodFingerprint resolution")
(executedPatch.patchInstance as Closeable).close()
executedPatch.patchResult MethodFingerprint.initializeFingerprintResolutionLookupMaps(context.bytecodeContext)
} catch (exception: PatchException) {
PatchResult(patchName, exception) // prevent from decoding the manifest twice if it is not needed
} catch (exception: Exception) { if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL)
PatchResult(patchName, PatchException(exception))
logger.info("Executing patches")
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // first is name
context.patches.forEach { patch ->
val patchResult = executePatch(patch, executedPatches)
val result = if (patchResult.isSuccess()) {
Result.success(patchResult.success()!!)
} else {
Result.failure(patchResult.error()!!)
} }
result.exception?.let { // TODO: This prints before the patch really finishes in case it is a Closeable
emit( // because the Closeable is closed after all patches are executed.
PatchResult( yield(patch.patchName to result)
patchName,
PatchException("'$patchName' raised an exception while being closed: $it")
)
)
if (returnOnError) return@flow if (stopOnError && patchResult.isError()) return@sequence
} ?: run {
executedPatch
.patchInstance::class
.java
.findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)
?: return@run
emit(result)
}
} }
}
override fun close() { executedPatches.values
MethodFingerprint.clearFingerprintResolutionLookupMaps() .filter(ExecutedPatch::success)
.map(ExecutedPatch::patchInstance)
.filterIsInstance(Closeable::class.java)
.asReversed().forEach {
try {
it.close()
} catch (exception: Exception) {
val patchName = (it as Patch<Context>).javaClass.patchName
logger.error("Failed to close '$patchName': ${exception.stackTraceToString()}")
yield(patchName to Result.failure(exception))
// This is not failsafe. If a patch throws an exception while closing,
// the other patches that depend on it may fail.
if (stopOnError) return@sequence
}
}
MethodFingerprint.clearFingerprintResolutionLookupMaps()
}
} }
/** /**
* Compile and save the patched APK file. * The type of decoding the resources.
*
* @return The [PatcherResult] containing the patched input files.
*/ */
override fun get() = PatcherResult( private enum class ResourceDecodingMode {
context.bytecodeContext.get(), /**
context.resourceContext.get(), * Decode all resources.
context.packageMetadata.apkInfo.doNotCompress?.toList() */
) FULL,
/**
* Decode the manifest file only.
*/
MANIFEST_ONLY,
}
} }
/**
* A result of executing a [Patch].
*
* @param patchInstance The instance of the [Patch] that was applied.
* @param success The result of the [Patch].
*/
internal data class ExecutedPatch(val patchInstance: Patch<Context>, val success: Boolean)

View File

@@ -1,37 +1,64 @@
package app.revanced.patcher package app.revanced.patcher
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.*
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.logging.Logger
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchClass import app.revanced.patcher.util.ClassMerger.merge
import brut.androlib.apk.ApkInfo import org.jf.dexlib2.iface.ClassDef
import brut.directory.ExtFile import java.io.File
/** data class PatcherContext(
* A context for ReVanced [Patcher]. val classes: MutableList<ClassDef>,
* val resourceCacheDirectory: File,
* @param options The [PatcherOptions] used to create this context. ) {
*/ val packageMetadata = PackageMetadata()
class PatcherContext internal constructor(options: PatcherOptions) { internal val patches = mutableListOf<Class<out Patch<Context>>>()
/** internal val integrations = Integrations(this)
* [PackageMetadata] of the supplied [PatcherOptions.inputFile]. internal val bytecodeContext = BytecodeContext(classes)
*/ internal val resourceContext = ResourceContext(resourceCacheDirectory)
val packageMetadata = PackageMetadata(ApkInfo(ExtFile(options.inputFile)))
/** internal class Integrations(val context: PatcherContext) {
* The list of [Patch]es to execute. var callback: ((File) -> Unit)? = null
*/ private val integrations: MutableList<File> = mutableListOf()
internal val patches = mutableListOf<PatchClass>()
/** fun add(integrations: List<File>) = this@Integrations.integrations.addAll(integrations)
* The [ResourceContext] of this [PatcherContext].
* This holds the current state of the resources.
*/
internal val resourceContext = ResourceContext(this, options)
/** /**
* The [BytecodeContext] of this [PatcherContext]. * Merge integrations.
* This holds the current state of the bytecode. * @param logger A logger.
*/ */
internal val bytecodeContext = BytecodeContext(options) fun merge(logger: Logger) {
with(context.bytecodeContext.classes) {
for (integrations in integrations) {
callback?.let { it(integrations) }
for (classDef in lanchon.multidexlib2.MultiDexIO.readDexFile(
true,
integrations,
NAMER,
null,
null
).classes) {
val type = classDef.type
val result = classes.findIndexed { it.type == type }
if (result == null) {
logger.trace("Merging type $type")
classes.add(classDef)
continue
}
val (existingClass, existingClassIndex) = result
logger.trace("Type $type exists. Adding missing methods and fields.")
existingClass.merge(classDef, context, logger).let { mergedClass ->
if (mergedClass !== existingClass) // referential equality check
classes[existingClassIndex] = mergedClass
}
}
}
}
}
}
} }

View File

@@ -1,73 +1,21 @@
package app.revanced.patcher package app.revanced.patcher
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.logging.Logger
import app.revanced.patcher.logging.impl.NopLogger import app.revanced.patcher.logging.impl.NopLogger
import brut.androlib.Config
import java.io.File import java.io.File
import java.util.logging.Logger
/** /**
* Options for ReVanced [Patcher]. * Options for the [Patcher].
* @param inputFile The input file to patch. * @param inputFile The input file (usually an apk file).
* @param resourceCachePath The path to the directory to use for caching resources. * @param resourceCacheDirectory Directory to cache resources.
* @param aaptBinaryPath The path to a custom aapt binary. * @param aaptPath Optional path to a custom aapt binary.
* @param frameworkFileDirectory The path to the directory to cache the framework file in. * @param frameworkDirectory Optional path to a custom framework directory.
* @param unusedLogger The logger to use for logging. * @param logger Custom logger implementation for the [Patcher].
*/ */
data class PatcherOptions data class PatcherOptions(
@Deprecated("Use the constructor without the logger parameter instead")
constructor(
internal val inputFile: File, internal val inputFile: File,
internal val resourceCachePath: File = File("revanced-resource-cache"), internal val resourceCacheDirectory: String,
internal val aaptBinaryPath: String? = null, internal val aaptPath: String? = null,
internal val frameworkFileDirectory: String? = null, internal val frameworkDirectory: String? = null,
internal val unusedLogger: app.revanced.patcher.logging.Logger = NopLogger internal val logger: Logger = NopLogger
) { )
private val logger = Logger.getLogger(PatcherOptions::class.java.name)
/**
* The mode to use for resource decoding.
* @see ResourceContext.ResourceDecodingMode
*/
internal var resourceDecodingMode = ResourceContext.ResourceDecodingMode.MANIFEST_ONLY
/**
* The configuration to use for resource decoding and compiling.
*/
internal val resourceConfig = Config.getDefaultConfig().apply {
useAapt2 = true
aaptPath = aaptBinaryPath ?: ""
frameworkDirectory = frameworkFileDirectory
}
/**
* Options for ReVanced [Patcher].
* @param inputFile The input file to patch.
* @param resourceCachePath The path to the directory to use for caching resources.
* @param aaptBinaryPath The path to a custom aapt binary.
* @param frameworkFileDirectory The path to the directory to cache the framework file in.
*/
constructor(
inputFile: File,
resourceCachePath: File = File("revanced-resource-cache"),
aaptBinaryPath: String? = null,
frameworkFileDirectory: String? = null,
) : this(
inputFile,
resourceCachePath,
aaptBinaryPath,
frameworkFileDirectory,
NopLogger
)
fun recreateResourceCacheDirectory() = resourceCachePath.also {
if (it.exists()) {
logger.info("Deleting existing resource cache directory")
if (!it.deleteRecursively())
logger.severe("Failed to delete existing resource cache directory")
}
it.mkdirs()
}
}

View File

@@ -1,23 +1,16 @@
package app.revanced.patcher package app.revanced.patcher
import app.revanced.patcher.util.dex.DexFile
import java.io.File import java.io.File
import java.io.InputStream
/** /**
* 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 resourceFile File containing resources that need to be extracted into the APK. * @param resourceFile File containing resources that need to be extracted into the APK.
* @param doNotCompress List of relative paths of files to exclude from compressing.
*/ */
data class PatcherResult( data class PatcherResult(
val dexFiles: List<PatchedDexFile>, val dexFiles: List<DexFile>,
val resourceFile: File?, val doNotCompress: List<String>? = null,
val doNotCompress: List<String>? = null val resourceFile: File?
) { )
/**
* Wrapper for dex files.
* @param name The original name of the dex file.
* @param stream The dex file as [InputStream].
*/
class PatchedDexFile(val name: String, val stream: InputStream)
}

View File

@@ -1,8 +0,0 @@
package app.revanced.patcher
import app.revanced.patcher.patch.PatchClass
@FunctionalInterface
interface PatchesConsumer {
fun acceptPatches(patches: List<PatchClass>)
}

View File

@@ -18,4 +18,15 @@ annotation class Name(
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
annotation class Description( annotation class Description(
val description: String, val description: String,
) )
/**
* Annotation to version a [Patch].
* @param version The version of a [Patch].
*/
@Target(AnnotationTarget.CLASS)
@Deprecated("This annotation is deprecated and will be removed in a future release.")
annotation class Version(
val version: String,
)

View File

@@ -1,157 +0,0 @@
package app.revanced.patcher.data
import app.revanced.patcher.PatcherContext
import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.PatcherResult
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.annotations.RequiresIntegrations
import app.revanced.patcher.util.ClassMerger.merge
import app.revanced.patcher.util.ProxyClassList
import app.revanced.patcher.util.method.MethodWalker
import app.revanced.patcher.util.proxy.ClassProxy
import com.android.tools.smali.dexlib2.Opcodes
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.DexFile
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.writer.io.MemoryDataStore
import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.DexIO
import lanchon.multidexlib2.MultiDexIO
import java.io.File
import java.io.Flushable
import java.util.logging.Logger
/**
* A context for bytecode.
* This holds the current state of the bytecode.
*
* @param options The [PatcherOptions] used to create this context.
*/
class BytecodeContext internal constructor(private val options: PatcherOptions) :
Context<List<PatcherResult.PatchedDexFile>> {
private val logger = Logger.getLogger(BytecodeContext::class.java.name)
/**
* [Opcodes] of the supplied [PatcherOptions.inputFile].
*/
internal lateinit var opcodes: Opcodes
/**
* The list of classes.
*/
val classes by lazy {
ProxyClassList(
MultiDexIO.readDexFile(
true, options.inputFile, BasicDexFileNamer(), null, null
).also { opcodes = it.opcodes }.classes.toMutableSet()
)
}
/**
* The [Integrations] of this [PatcherContext].
*/
internal val integrations = Integrations()
/**
* Find a class by a given class name.
*
* @param className The name of the class.
* @return A proxy for the first class that matches the class name.
*/
fun findClass(className: String) = findClass { it.type.contains(className) }
/**
* Find a class by a given predicate.
*
* @param predicate A predicate to match the class.
* @return A proxy for the first class that matches the predicate.
*/
fun findClass(predicate: (ClassDef) -> Boolean) =
// if we already proxied the class matching the predicate...
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
// else resolve the class to a proxy and return it, if the predicate is matching a class
classes.find(predicate)?.let { proxy(it) }
/**
* Proxy a class.
* This will allow the class to be modified.
*
* @param classDef The class to proxy.
* @return A proxy for the class.
*/
fun proxy(classDef: ClassDef) = this.classes.proxies.find { it.immutableClass.type == classDef.type } ?: let {
ClassProxy(classDef).also { this.classes.add(it) }
}
/**
* Create a [MethodWalker] instance for the current [BytecodeContext].
*
* @param startMethod The method to start at.
* @return A [MethodWalker] instance.
*/
fun toMethodWalker(startMethod: Method) = MethodWalker(this, startMethod)
/**
* The integrations of a [PatcherContext].
*/
internal inner class Integrations : MutableList<File> by mutableListOf(), Flushable {
/**
* Whether to merge integrations.
* True when any supplied [Patch] is annotated with [RequiresIntegrations].
*/
var merge = false
/**
* Merge integrations into the [BytecodeContext] and flush all [Integrations].
*/
override fun flush() {
if (!merge) return
logger.info("Merging integrations")
this@Integrations.forEach { integrations ->
MultiDexIO.readDexFile(
true,
integrations, BasicDexFileNamer(),
null,
null
).classes.forEach classDef@{ classDef ->
val existingClass = classes.find { it == classDef } ?: run {
logger.fine("Merging $classDef")
classes.add(classDef)
return@classDef
}
logger.fine("$classDef exists. Adding missing methods and fields.")
existingClass.merge(classDef, this@BytecodeContext).let { mergedClass ->
// If the class was merged, replace the original class with the merged class.
if (mergedClass === existingClass) return@let
classes.apply { remove(existingClass); add(mergedClass) }
}
}
}
clear()
}
}
/**
* Compile bytecode from the [BytecodeContext].
*
* @return The compiled bytecode.
*/
override fun get(): List<PatcherResult.PatchedDexFile> {
logger.info("Compiling modified dex files")
return mutableMapOf<String, MemoryDataStore>().apply {
MultiDexIO.writeDexFile(
true, -1, // Defaults to amount of available cores.
this, BasicDexFileNamer(), object : DexFile {
override fun getClasses() = this@BytecodeContext.classes.also(ProxyClassList::replaceClasses)
override fun getOpcodes() = this@BytecodeContext.opcodes
}, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
)
}.map { PatcherResult.PatchedDexFile(it.key, it.value.readAt(0)) }
}
}

View File

@@ -1,9 +1,170 @@
package app.revanced.patcher.data package app.revanced.patcher.data
import java.util.function.Supplier import app.revanced.patcher.util.ProxyBackedClassList
import app.revanced.patcher.util.method.MethodWalker
import org.jf.dexlib2.iface.ClassDef
import org.jf.dexlib2.iface.Method
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
/** /**
* A common interface for contexts such as [ResourceContext] and [BytecodeContext]. * A common interface to constrain [Context] to [BytecodeContext] and [ResourceContext].
*/ */
sealed interface Context<T> : Supplier<T> sealed interface Context
class BytecodeContext internal constructor(classes: MutableList<ClassDef>) : Context {
/**
* The list of classes.
*/
val classes = ProxyBackedClassList(classes)
/**
* Find a class by a given class name.
*
* @param className The name of the class.
* @return A proxy for the first class that matches the class name.
*/
fun findClass(className: String) = findClass { it.type.contains(className) }
/**
* Find a class by a given predicate.
*
* @param predicate A predicate to match the class.
* @return A proxy for the first class that matches the predicate.
*/
fun findClass(predicate: (ClassDef) -> Boolean) =
// if we already proxied the class matching the predicate...
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
// else resolve the class to a proxy and return it, if the predicate is matching a class
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
}
}
/**
* Create a [MethodWalker] instance for the current [BytecodeContext].
*
* @param startMethod The method to start at.
* @return A [MethodWalker] instance.
*/
fun BytecodeContext.toMethodWalker(startMethod: Method): MethodWalker {
return MethodWalker(this, startMethod)
}
internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair<T, Int>? {
for ((index, element) in this.withIndex()) {
if (predicate(element)) {
return element to index
}
}
return null
}
class ResourceContext internal constructor(private val resourceCacheDirectory: File) : Context, 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) =
DomFileEditor(inputStream)
operator fun get(path: String): DomFileEditor {
return DomFileEditor(this@ResourceContext[path])
}
}
}
/**
* Wrapper for a file that can be edited as a dom document.
*
* This constructor does not check for locks to the file when writing.
* Use the secondary constructor.
*
* @param inputStream the input stream to read the xml file from.
* @param outputStream the output stream to write the xml file to. If null, the file will be read only.
*
*/
class DomFileEditor internal constructor(
private val inputStream: InputStream,
private val outputStream: Lazy<OutputStream>? = null,
) : Closeable {
// path to the xml file to unlock the resource when closing the editor
private var filePath: String? = null
private var closed: Boolean = false
/**
* The document of the xml file
*/
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
.also(Document::normalize)
// 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() }) {
// increase the lock
locks.merge(file.path, 1, Integer::sum)
filePath = file.path
}
/**
* Closes the editor. Write backs and decreases the lock count.
*
* Will not write back to the file if the file is still locked.
*/
override fun close() {
if (closed) return
inputStream.close()
// if the output stream is not null, do not close it
outputStream?.let {
// prevent writing to same file, if it is being locked
// isLocked will be false if the editor was created through a stream
val isLocked = filePath?.let { path ->
val isLocked = locks[path]!! > 1
// decrease the lock count if the editor was opened for a file
locks.merge(path, -1, Integer::sum)
isLocked
} ?: false
// if unlocked, write back to the file
if (!isLocked) {
it.value.use { stream ->
val result = StreamResult(stream)
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), result)
}
it.value.close()
return
}
}
closed = true
}
private companion object {
// map of concurrent open files
val locks = mutableMapOf<String, Int>()
}
}

View File

@@ -1,14 +1,15 @@
package app.revanced.patcher package app.revanced.patcher.data
import brut.androlib.apk.ApkInfo import brut.androlib.apk.ApkInfo
/** /**
* Metadata about a package. * Metadata about a package.
*/ */
class PackageMetadata internal constructor(internal val apkInfo: ApkInfo) { class PackageMetadata {
lateinit var packageName: String lateinit var packageName: String
internal set internal set
lateinit var packageVersion: String lateinit var packageVersion: String
internal set internal set
internal lateinit var apkInfo: ApkInfo
} }

View File

@@ -1,160 +0,0 @@
package app.revanced.patcher.data
import app.revanced.patcher.PatcherContext
import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.util.DomFileEditor
import brut.androlib.AaptInvoker
import brut.androlib.ApkDecoder
import brut.androlib.apk.UsesFramework
import brut.androlib.res.Framework
import brut.androlib.res.ResourcesDecoder
import brut.androlib.res.decoder.AndroidManifestResourceParser
import brut.androlib.res.decoder.XmlPullStreamDecoder
import brut.androlib.res.xml.ResXmlPatcher
import brut.directory.ExtFile
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Files
import java.util.logging.Logger
/**
* A context for resources.
* This holds the current state of the resources.
*
* @param context The [PatcherContext] to create the context for.
*/
class ResourceContext internal constructor(
private val context: PatcherContext,
private val options: PatcherOptions
) : Context<File?>, Iterable<File> {
private val logger = Logger.getLogger(ResourceContext::class.java.name)
val xmlEditor = XmlFileHolder()
/**
* Decode resources for the patcher.
*
* @param mode The [ResourceDecodingMode] to use when decoding.
*/
internal fun decodeResources(mode: ResourceDecodingMode) = with(context.packageMetadata.apkInfo) {
// Needed to decode resources.
val resourcesDecoder = ResourcesDecoder(options.resourceConfig, this)
when (mode) {
ResourceDecodingMode.FULL -> {
val outDir = options.recreateResourceCacheDirectory()
logger.info("Decoding resources")
resourcesDecoder.decodeResources(outDir)
resourcesDecoder.decodeManifest(outDir)
// Needed to record uncompressed files.
val apkDecoder = ApkDecoder(options.resourceConfig, this)
apkDecoder.recordUncompressedFiles(resourcesDecoder.resFileMapping)
usesFramework = UsesFramework().apply {
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
}
}
ResourceDecodingMode.MANIFEST_ONLY -> {
logger.info("Decoding app manifest")
// Decode manually instead of using resourceDecoder.decodeManifest
// because it does not support decoding to an OutputStream.
XmlPullStreamDecoder(
AndroidManifestResourceParser(resourcesDecoder.resTable),
resourcesDecoder.resXmlSerializer
).decodeManifest(
apkFile.directory.getFileInput("AndroidManifest.xml"),
// Older Android versions do not support OutputStream.nullOutputStream()
object : OutputStream() {
override fun write(b: Int) { /* do nothing */
}
}
)
// Get the package name and version from the manifest using the XmlPullStreamDecoder.
// XmlPullStreamDecoder.decodeManifest() sets metadata.apkInfo.
context.packageMetadata.let { metadata ->
metadata.packageName = resourcesDecoder.resTable.packageRenamed
versionInfo.let {
metadata.packageVersion = it.versionName ?: it.versionCode
}
}
}
}
}
operator fun get(path: String) = options.resourceCachePath.resolve(path)
override fun iterator() = options.resourceCachePath.walkTopDown().iterator()
/**
* Compile resources from the [ResourceContext].
*
* @return The compiled resources.
*/
override fun get(): File? {
var resourceFile: File? = null
if (options.resourceDecodingMode == ResourceDecodingMode.FULL) {
logger.info("Compiling modified resources")
val cacheDirectory = ExtFile(options.resourceCachePath)
val aaptFile = cacheDirectory.resolve("aapt_temp_file").also {
Files.deleteIfExists(it.toPath())
}.also { resourceFile = it }
try {
AaptInvoker(
options.resourceConfig, context.packageMetadata.apkInfo
).invokeAapt(aaptFile,
cacheDirectory.resolve("AndroidManifest.xml").also {
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(it)
},
cacheDirectory.resolve("res"),
null,
null,
context.packageMetadata.apkInfo.usesFramework.let { usesFramework ->
usesFramework.ids.map { id ->
Framework(options.resourceConfig).getFrameworkApk(id, usesFramework.tag)
}.toTypedArray()
})
} finally {
cacheDirectory.close()
}
}
return resourceFile
}
/**
* The type of decoding the resources.
*/
internal enum class ResourceDecodingMode {
/**
* Decode all resources.
*/
FULL,
/**
* Decode the manifest file only.
*/
MANIFEST_ONLY,
}
inner class XmlFileHolder {
operator fun get(inputStream: InputStream) =
DomFileEditor(inputStream)
operator fun get(path: String): DomFileEditor {
return DomFileEditor(this@ResourceContext[path])
}
}
}

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.extensions package app.revanced.patcher.extensions
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import com.android.tools.smali.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
/** /**
* Create a label for the instruction at given index. * Create a label for the instruction at given index.

View File

@@ -4,12 +4,12 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patcher.util.smali.toInstruction import app.revanced.patcher.util.smali.toInstruction
import app.revanced.patcher.util.smali.toInstructions import app.revanced.patcher.util.smali.toInstructions
import com.android.tools.smali.dexlib2.builder.BuilderInstruction import org.jf.dexlib2.builder.BuilderInstruction
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction import org.jf.dexlib2.builder.BuilderOffsetInstruction
import com.android.tools.smali.dexlib2.builder.Label import org.jf.dexlib2.builder.Label
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation import org.jf.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.builder.instruction.* import org.jf.dexlib2.builder.instruction.*
import com.android.tools.smali.dexlib2.iface.instruction.Instruction import org.jf.dexlib2.iface.instruction.Instruction
object InstructionExtensions { object InstructionExtensions {
@@ -22,7 +22,8 @@ object InstructionExtensions {
fun MutableMethodImplementation.addInstructions( fun MutableMethodImplementation.addInstructions(
index: Int, index: Int,
instructions: List<BuilderInstruction> instructions: List<BuilderInstruction>
) = instructions.asReversed().forEach { addInstruction(index, it) } ) =
instructions.asReversed().forEach { addInstruction(index, it) }
/** /**
* Add instructions to a method. * Add instructions to a method.
@@ -320,11 +321,6 @@ object InstructionExtensions {
* @param T The type of instruction to return. * @param T The type of instruction to return.
* @return The instruction. * @return The instruction.
*/ */
@Suppress("UNCHECKED_CAST")
fun <T> MutableMethod.getInstruction(index: Int): T = implementation!!.getInstruction<T>(index) fun <T> MutableMethod.getInstruction(index: Int): T = implementation!!.getInstruction<T>(index)
/**
* Get the instructions of a method.
* @return The instructions.
*/
fun MutableMethod.getInstructions(): MutableList<BuilderInstruction> = implementation!!.instructions
} }

View File

@@ -3,10 +3,11 @@ package app.revanced.patcher.extensions
import app.revanced.patcher.annotation.Compatibility 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.data.Context
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
import app.revanced.patcher.patch.OptionsContainer import app.revanced.patcher.patch.OptionsContainer
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchClass
import app.revanced.patcher.patch.PatchOptions import app.revanced.patcher.patch.PatchOptions
import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.RequiresIntegrations import app.revanced.patcher.patch.annotations.RequiresIntegrations
@@ -18,43 +19,49 @@ object PatchExtensions {
/** /**
* The name of a [Patch]. * The name of a [Patch].
*/ */
val PatchClass.patchName: String val Class<out Patch<Context>>.patchName: String
get() = findAnnotationRecursively(Name::class)?.name ?: this.simpleName get() = findAnnotationRecursively(Name::class)?.name ?: this.simpleName
/**
* The version of a [Patch].
*/
val Class<out Patch<Context>>.version
get() = findAnnotationRecursively(Version::class)?.version
/** /**
* Weather or not a [Patch] should be included. * Weather or not a [Patch] should be included.
*/ */
val PatchClass.include val Class<out Patch<Context>>.include
get() = findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)!!.include get() = findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)!!.include
/** /**
* The description of a [Patch]. * The description of a [Patch].
*/ */
val PatchClass.description val Class<out Patch<Context>>.description
get() = findAnnotationRecursively(Description::class)?.description get() = findAnnotationRecursively(Description::class)?.description
/** /**
* The dependencies of a [Patch]. * The dependencies of a [Patch].
*/ */
val PatchClass.dependencies val Class<out Patch<Context>>.dependencies
get() = findAnnotationRecursively(DependsOn::class)?.dependencies get() = findAnnotationRecursively(DependsOn::class)?.dependencies
/** /**
* The packages a [Patch] is compatible with. * The packages a [Patch] is compatible with.
*/ */
val PatchClass.compatiblePackages val Class<out Patch<Context>>.compatiblePackages
get() = findAnnotationRecursively(Compatibility::class)?.compatiblePackages get() = findAnnotationRecursively(Compatibility::class)?.compatiblePackages
/** /**
* Weather or not a [Patch] requires integrations. * Weather or not a [Patch] requires integrations.
*/ */
internal val PatchClass.requiresIntegrations internal val Class<out Patch<Context>>.requiresIntegrations
get() = findAnnotationRecursively(RequiresIntegrations::class) != null get() = findAnnotationRecursively(RequiresIntegrations::class) != null
/** /**
* The options of a [Patch]. * The options of a [Patch].
*/ */
val PatchClass.options: PatchOptions? val Class<out Patch<Context>>.options: PatchOptions?
get() = kotlin.companionObject?.let { cl -> get() = kotlin.companionObject?.let { cl ->
if (cl.visibility != KVisibility.PUBLIC) return null if (cl.visibility != KVisibility.PUBLIC) return null
kotlin.companionObjectInstance?.let { kotlin.companionObjectInstance?.let {

View File

@@ -4,16 +4,16 @@ import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.Fingerprint import app.revanced.patcher.fingerprint.Fingerprint
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchResultError
import app.revanced.patcher.util.proxy.ClassProxy import app.revanced.patcher.util.proxy.ClassProxy
import com.android.tools.smali.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import org.jf.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method import org.jf.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction import org.jf.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference import org.jf.dexlib2.iface.reference.StringReference
import com.android.tools.smali.dexlib2.util.MethodUtil import org.jf.dexlib2.util.MethodUtil
import java.util.* import java.util.*
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
@@ -99,9 +99,9 @@ abstract class MethodFingerprint(
methodClassPairs!!.add(methodClassPair) methodClassPairs!!.add(methodClassPair)
} }
if (methods.isNotEmpty()) clearFingerprintResolutionLookupMaps() if (methods.isNotEmpty()) throw PatchResultError("Map already initialized")
context.classes.forEach { classDef -> context.classes.classes.forEach { classDef ->
classDef.methods.forEach { method -> classDef.methods.forEach { method ->
val methodClassPair = method to classDef val methodClassPair = method to classDef
@@ -160,7 +160,7 @@ abstract class MethodFingerprint(
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match. * - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
*/ */
internal fun Iterable<MethodFingerprint>.resolveUsingLookupMap(context: BytecodeContext) { internal fun Iterable<MethodFingerprint>.resolveUsingLookupMap(context: BytecodeContext) {
if (methods.isEmpty()) throw PatchException("lookup map not initialized") if (methods.isEmpty()) throw PatchResultError("lookup map not initialized")
for (fingerprint in this) { for (fingerprint in this) {
fingerprint.resolveUsingLookupMap(context) fingerprint.resolveUsingLookupMap(context)
@@ -510,4 +510,4 @@ data class MethodFingerprintResult(
) )
} }
} }
} }

View File

@@ -1,6 +1,5 @@
package app.revanced.patcher.logging package app.revanced.patcher.logging
@Deprecated("This will be removed in a future release")
interface Logger { interface Logger {
fun error(msg: String) {} fun error(msg: String) {}
fun warn(msg: String) {} fun warn(msg: String) {}

View File

@@ -2,5 +2,4 @@ package app.revanced.patcher.logging.impl
import app.revanced.patcher.logging.Logger import app.revanced.patcher.logging.Logger
@Deprecated("This will be removed in a future release")
object NopLogger : Logger object NopLogger : Logger

View File

@@ -6,22 +6,20 @@ import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import java.io.Closeable import java.io.Closeable
typealias PatchClass = Class<out Patch<Context<*>>>
/** /**
* A ReVanced patch. * A ReVanced patch.
* *
* If it implements [Closeable], it will be closed after all patches have been executed. * If it implements [Closeable], it will be closed after all patches have been executed.
* Closing will be done in reverse execution order. * Closing will be done in reverse execution order.
*/ */
sealed interface Patch<out T : Context<*>> { sealed interface Patch<out T : Context> {
/** /**
* The main function of the [Patch] which the patcher will call. * The main function of the [Patch] which the patcher will call.
* *
* @param context The [Context] the patch will work on. * @param context The [Context] the patch will work on.
* @return The result of executing the patch. * @return The result of executing the patch.
*/ */
fun execute(context: @UnsafeVariance T) fun execute(context: @UnsafeVariance T): PatchResult
} }
/** /**

View File

@@ -1,12 +0,0 @@
package app.revanced.patcher.patch
/**
* An exception thrown when patching.
*
* @param errorMessage The exception message.
* @param cause The corresponding [Throwable].
*/
class PatchException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
constructor(errorMessage: String) : this(errorMessage, null)
constructor(cause: Throwable) : this(cause.message, cause)
}

View File

@@ -79,10 +79,10 @@ class PatchOptions(vararg options: PatchOption<*>) : Iterable<PatchOption<*>> {
/** /**
* A [Patch] option. * A [Patch] option.
* @param key Unique identifier of the option. Example: _`settings`_ * @param key Unique identifier of the option. Example: _`settings.microg.enabled`_
* @param default The default value of the option. * @param default The default value of the option.
* @param title A human-readable title of the option. Example: _Patch Settings_ * @param title A human-readable title of the option. Example: _MicroG Settings_
* @param description A human-readable description of the option. Example: _Settings for the patches._ * @param description A human-readable description of the option. Example: _Settings integration for MicroG._
* @param required Whether the option is required. * @param required Whether the option is required.
*/ */
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")

View File

@@ -1,10 +1,35 @@
package app.revanced.patcher.patch package app.revanced.patcher.patch
/** interface PatchResult {
* A result of executing a [Patch]. fun error(): PatchResultError? {
* if (this is PatchResultError) {
* @param patchName The name of the [Patch]. return this
* @param exception The [PatchException] thrown, if any. }
*/ return null
@Suppress("MemberVisibilityCanBePrivate") }
class PatchResult internal constructor(val patchName: String, val exception: PatchException? = null)
fun success(): PatchResultSuccess? {
if (this is PatchResultSuccess) {
return this
}
return null
}
fun isError(): Boolean {
return this is PatchResultError
}
fun isSuccess(): Boolean {
return this is PatchResultSuccess
}
}
class PatchResultError(
errorMessage: String?, cause: Exception?
) : Exception(errorMessage, cause), PatchResult {
constructor(errorMessage: String) : this(errorMessage, null)
constructor(cause: Exception) : this(cause.message, cause)
}
class PatchResultSuccess : PatchResult

View File

@@ -16,12 +16,12 @@ annotation class Patch(val include: Boolean = true)
*/ */
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
annotation class DependsOn( annotation class DependsOn(
val dependencies: Array<KClass<out Patch<Context<*>>>> = [] val dependencies: Array<KClass<out Patch<Context>>> = []
) )
// TODO: Remove this annotation, once integrations are coupled with patches.
/** /**
* Annotation to mark [Patch]es which depend on integrations. * Annotation to mark [Patch]es which depend on integrations.
*/ */
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
annotation class RequiresIntegrations annotation class RequiresIntegrations // required because integrations are decoupled from patches

View File

@@ -1,23 +1,23 @@
package app.revanced.patcher.util package app.revanced.patcher.util
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.PatcherContext
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.logging.Logger
import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass
import app.revanced.patcher.util.ClassMerger.Utils.filterAny import app.revanced.patcher.util.ClassMerger.Utils.filterAny
import app.revanced.patcher.util.ClassMerger.Utils.filterNotAny import app.revanced.patcher.util.ClassMerger.Utils.filterNotAny
import app.revanced.patcher.util.ClassMerger.Utils.isPublic import app.revanced.patcher.util.ClassMerger.Utils.isPublic
import app.revanced.patcher.util.ClassMerger.Utils.toPublic import app.revanced.patcher.util.ClassMerger.Utils.toPublic
import app.revanced.patcher.util.ClassMerger.Utils.traverseClassHierarchy import app.revanced.patcher.util.TypeUtil.traverseClassHierarchy
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableField import app.revanced.patcher.util.proxy.mutableTypes.MutableField
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 import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import com.android.tools.smali.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.util.MethodUtil import org.jf.dexlib2.util.MethodUtil
import java.util.logging.Logger
import kotlin.reflect.KFunction2 import kotlin.reflect.KFunction2
/** /**
@@ -25,28 +25,27 @@ import kotlin.reflect.KFunction2
* Note: This will not consider method implementations or if the class is missing a superclass or interfaces. * Note: This will not consider method implementations or if the class is missing a superclass or interfaces.
*/ */
internal object ClassMerger { internal object ClassMerger {
private val logger = Logger.getLogger(ClassMerger::class.java.name)
/** /**
* Merge a class with [otherClass]. * Merge a class with [otherClass].
* *
* @param otherClass The class to merge with * @param otherClass The class to merge with
* @param context The context to traverse the class hierarchy in. * @param context The context to traverse the class hierarchy in.
* @return The merged class or the original class if no merge was needed. * @param logger A logger.
*/ */
fun ClassDef.merge(otherClass: ClassDef, context: BytecodeContext) = this fun ClassDef.merge(otherClass: ClassDef, context: PatcherContext, logger: Logger? = null) = this
//.fixFieldAccess(otherClass) //.fixFieldAccess(otherClass, logger)
//.fixMethodAccess(otherClass) //.fixMethodAccess(otherClass, logger)
.addMissingFields(otherClass) .addMissingFields(otherClass, logger)
.addMissingMethods(otherClass) .addMissingMethods(otherClass, logger)
.publicize(otherClass, context) .publicize(otherClass, context, logger)
/** /**
* Add methods which are missing but existing in [fromClass]. * Add methods which are missing but existing in [fromClass].
* *
* @param fromClass The class to add missing methods from. * @param fromClass The class to add missing methods from.
* @param logger A logger.
*/ */
private fun ClassDef.addMissingMethods(fromClass: ClassDef): ClassDef { private fun ClassDef.addMissingMethods(fromClass: ClassDef, logger: Logger? = null): ClassDef {
val missingMethods = fromClass.methods.let { fromMethods -> val missingMethods = fromClass.methods.let { fromMethods ->
methods.filterNot { method -> methods.filterNot { method ->
fromMethods.any { fromMethod -> fromMethods.any { fromMethod ->
@@ -57,7 +56,7 @@ internal object ClassMerger {
if (missingMethods.isEmpty()) return this if (missingMethods.isEmpty()) return this
logger.fine("Found ${missingMethods.size} missing methods") logger?.trace("Found ${missingMethods.size} missing methods")
return asMutableClass().apply { return asMutableClass().apply {
methods.addAll(missingMethods.map { it.toMutable() }) methods.addAll(missingMethods.map { it.toMutable() })
@@ -68,15 +67,16 @@ internal object ClassMerger {
* Add fields which are missing but existing in [fromClass]. * Add fields which are missing but existing in [fromClass].
* *
* @param fromClass The class to add missing fields from. * @param fromClass The class to add missing fields from.
* @param logger A logger.
*/ */
private fun ClassDef.addMissingFields(fromClass: ClassDef): ClassDef { private fun ClassDef.addMissingFields(fromClass: ClassDef, logger: Logger? = null): ClassDef {
val missingFields = fields.filterNotAny(fromClass.fields) { field, fromField -> val missingFields = fields.filterNotAny(fromClass.fields) { field, fromField ->
fromField.name == field.name fromField.name == field.name
} }
if (missingFields.isEmpty()) return this if (missingFields.isEmpty()) return this
logger.fine("Found ${missingFields.size} missing fields") logger?.trace("Found ${missingFields.size} missing fields")
return asMutableClass().apply { return asMutableClass().apply {
fields.addAll(missingFields.map { it.toMutable() }) fields.addAll(missingFields.map { it.toMutable() })
@@ -87,14 +87,15 @@ internal object ClassMerger {
* Make a class and its super class public recursively. * Make a class and its super class public recursively.
* @param reference The class to check the [AccessFlags] of. * @param reference The class to check the [AccessFlags] of.
* @param context The context to traverse the class hierarchy in. * @param context The context to traverse the class hierarchy in.
* @param logger A logger.
*/ */
private fun ClassDef.publicize(reference: ClassDef, context: BytecodeContext) = private fun ClassDef.publicize(reference: ClassDef, context: PatcherContext, logger: Logger? = null) =
if (reference.accessFlags.isPublic() && !accessFlags.isPublic()) if (reference.accessFlags.isPublic() && !accessFlags.isPublic())
this.asMutableClass().apply { this.asMutableClass().apply {
context.traverseClassHierarchy(this) { context.bytecodeContext.traverseClassHierarchy(this) {
if (accessFlags.isPublic()) return@traverseClassHierarchy if (accessFlags.isPublic()) return@traverseClassHierarchy
logger.fine("Publicizing ${this.type}") logger?.trace("Publicizing ${this.type}")
accessFlags = accessFlags.toPublic() accessFlags = accessFlags.toPublic()
} }
@@ -105,8 +106,9 @@ internal object ClassMerger {
* Publicize fields if they are public in [reference]. * Publicize fields if they are public in [reference].
* *
* @param reference The class to check the [AccessFlags] of the fields in. * @param reference The class to check the [AccessFlags] of the fields in.
* @param logger A logger.
*/ */
private fun ClassDef.fixFieldAccess(reference: ClassDef): ClassDef { private fun ClassDef.fixFieldAccess(reference: ClassDef, logger: Logger? = null): ClassDef {
val brokenFields = fields.filterAny(reference.fields) { field, referenceField -> val brokenFields = fields.filterAny(reference.fields) { field, referenceField ->
if (field.name != referenceField.name) return@filterAny false if (field.name != referenceField.name) return@filterAny false
@@ -115,7 +117,7 @@ internal object ClassMerger {
if (brokenFields.isEmpty()) return this if (brokenFields.isEmpty()) return this
logger.fine("Found ${brokenFields.size} broken fields") logger?.trace("Found ${brokenFields.size} broken fields")
/** /**
* Make a field public. * Make a field public.
@@ -133,8 +135,9 @@ internal object ClassMerger {
* Publicize methods if they are public in [reference]. * Publicize methods if they are public in [reference].
* *
* @param reference The class to check the [AccessFlags] of the methods in. * @param reference The class to check the [AccessFlags] of the methods in.
* @param logger A logger.
*/ */
private fun ClassDef.fixMethodAccess(reference: ClassDef): ClassDef { private fun ClassDef.fixMethodAccess(reference: ClassDef, logger: Logger? = null): ClassDef {
val brokenMethods = methods.filterAny(reference.methods) { method, referenceMethod -> val brokenMethods = methods.filterAny(reference.methods) { method, referenceMethod ->
if (!MethodUtil.methodSignaturesMatch(method, referenceMethod)) return@filterAny false if (!MethodUtil.methodSignaturesMatch(method, referenceMethod)) return@filterAny false
@@ -143,7 +146,7 @@ internal object ClassMerger {
if (brokenMethods.isEmpty()) return this if (brokenMethods.isEmpty()) return this
logger.fine("Found ${brokenMethods.size} methods") logger?.trace("Found ${brokenMethods.size} methods")
/** /**
* Make a method public. * Make a method public.
@@ -158,19 +161,6 @@ internal object ClassMerger {
} }
private object Utils { private object Utils {
/**
* traverse the class hierarchy starting from the given root class
*
* @param targetClass the class to start traversing the class hierarchy from
* @param callback function that is called for every class in the hierarchy
*/
fun BytecodeContext.traverseClassHierarchy(targetClass: MutableClass, callback: MutableClass.() -> Unit) {
callback(targetClass)
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
traverseClassHierarchy(it, callback)
}
}
fun ClassDef.asMutableClass() = if (this is MutableClass) this else this.toMutable() fun ClassDef.asMutableClass() = if (this is MutableClass) this else this.toMutable()
/** /**
@@ -190,6 +180,7 @@ internal object ClassMerger {
/** /**
* Filter [this] on [needles] matching the given [predicate]. * Filter [this] on [needles] matching the given [predicate].
* *
* @param this The hay to filter for [needles].
* @param needles The needles to filter [this] with. * @param needles The needles to filter [this] with.
* @param predicate The filter. * @param predicate The filter.
* @return The [this] filtered on [needles] matching the given [predicate]. * @return The [this] filtered on [needles] matching the given [predicate].
@@ -201,6 +192,7 @@ internal object ClassMerger {
/** /**
* Filter [this] on [needles] not matching the given [predicate]. * Filter [this] on [needles] not matching the given [predicate].
* *
* @param this The hay to filter for [needles].
* @param needles The needles to filter [this] with. * @param needles The needles to filter [this] with.
* @param predicate The filter. * @param predicate The filter.
* @return The [this] filtered on [needles] not matching the given [predicate]. * @return The [this] filtered on [needles] not matching the given [predicate].

View File

@@ -1,87 +0,0 @@
package app.revanced.patcher.util
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
/**
* Wrapper for a file that can be edited as a dom document.
*
* This constructor does not check for locks to the file when writing.
* Use the secondary constructor.
*
* @param inputStream the input stream to read the xml file from.
* @param outputStream the output stream to write the xml file to. If null, the file will be read only.
*
*/
class DomFileEditor internal constructor(
private val inputStream: InputStream,
private val outputStream: Lazy<OutputStream>? = null,
) : Closeable {
// path to the xml file to unlock the resource when closing the editor
private var filePath: String? = null
private var closed: Boolean = false
/**
* The document of the xml file
*/
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
.also(Document::normalize)
// 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() }) {
// increase the lock
locks.merge(file.path, 1, Integer::sum)
filePath = file.path
}
/**
* Closes the editor. Write backs and decreases the lock count.
*
* Will not write back to the file if the file is still locked.
*/
override fun close() {
if (closed) return
inputStream.close()
// if the output stream is not null, do not close it
outputStream?.let {
// prevent writing to same file, if it is being locked
// isLocked will be false if the editor was created through a stream
val isLocked = filePath?.let { path ->
val isLocked = locks[path]!! > 1
// decrease the lock count if the editor was opened for a file
locks.merge(path, -1, Integer::sum)
isLocked
} ?: false
// if unlocked, write back to the file
if (!isLocked) {
it.value.use { stream ->
val result = StreamResult(stream)
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), result)
}
it.value.close()
return
}
}
closed = true
}
private companion object {
// map of concurrent open files
val locks = mutableMapOf<String, Int>()
}
}

View File

@@ -0,0 +1,15 @@
package app.revanced.patcher.util
internal class ListBackedSet<E>(private val list: MutableList<E>) : MutableSet<E> {
override val size get() = list.size
override fun add(element: E) = list.add(element)
override fun addAll(elements: Collection<E>) = list.addAll(elements)
override fun clear() = list.clear()
override fun iterator() = list.listIterator()
override fun remove(element: E) = list.remove(element)
override fun removeAll(elements: Collection<E>) = list.removeAll(elements)
override fun retainAll(elements: Collection<E>) = list.retainAll(elements)
override fun contains(element: E) = list.contains(element)
override fun containsAll(elements: Collection<E>) = list.containsAll(elements)
override fun isEmpty() = list.isEmpty()
}

View File

@@ -0,0 +1,46 @@
package app.revanced.patcher.util
import app.revanced.patcher.util.proxy.ClassProxy
import org.jf.dexlib2.iface.ClassDef
/**
* A class that represents a set of classes and proxies.
*
* @param classes The classes to be backed by proxies.
*/
class ProxyBackedClassList(internal val classes: MutableList<ClassDef>) : Set<ClassDef> {
internal val proxies = mutableListOf<ClassProxy>()
/**
* Add a [ClassDef].
*/
fun add(classDef: ClassDef) = classes.add(classDef)
/**
* Add a [ClassProxy].
*/
fun add(classProxy: ClassProxy) = proxies.add(classProxy)
/**
* Replace all classes with their mutated versions.
*/
internal fun replaceClasses() =
proxies.removeIf { proxy ->
// if the proxy is unused, keep it in the list
if (!proxy.resolved) return@removeIf false
// if it has been used, replace the original class with the new class
val index = classes.indexOfFirst { it.type == proxy.immutableClass.type }
classes[index] = proxy.mutableClass
// return true to remove it from the proxies list
return@removeIf true
}
override val size get() = classes.size
override fun contains(element: ClassDef) = classes.contains(element)
override fun containsAll(elements: Collection<ClassDef>) = classes.containsAll(elements)
override fun isEmpty() = classes.isEmpty()
override fun iterator() = classes.iterator()
}

View File

@@ -1,33 +0,0 @@
package app.revanced.patcher.util
import app.revanced.patcher.util.proxy.ClassProxy
import com.android.tools.smali.dexlib2.iface.ClassDef
/**
* A class that represents a set of classes and proxies.
*
* @param classes The classes to be backed by proxies.
*/
class ProxyClassList internal constructor(classes: MutableSet<ClassDef>) : MutableSet<ClassDef> by classes {
internal val proxies = mutableListOf<ClassProxy>()
/**
* Add a [ClassProxy].
*/
fun add(classProxy: ClassProxy) = proxies.add(classProxy)
/**
* Replace all classes with their mutated versions.
*/
internal fun replaceClasses() = proxies.removeIf { proxy ->
// If the proxy is unused, return false to keep it in the proxies list.
if (!proxy.resolved) return@removeIf false
// If it has been used, replace the original class with the mutable class.
remove(proxy.immutableClass)
add(proxy.mutableClass)
// Return true to remove the proxy from the proxies list.
return@removeIf true
}
}

View File

@@ -0,0 +1,19 @@
package app.revanced.patcher.util
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
object TypeUtil {
/**
* traverse the class hierarchy starting from the given root class
*
* @param targetClass the class to start traversing the class hierarchy from
* @param callback function that is called for every class in the hierarchy
*/
fun BytecodeContext.traverseClassHierarchy(targetClass: MutableClass, callback: MutableClass.() -> Unit) {
callback(targetClass)
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
traverseClassHierarchy(it, callback)
}
}
}

View File

@@ -0,0 +1,10 @@
package app.revanced.patcher.util.dex
import java.io.InputStream
/**
* Wrapper for dex files.
* @param name The original name of the dex file.
* @param stream The dex file as [InputStream].
*/
data class DexFile(val name: String, val stream: InputStream)

View File

@@ -2,10 +2,10 @@ package app.revanced.patcher.util.method
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import com.android.tools.smali.dexlib2.iface.Method import org.jf.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import org.jf.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil import org.jf.dexlib2.util.MethodUtil
/** /**
* Find a method from another method via instruction offsets. * Find a method from another method via instruction offsets.

View File

@@ -0,0 +1,76 @@
@file:Suppress("unused")
package app.revanced.patcher.util.patch
import app.revanced.patcher.data.Context
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.Patch
import org.jf.dexlib2.DexFileFactory
import java.io.File
import java.net.URLClassLoader
import java.util.jar.JarFile
/**
* A patch bundle.
* @param path The path to the patch bundle.
*/
sealed class PatchBundle(path: String) : File(path) {
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
classNames.forEach { className ->
val clazz = classLoader.loadClass(className)
// Annotations can not Patch.
if (clazz.isAnnotation) return@forEach
clazz.findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)
?: return@forEach
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
}
}.sortedBy { it.patchName }
/**
* A patch bundle of type [Jar].
*
* @param patchBundlePath The path to the patch bundle.
*/
class Jar(patchBundlePath: String) : PatchBundle(patchBundlePath) {
/**
* Load patches from the patch bundle.
*
* Patches will be loaded with a new [URLClassLoader].
*/
fun loadPatches() = loadPatches(
URLClassLoader(
arrayOf(this.toURI().toURL()),
Thread.currentThread().contextClassLoader // TODO: find out why this is required
),
JarFile(this)
.stream()
.filter { it.name.endsWith(".class") && !it.name.contains("$") }
.map { it.realName.replace('/', '.').replace(".class", "") }.iterator()
)
}
/**
* A patch bundle of type [Dex] format.
*
* @param patchBundlePath The path to a patch bundle of dex format.
* @param dexClassLoader The dex class loader.
*/
class Dex(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
/**
* Load patches from the patch bundle.
*
* Patches will be loaded to the provided [dexClassLoader].
*/
fun loadPatches() = loadPatches(dexClassLoader,
DexFileFactory.loadDexFile(path, null).classes.asSequence().map { classDef ->
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
}.iterator()
)
}
}

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy package app.revanced.patcher.util.proxy
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import com.android.tools.smali.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.ClassDef
/** /**
* A proxy class for a [ClassDef]. * A proxy class for a [ClassDef].

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher.util.proxy.mutableTypes package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
import com.android.tools.smali.dexlib2.base.BaseAnnotation import org.jf.dexlib2.base.BaseAnnotation
import com.android.tools.smali.dexlib2.iface.Annotation import org.jf.dexlib2.iface.Annotation
class MutableAnnotation(annotation: Annotation) : BaseAnnotation() { class MutableAnnotation(annotation: Annotation) : BaseAnnotation() {
private val visibility = annotation.visibility private val visibility = annotation.visibility

View File

@@ -2,9 +2,9 @@ package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
import com.android.tools.smali.dexlib2.base.BaseAnnotationElement import org.jf.dexlib2.base.BaseAnnotationElement
import com.android.tools.smali.dexlib2.iface.AnnotationElement import org.jf.dexlib2.iface.AnnotationElement
import com.android.tools.smali.dexlib2.iface.value.EncodedValue import org.jf.dexlib2.iface.value.EncodedValue
class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnotationElement() { class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnotationElement() {
private var name = annotationElement.name private var name = annotationElement.name

View File

@@ -4,10 +4,10 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.
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
import com.google.common.collect.Iterables import com.google.common.collect.Iterables
import com.android.tools.smali.dexlib2.base.reference.BaseTypeReference import org.jf.dexlib2.base.reference.BaseTypeReference
import com.android.tools.smali.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.util.FieldUtil import org.jf.dexlib2.util.FieldUtil
import com.android.tools.smali.dexlib2.util.MethodUtil import org.jf.dexlib2.util.MethodUtil
class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() { class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
// Class // Class

View File

@@ -3,9 +3,9 @@ package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
import com.android.tools.smali.dexlib2.HiddenApiRestriction import org.jf.dexlib2.HiddenApiRestriction
import com.android.tools.smali.dexlib2.base.reference.BaseFieldReference import org.jf.dexlib2.base.reference.BaseFieldReference
import com.android.tools.smali.dexlib2.iface.Field import org.jf.dexlib2.iface.Field
class MutableField(field: Field) : Field, BaseFieldReference() { class MutableField(field: Field) : Field, BaseFieldReference() {
private var definingClass = field.definingClass private var definingClass = field.definingClass

View File

@@ -2,10 +2,10 @@ package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethodParameter.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethodParameter.Companion.toMutable
import com.android.tools.smali.dexlib2.HiddenApiRestriction import org.jf.dexlib2.HiddenApiRestriction
import com.android.tools.smali.dexlib2.base.reference.BaseMethodReference import org.jf.dexlib2.base.reference.BaseMethodReference
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation import org.jf.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.Method import org.jf.dexlib2.iface.Method
class MutableMethod(method: Method) : Method, BaseMethodReference() { class MutableMethod(method: Method) : Method, BaseMethodReference() {
private var definingClass = method.definingClass private var definingClass = method.definingClass

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher.util.proxy.mutableTypes package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import com.android.tools.smali.dexlib2.base.BaseMethodParameter import org.jf.dexlib2.base.BaseMethodParameter
import com.android.tools.smali.dexlib2.iface.MethodParameter import org.jf.dexlib2.iface.MethodParameter
// TODO: finish overriding all members if necessary // TODO: finish overriding all members if necessary
class MutableMethodParameter(parameter: MethodParameter) : MethodParameter, BaseMethodParameter() { class MutableMethodParameter(parameter: MethodParameter) : MethodParameter, BaseMethodParameter() {

View File

@@ -1,9 +1,9 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
import com.android.tools.smali.dexlib2.base.value.BaseAnnotationEncodedValue import org.jf.dexlib2.base.value.BaseAnnotationEncodedValue
import com.android.tools.smali.dexlib2.iface.AnnotationElement import org.jf.dexlib2.iface.AnnotationElement
import com.android.tools.smali.dexlib2.iface.value.AnnotationEncodedValue import org.jf.dexlib2.iface.value.AnnotationEncodedValue
class MutableAnnotationEncodedValue(annotationEncodedValue: AnnotationEncodedValue) : BaseAnnotationEncodedValue(), class MutableAnnotationEncodedValue(annotationEncodedValue: AnnotationEncodedValue) : BaseAnnotationEncodedValue(),
MutableEncodedValue { MutableEncodedValue {

View File

@@ -1,9 +1,9 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
import com.android.tools.smali.dexlib2.base.value.BaseArrayEncodedValue import org.jf.dexlib2.base.value.BaseArrayEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ArrayEncodedValue import org.jf.dexlib2.iface.value.ArrayEncodedValue
import com.android.tools.smali.dexlib2.iface.value.EncodedValue import org.jf.dexlib2.iface.value.EncodedValue
class MutableArrayEncodedValue(arrayEncodedValue: ArrayEncodedValue) : BaseArrayEncodedValue(), MutableEncodedValue { class MutableArrayEncodedValue(arrayEncodedValue: ArrayEncodedValue) : BaseArrayEncodedValue(), MutableEncodedValue {
private val _value by lazy { private val _value by lazy {

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseBooleanEncodedValue import org.jf.dexlib2.base.value.BaseBooleanEncodedValue
import com.android.tools.smali.dexlib2.iface.value.BooleanEncodedValue import org.jf.dexlib2.iface.value.BooleanEncodedValue
class MutableBooleanEncodedValue(booleanEncodedValue: BooleanEncodedValue) : BaseBooleanEncodedValue(), class MutableBooleanEncodedValue(booleanEncodedValue: BooleanEncodedValue) : BaseBooleanEncodedValue(),
MutableEncodedValue { MutableEncodedValue {

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseByteEncodedValue import org.jf.dexlib2.base.value.BaseByteEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue import org.jf.dexlib2.iface.value.ByteEncodedValue
class MutableByteEncodedValue(byteEncodedValue: ByteEncodedValue) : BaseByteEncodedValue(), MutableEncodedValue { class MutableByteEncodedValue(byteEncodedValue: ByteEncodedValue) : BaseByteEncodedValue(), MutableEncodedValue {
private var value = byteEncodedValue.value private var value = byteEncodedValue.value

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseCharEncodedValue import org.jf.dexlib2.base.value.BaseCharEncodedValue
import com.android.tools.smali.dexlib2.iface.value.CharEncodedValue import org.jf.dexlib2.iface.value.CharEncodedValue
class MutableCharEncodedValue(charEncodedValue: CharEncodedValue) : BaseCharEncodedValue(), MutableEncodedValue { class MutableCharEncodedValue(charEncodedValue: CharEncodedValue) : BaseCharEncodedValue(), MutableEncodedValue {
private var value = charEncodedValue.value private var value = charEncodedValue.value

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseDoubleEncodedValue import org.jf.dexlib2.base.value.BaseDoubleEncodedValue
import com.android.tools.smali.dexlib2.iface.value.DoubleEncodedValue import org.jf.dexlib2.iface.value.DoubleEncodedValue
class MutableDoubleEncodedValue(doubleEncodedValue: DoubleEncodedValue) : BaseDoubleEncodedValue(), class MutableDoubleEncodedValue(doubleEncodedValue: DoubleEncodedValue) : BaseDoubleEncodedValue(),
MutableEncodedValue { MutableEncodedValue {

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.ValueType import org.jf.dexlib2.ValueType
import com.android.tools.smali.dexlib2.iface.value.* import org.jf.dexlib2.iface.value.*
interface MutableEncodedValue : EncodedValue { interface MutableEncodedValue : EncodedValue {
companion object { companion object {

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseEnumEncodedValue import org.jf.dexlib2.base.value.BaseEnumEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.FieldReference import org.jf.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.value.EnumEncodedValue import org.jf.dexlib2.iface.value.EnumEncodedValue
class MutableEnumEncodedValue(enumEncodedValue: EnumEncodedValue) : BaseEnumEncodedValue(), MutableEncodedValue { class MutableEnumEncodedValue(enumEncodedValue: EnumEncodedValue) : BaseEnumEncodedValue(), MutableEncodedValue {
private var value = enumEncodedValue.value private var value = enumEncodedValue.value

View File

@@ -1,9 +1,9 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.ValueType import org.jf.dexlib2.ValueType
import com.android.tools.smali.dexlib2.base.value.BaseFieldEncodedValue import org.jf.dexlib2.base.value.BaseFieldEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.FieldReference import org.jf.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.value.FieldEncodedValue import org.jf.dexlib2.iface.value.FieldEncodedValue
class MutableFieldEncodedValue(fieldEncodedValue: FieldEncodedValue) : BaseFieldEncodedValue(), MutableEncodedValue { class MutableFieldEncodedValue(fieldEncodedValue: FieldEncodedValue) : BaseFieldEncodedValue(), MutableEncodedValue {
private var value = fieldEncodedValue.value private var value = fieldEncodedValue.value

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseFloatEncodedValue import org.jf.dexlib2.base.value.BaseFloatEncodedValue
import com.android.tools.smali.dexlib2.iface.value.FloatEncodedValue import org.jf.dexlib2.iface.value.FloatEncodedValue
class MutableFloatEncodedValue(floatEncodedValue: FloatEncodedValue) : BaseFloatEncodedValue(), MutableEncodedValue { class MutableFloatEncodedValue(floatEncodedValue: FloatEncodedValue) : BaseFloatEncodedValue(), MutableEncodedValue {
private var value = floatEncodedValue.value private var value = floatEncodedValue.value

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseIntEncodedValue import org.jf.dexlib2.base.value.BaseIntEncodedValue
import com.android.tools.smali.dexlib2.iface.value.IntEncodedValue import org.jf.dexlib2.iface.value.IntEncodedValue
class MutableIntEncodedValue(intEncodedValue: IntEncodedValue) : BaseIntEncodedValue(), MutableEncodedValue { class MutableIntEncodedValue(intEncodedValue: IntEncodedValue) : BaseIntEncodedValue(), MutableEncodedValue {
private var value = intEncodedValue.value private var value = intEncodedValue.value

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseLongEncodedValue import org.jf.dexlib2.base.value.BaseLongEncodedValue
import com.android.tools.smali.dexlib2.iface.value.LongEncodedValue import org.jf.dexlib2.iface.value.LongEncodedValue
class MutableLongEncodedValue(longEncodedValue: LongEncodedValue) : BaseLongEncodedValue(), MutableEncodedValue { class MutableLongEncodedValue(longEncodedValue: LongEncodedValue) : BaseLongEncodedValue(), MutableEncodedValue {
private var value = longEncodedValue.value private var value = longEncodedValue.value

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseMethodEncodedValue import org.jf.dexlib2.base.value.BaseMethodEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import org.jf.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.value.MethodEncodedValue import org.jf.dexlib2.iface.value.MethodEncodedValue
class MutableMethodEncodedValue(methodEncodedValue: MethodEncodedValue) : BaseMethodEncodedValue(), class MutableMethodEncodedValue(methodEncodedValue: MethodEncodedValue) : BaseMethodEncodedValue(),
MutableEncodedValue { MutableEncodedValue {

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseMethodHandleEncodedValue import org.jf.dexlib2.base.value.BaseMethodHandleEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.MethodHandleReference import org.jf.dexlib2.iface.reference.MethodHandleReference
import com.android.tools.smali.dexlib2.iface.value.MethodHandleEncodedValue import org.jf.dexlib2.iface.value.MethodHandleEncodedValue
class MutableMethodHandleEncodedValue(methodHandleEncodedValue: MethodHandleEncodedValue) : class MutableMethodHandleEncodedValue(methodHandleEncodedValue: MethodHandleEncodedValue) :
BaseMethodHandleEncodedValue(), BaseMethodHandleEncodedValue(),

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseMethodTypeEncodedValue import org.jf.dexlib2.base.value.BaseMethodTypeEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.MethodProtoReference import org.jf.dexlib2.iface.reference.MethodProtoReference
import com.android.tools.smali.dexlib2.iface.value.MethodTypeEncodedValue import org.jf.dexlib2.iface.value.MethodTypeEncodedValue
class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedValue) : BaseMethodTypeEncodedValue(), class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedValue) : BaseMethodTypeEncodedValue(),
MutableEncodedValue { MutableEncodedValue {

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseNullEncodedValue import org.jf.dexlib2.base.value.BaseNullEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue import org.jf.dexlib2.iface.value.ByteEncodedValue
class MutableNullEncodedValue : BaseNullEncodedValue(), MutableEncodedValue { class MutableNullEncodedValue : BaseNullEncodedValue(), MutableEncodedValue {
companion object { companion object {

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseShortEncodedValue import org.jf.dexlib2.base.value.BaseShortEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ShortEncodedValue import org.jf.dexlib2.iface.value.ShortEncodedValue
class MutableShortEncodedValue(shortEncodedValue: ShortEncodedValue) : BaseShortEncodedValue(), MutableEncodedValue { class MutableShortEncodedValue(shortEncodedValue: ShortEncodedValue) : BaseShortEncodedValue(), MutableEncodedValue {
private var value = shortEncodedValue.value private var value = shortEncodedValue.value

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseStringEncodedValue import org.jf.dexlib2.base.value.BaseStringEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue import org.jf.dexlib2.iface.value.ByteEncodedValue
import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue import org.jf.dexlib2.iface.value.StringEncodedValue
class MutableStringEncodedValue(stringEncodedValue: StringEncodedValue) : BaseStringEncodedValue(), class MutableStringEncodedValue(stringEncodedValue: StringEncodedValue) : BaseStringEncodedValue(),
MutableEncodedValue { MutableEncodedValue {

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseTypeEncodedValue import org.jf.dexlib2.base.value.BaseTypeEncodedValue
import com.android.tools.smali.dexlib2.iface.value.TypeEncodedValue import org.jf.dexlib2.iface.value.TypeEncodedValue
class MutableTypeEncodedValue(typeEncodedValue: TypeEncodedValue) : BaseTypeEncodedValue(), MutableEncodedValue { class MutableTypeEncodedValue(typeEncodedValue: TypeEncodedValue) : BaseTypeEncodedValue(), MutableEncodedValue {
private var value = typeEncodedValue.value private var value = typeEncodedValue.value

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.util.smali package app.revanced.patcher.util.smali
import com.android.tools.smali.dexlib2.iface.instruction.Instruction import org.jf.dexlib2.iface.instruction.Instruction
/** /**
* A class that represents a label for an instruction. * A class that represents a label for an instruction.

View File

@@ -4,14 +4,14 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import org.antlr.runtime.CommonTokenStream import org.antlr.runtime.CommonTokenStream
import org.antlr.runtime.TokenSource import org.antlr.runtime.TokenSource
import org.antlr.runtime.tree.CommonTreeNodeStream import org.antlr.runtime.tree.CommonTreeNodeStream
import com.android.tools.smali.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcodes import org.jf.dexlib2.Opcodes
import com.android.tools.smali.dexlib2.builder.BuilderInstruction import org.jf.dexlib2.builder.BuilderInstruction
import com.android.tools.smali.dexlib2.writer.builder.DexBuilder import org.jf.dexlib2.writer.builder.DexBuilder
import com.android.tools.smali.smali.LexerErrorInterface import org.jf.smali.LexerErrorInterface
import com.android.tools.smali.smali.smaliFlexLexer import org.jf.smali.smaliFlexLexer
import com.android.tools.smali.smali.smaliParser import org.jf.smali.smaliParser
import com.android.tools.smali.smali.smaliTreeWalker import org.jf.smali.smaliTreeWalker
import java.io.InputStreamReader import java.io.InputStreamReader
private const val METHOD_TEMPLATE = """ private const val METHOD_TEMPLATE = """

View File

@@ -11,13 +11,13 @@ import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patcher.util.smali.ExternalLabel
import com.android.tools.smali.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import org.jf.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction import org.jf.dexlib2.builder.BuilderOffsetInstruction
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation import org.jf.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21s import org.jf.dexlib2.builder.instruction.BuilderInstruction21s
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import org.jf.dexlib2.immutable.ImmutableMethod
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@@ -2,44 +2,44 @@ package app.revanced.patcher.usage.bytecode
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.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.*
import app.revanced.patcher.patch.OptionsContainer
import app.revanced.patcher.patch.PatchOption
import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
import app.revanced.patcher.usage.resource.patch.ExampleResourcePatch import app.revanced.patcher.usage.resource.patch.ExampleResourcePatch
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
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Format
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction11x
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c
import com.android.tools.smali.dexlib2.immutable.ImmutableField
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableFieldReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
import com.android.tools.smali.dexlib2.immutable.value.ImmutableFieldEncodedValue
import com.android.tools.smali.dexlib2.util.Preconditions
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Format
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.builder.MutableMethodImplementation
import org.jf.dexlib2.builder.instruction.BuilderInstruction11x
import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
import org.jf.dexlib2.iface.instruction.formats.Instruction21c
import org.jf.dexlib2.immutable.ImmutableField
import org.jf.dexlib2.immutable.ImmutableMethod
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
import org.jf.dexlib2.immutable.reference.ImmutableFieldReference
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
import org.jf.dexlib2.util.Preconditions
@Patch @Patch
@Name("example-bytecode-patch") @Name("example-bytecode-patch")
@Description("Example demonstration of a bytecode patch.") @Description("Example demonstration of a bytecode patch.")
@ExampleResourceCompatibility @ExampleResourceCompatibility
@Version("0.0.1")
@DependsOn([ExampleResourcePatch::class]) @DependsOn([ExampleResourcePatch::class])
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
// 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(context: BytecodeContext) { override fun execute(context: BytecodeContext): PatchResult {
// Get the resolved method by its fingerprint from the resolver cache // Get the resolved method by its fingerprint from the resolver cache
val result = ExampleFingerprint.result!! val result = ExampleFingerprint.result!!
@@ -126,6 +126,12 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
""" """
) )
// Finally, tell the patcher that this patch was a success.
// You can also return PatchResultError with a message.
// If an exception is thrown inside this function,
// a PatchResultError will be returned with the error message.
return PatchResultSuccess()
} }
/** /**

View File

@@ -2,8 +2,8 @@ package app.revanced.patcher.usage.bytecode
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import org.jf.dexlib2.Opcode
@FuzzyPatternScanMethod(2) @FuzzyPatternScanMethod(2)
object ExampleFingerprint : MethodFingerprint( object ExampleFingerprint : MethodFingerprint(

View File

@@ -2,7 +2,10 @@ 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.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
@@ -12,8 +15,9 @@ import org.w3c.dom.Element
@Name("example-resource-patch") @Name("example-resource-patch")
@Description("Example demonstration of a resource patch.") @Description("Example demonstration of a resource patch.")
@ExampleResourceCompatibility @ExampleResourceCompatibility
@Version("0.0.1")
class ExampleResourcePatch : ResourcePatch { class ExampleResourcePatch : ResourcePatch {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext): PatchResult {
context.xmlEditor["AndroidManifest.xml"].use { editor -> context.xmlEditor["AndroidManifest.xml"].use { editor ->
val element = editor // regular DomFileEditor val element = editor // regular DomFileEditor
.file .file
@@ -25,5 +29,7 @@ class ExampleResourcePatch : ResourcePatch {
"exampleValue" "exampleValue"
) )
} }
return PatchResultSuccess()
} }
} }

View File

@@ -5,14 +5,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.newLabel import app.revanced.patcher.extensions.newLabel
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import com.android.tools.smali.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import org.jf.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.BuilderInstruction import org.jf.dexlib2.builder.BuilderInstruction
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation import org.jf.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21t import org.jf.dexlib2.builder.instruction.BuilderInstruction21t
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import org.jf.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference import org.jf.dexlib2.immutable.reference.ImmutableStringReference
import java.util.* import java.util.*
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals