chore: Lint code

This commit is contained in:
oSumAtrIX 2023-11-26 05:57:41 +01:00
parent 287841d806
commit 80407b6102
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
57 changed files with 1060 additions and 840 deletions

View File

@ -83,4 +83,4 @@ publishing {
}
}
}
}
}

View File

@ -5,4 +5,4 @@ import java.io.File
@FunctionalInterface
interface IntegrationsConsumer {
fun acceptIntegrations(integrations: List<File>)
}
}

View File

@ -37,7 +37,7 @@ sealed class PatchBundleLoader private constructor(
// This constructor parameter is unfortunately necessary,
// so that a reference to the mutable set is present in the constructor to be able to add patches to it.
// because the instance itself is a PatchSet, which is immutable, that is delegated by the parameter.
private val patchSet: MutableSet<Patch<*>> = mutableSetOf()
private val patchSet: MutableSet<Patch<*>> = mutableSetOf(),
) : PatchSet by patchSet {
private val logger = Logger.getLogger(PatchBundleLoader::class.java.name)
@ -63,22 +63,29 @@ sealed class PatchBundleLoader private constructor(
* @param silent Whether to suppress logging.
* @return The instantiated [Patch] or `null` if the [Patch] could not be instantiated.
*/
internal fun Class<*>.getInstance(logger: Logger, silent: Boolean = false): Patch<*>? {
internal fun Class<*>.getInstance(
logger: Logger,
silent: Boolean = false,
): Patch<*>? {
return try {
getField("INSTANCE").get(null)
} catch (exception: NoSuchFieldException) {
if (!silent) logger.fine(
"Patch class '${name}' has no INSTANCE field, therefor not a singleton. " +
"Will try to instantiate it."
)
if (!silent) {
logger.fine(
"Patch class '$name' has no INSTANCE field, therefor not a singleton. " +
"Will try to instantiate it.",
)
}
try {
getDeclaredConstructor().newInstance()
} catch (exception: Exception) {
if (!silent) logger.severe(
"Patch class '${name}' is not singleton and has no suitable constructor, " +
"therefor cannot be instantiated and will be ignored."
)
if (!silent) {
logger.severe(
"Patch class '$name' is not singleton and has no suitable constructor, " +
"therefor cannot be instantiated and will be ignored.",
)
}
return null
}
@ -97,7 +104,7 @@ sealed class PatchBundleLoader private constructor(
{ patchBundle ->
JarFile(patchBundle).entries().toList().filter { it.name.endsWith(".class") }
.map { it.name.replace('/', '.').replace(".class", "") }
}
},
)
/**
@ -109,9 +116,10 @@ sealed class PatchBundleLoader private constructor(
*/
class Dex(vararg patchBundles: File, optimizedDexDirectory: File? = null) : PatchBundleLoader(
DexClassLoader(
patchBundles.joinToString(File.pathSeparator) { it.absolutePath }, optimizedDexDirectory?.absolutePath,
patchBundles.joinToString(File.pathSeparator) { it.absolutePath },
optimizedDexDirectory?.absolutePath,
null,
PatchBundleLoader::class.java.classLoader
PatchBundleLoader::class.java.classLoader,
),
patchBundles,
{ patchBundle ->
@ -119,9 +127,9 @@ sealed class PatchBundleLoader private constructor(
.map { classDef ->
classDef.type.substring(1, classDef.length - 1)
}
}
},
) {
@Deprecated("This constructor is deprecated. Use the constructor with the second parameter instead.")
constructor(vararg patchBundles: File) : this(*patchBundles, optimizedDexDirectory = null)
}
}
}

View File

@ -5,4 +5,4 @@ import kotlinx.coroutines.flow.Flow
import java.util.function.Function
@FunctionalInterface
interface PatchExecutorFunction : Function<Boolean, Flow<PatchResult>>
interface PatchExecutorFunction : Function<Boolean, Flow<PatchResult>>

View File

@ -17,7 +17,7 @@ import java.util.logging.Logger
* @param options The options for the patcher.
*/
class Patcher(
private val options: PatcherOptions
private val options: PatcherOptions,
) : PatchExecutorFunction, PatchesConsumer, IntegrationsConsumer, Supplier<PatcherResult>, Closeable {
private val logger = Logger.getLogger(Patcher::class.java.name)
@ -39,11 +39,12 @@ class Patcher(
// * @param patches The [Patch]es to add.
// * @throws PatcherException.CircularDependencyException If a circular dependency is detected.
// */
/**
* Add [Patch]es to ReVanced [Patcher].
*
* @param patches The [Patch]es to add.
*/
*/
@Suppress("NAME_SHADOWING")
override fun acceptPatches(patches: List<Patch<*>>) {
/**
@ -82,7 +83,7 @@ class Patcher(
dependency.visit()
}
}
*/
*/
/**
* Returns true if at least one patch or its dependencies matches the given predicate.
@ -126,128 +127,130 @@ class Patcher(
* @param returnOnError If true, ReVanced [Patcher] will return immediately if a [Patch] fails.
* @return A pair of the name of the [Patch] and its [PatchResult].
*/
override fun apply(returnOnError: Boolean) = flow {
override fun apply(returnOnError: Boolean) =
flow {
/**
* Execute a [Patch] and its dependencies recursively.
*
* @param patch The [Patch] to execute.
* @param executedPatches A map to prevent [Patch]es from being executed twice due to dependencies.
* @return The result of executing the [Patch].
*/
fun executePatch(
patch: Patch<*>,
executedPatches: LinkedHashMap<Patch<*>, PatchResult>,
): PatchResult {
val patchName = patch.name ?: patch.toString()
/**
* Execute a [Patch] and its dependencies recursively.
*
* @param patch The [Patch] to execute.
* @param executedPatches A map to prevent [Patch]es from being executed twice due to dependencies.
* @return The result of executing the [Patch].
*/
fun executePatch(
patch: Patch<*>,
executedPatches: LinkedHashMap<Patch<*>, PatchResult>
): PatchResult {
val patchName = patch.name ?: patch.toString()
executedPatches[patch]?.let { patchResult ->
patchResult.exception ?: return patchResult
executedPatches[patch]?.let { patchResult ->
patchResult.exception ?: return patchResult
// Return a new result with an exception indicating that the patch was not executed previously,
// because it is a dependency of another patch that failed.
return PatchResult(patch, PatchException("'$patchName' did not succeed previously"))
}
// Return a new result with an exception indicating that the patch was not executed previously,
// because it is a dependency of another patch that failed.
return PatchResult(patch, PatchException("'$patchName' did not succeed previously"))
}
// Recursively execute all dependency patches.
patch.dependencies?.forEach { dependencyClass ->
val dependency = context.allPatches[dependencyClass]!!
val result = executePatch(dependency, executedPatches)
// Recursively execute all dependency patches.
patch.dependencies?.forEach { dependencyClass ->
val dependency = context.allPatches[dependencyClass]!!
val result = executePatch(dependency, executedPatches)
result.exception?.let {
return PatchResult(
patch,
PatchException(
"'$patchName' depends on '${dependency.name ?: dependency}' " +
"that raised an exception:\n${it.stackTraceToString()}"
result.exception?.let {
return PatchResult(
patch,
PatchException(
"'$patchName' depends on '${dependency.name ?: dependency}' " +
"that raised an exception:\n${it.stackTraceToString()}",
),
)
)
}
}
return try {
// TODO: Implement this in a more polymorphic way.
when (patch) {
is BytecodePatch -> {
patch.fingerprints.resolveUsingLookupMap(context.bytecodeContext)
patch.execute(context.bytecodeContext)
}
is ResourcePatch -> {
patch.execute(context.resourceContext)
}
}
PatchResult(patch)
} catch (exception: PatchException) {
PatchResult(patch, exception)
} catch (exception: Exception) {
PatchResult(patch, PatchException(exception))
}.also { executedPatches[patch] = it }
}
return try {
// TODO: Implement this in a more polymorphic way.
when (patch) {
is BytecodePatch -> {
patch.fingerprints.resolveUsingLookupMap(context.bytecodeContext)
patch.execute(context.bytecodeContext)
}
is ResourcePatch -> {
patch.execute(context.resourceContext)
}
}
if (context.bytecodeContext.integrations.merge) context.bytecodeContext.integrations.flush()
LookupMap.initializeLookupMaps(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<Patch<*>, PatchResult>() // Key is name.
context.executablePatches.values.sortedBy { it.name }.forEach { patch ->
val patchResult = executePatch(patch, executedPatches)
// If the patch failed, emit the result, even if it is closeable.
// Results of executed patches that are closeable will be emitted later.
patchResult.exception?.let {
// Propagate exception to caller instead of wrapping it in a new exception.
emit(patchResult)
if (returnOnError) return@flow
} ?: run {
if (patch is Closeable) return@run
emit(patchResult)
}
}
executedPatches.values
.filter { it.exception == null }
.filter { it.patch is Closeable }.asReversed().forEach { executedPatch ->
val patch = executedPatch.patch
val result = try {
(patch as Closeable).close()
executedPatch
PatchResult(patch)
} catch (exception: PatchException) {
PatchResult(patch, exception)
} catch (exception: Exception) {
PatchResult(patch, PatchException(exception))
}
}.also { executedPatches[patch] = it }
}
result.exception?.let {
emit(
PatchResult(
patch,
PatchException(
"'${patch.name}' raised an exception while being closed: ${it.stackTraceToString()}",
result.exception
)
)
)
if (context.bytecodeContext.integrations.merge) context.bytecodeContext.integrations.flush()
LookupMap.initializeLookupMaps(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<Patch<*>, PatchResult>() // Key is name.
context.executablePatches.values.sortedBy { it.name }.forEach { patch ->
val patchResult = executePatch(patch, executedPatches)
// If the patch failed, emit the result, even if it is closeable.
// Results of executed patches that are closeable will be emitted later.
patchResult.exception?.let {
// Propagate exception to caller instead of wrapping it in a new exception.
emit(patchResult)
if (returnOnError) return@flow
} ?: run {
patch.name ?: return@run
if (patch is Closeable) return@run
emit(result)
emit(patchResult)
}
}
}
executedPatches.values
.filter { it.exception == null }
.filter { it.patch is Closeable }.asReversed().forEach { executedPatch ->
val patch = executedPatch.patch
val result =
try {
(patch as Closeable).close()
executedPatch
} catch (exception: PatchException) {
PatchResult(patch, exception)
} catch (exception: Exception) {
PatchResult(patch, PatchException(exception))
}
result.exception?.let {
emit(
PatchResult(
patch,
PatchException(
"'${patch.name}' raised an exception while being closed: ${it.stackTraceToString()}",
result.exception,
),
),
)
if (returnOnError) return@flow
} ?: run {
patch.name ?: return@run
emit(result)
}
}
}
override fun close() = LookupMap.clearLookupMaps()
@ -256,10 +259,10 @@ class Patcher(
*
* @return The [PatcherResult] containing the patched input files.
*/
override fun get() = PatcherResult(
context.bytecodeContext.get(),
context.resourceContext.get(),
context.packageMetadata.apkInfo.doNotCompress?.toList()
)
override fun get() =
PatcherResult(
context.bytecodeContext.get(),
context.resourceContext.get(),
context.packageMetadata.apkInfo.doNotCompress?.toList(),
)
}

View File

@ -38,4 +38,4 @@ class PatcherContext internal constructor(options: PatcherOptions) {
* This holds the current state of the bytecode.
*/
internal val bytecodeContext = BytecodeContext(options)
}
}

View File

@ -9,8 +9,7 @@ package app.revanced.patcher
sealed class PatcherException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
constructor(errorMessage: String) : this(errorMessage, null)
class CircularDependencyException internal constructor(dependant: String) : PatcherException(
"Patch '$dependant' causes a circular dependency"
"Patch '$dependant' causes a circular dependency",
)
}
}

View File

@ -32,20 +32,23 @@ data class PatcherOptions(
/**
* The configuration to use for resource decoding and compiling.
*/
internal val resourceConfig = Config.getDefaultConfig().apply {
useAapt2 = true
aaptPath = aaptBinaryPath ?: ""
frameworkDirectory = frameworkFileDirectory
}
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")
internal val resourceConfig =
Config.getDefaultConfig().apply {
useAapt2 = true
aaptPath = aaptBinaryPath ?: ""
frameworkDirectory = frameworkFileDirectory
}
it.mkdirs()
}
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

@ -12,7 +12,7 @@ import java.io.InputStream
data class PatcherResult(
val dexFiles: List<PatchedDexFile>,
val resourceFile: File?,
val doNotCompress: List<String>? = null
val doNotCompress: List<String>? = null,
) {
/**
* Wrapper for dex files.
@ -20,4 +20,4 @@ data class PatcherResult(
* @param stream The dex file as [InputStream].
*/
class PatchedDexFile(val name: String, val stream: InputStream)
}
}

View File

@ -5,4 +5,4 @@ import app.revanced.patcher.patch.Patch
@FunctionalInterface
interface PatchesConsumer {
fun acceptPatches(patches: List<Patch<*>>)
}
}

View File

@ -28,141 +28,153 @@ import java.util.logging.Logger
*/
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)
/**
* Compile bytecode from the [BytecodeContext].
*
* @return The compiled bytecode.
*/
override fun get(): List<PatcherResult.PatchedDexFile> {
logger.info("Compiling patched dex files")
val patchedDexFileResults = options.resourceCachePath.resolve("dex").also {
it.deleteRecursively() // Make sure the directory is empty.
it.mkdirs()
}.apply {
MultiDexIO.writeDexFile(
true,
if (options.multithreadingDexFileWriter) -1 else 1,
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
) { _, entryName, _ -> logger.info("Compiled $entryName") }
}.listFiles(FileFilter { it.isFile })!!.map { PatcherResult.PatchedDexFile(it.name, it.inputStream()) }
System.gc()
return patchedDexFileResults
}
/**
* The integrations of a [PatcherContext].
*/
internal inner class Integrations : MutableList<File> by mutableListOf(), Flushable {
/**
* Whether to merge integrations.
* Set to true, if the field requiresIntegrations of any supplied [Patch] is true.
*/
var merge = false
private val logger = Logger.getLogger(BytecodeContext::class.java.name)
/**
* Merge integrations into the [BytecodeContext] and flush all [Integrations].
* [Opcodes] of the supplied [PatcherOptions.inputFile].
*/
override fun flush() {
if (!merge) return
internal lateinit var opcodes: Opcodes
logger.info("Merging integrations")
val classMap = classes.associateBy { it.type }
this@Integrations.forEach { integrations ->
/**
* The list of classes.
*/
val classes by lazy {
ProxyClassList(
MultiDexIO.readDexFile(
true,
integrations, BasicDexFileNamer(),
options.inputFile,
BasicDexFileNamer(),
null,
null
).classes.forEach classDef@{ classDef ->
val existingClass = classMap[classDef.type] ?: run {
logger.fine("Adding $classDef")
classes.add(classDef)
return@classDef
}
null,
).also { opcodes = it.opcodes }.classes.toMutableSet(),
)
}
logger.fine("$classDef exists. Adding missing methods and fields.")
/**
* The [Integrations] of this [PatcherContext].
*/
internal val integrations = Integrations()
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) }
/**
* 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)
/**
* Compile bytecode from the [BytecodeContext].
*
* @return The compiled bytecode.
*/
override fun get(): List<PatcherResult.PatchedDexFile> {
logger.info("Compiling patched dex files")
val patchedDexFileResults =
options.resourceCachePath.resolve("dex").also {
it.deleteRecursively() // Make sure the directory is empty.
it.mkdirs()
}.apply {
MultiDexIO.writeDexFile(
true,
if (options.multithreadingDexFileWriter) -1 else 1,
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,
) { _, entryName, _ -> logger.info("Compiled $entryName") }
}.listFiles(FileFilter { it.isFile })!!.map { PatcherResult.PatchedDexFile(it.name, it.inputStream()) }
System.gc()
return patchedDexFileResults
}
/**
* The integrations of a [PatcherContext].
*/
internal inner class Integrations : MutableList<File> by mutableListOf(), Flushable {
/**
* Whether to merge integrations.
* Set to true, if the field requiresIntegrations of any supplied [Patch] is true.
*/
var merge = false
/**
* Merge integrations into the [BytecodeContext] and flush all [Integrations].
*/
override fun flush() {
if (!merge) return
logger.info("Merging integrations")
val classMap = classes.associateBy { it.type }
this@Integrations.forEach { integrations ->
MultiDexIO.readDexFile(
true,
integrations,
BasicDexFileNamer(),
null,
null,
).classes.forEach classDef@{ classDef ->
val existingClass =
classMap[classDef.type] ?: run {
logger.fine("Adding $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()
}
clear()
}
}
}

View File

@ -6,4 +6,4 @@ import java.util.function.Supplier
* A common interface for contexts such as [ResourceContext] and [BytecodeContext].
*/
sealed interface Context<T> : Supplier<T>
sealed interface Context<T> : Supplier<T>

View File

@ -26,7 +26,7 @@ import java.util.logging.Logger
*/
class ResourceContext internal constructor(
private val context: PatcherContext,
private val options: PatcherOptions
private val options: PatcherOptions,
) : Context<File?>, Iterable<File> {
private val logger = Logger.getLogger(ResourceContext::class.java.name)
@ -37,52 +37,54 @@ class ResourceContext internal constructor(
*
* @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)
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()
when (mode) {
ResourceDecodingMode.FULL -> {
val outDir = options.recreateResourceCacheDirectory()
logger.info("Decoding resources")
logger.info("Decoding resources")
resourcesDecoder.decodeResources(outDir)
resourcesDecoder.decodeManifest(outDir)
resourcesDecoder.decodeResources(outDir)
resourcesDecoder.decodeManifest(outDir)
// Needed to record uncompressed files.
val apkDecoder = ApkDecoder(options.resourceConfig, this)
apkDecoder.recordUncompressedFiles(resourcesDecoder.resFileMapping)
// 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 */
usesFramework =
UsesFramework().apply {
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
}
}
)
}
// 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
}
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
}
/*
The ResTable if flagged as sparse if the main package is not loaded, which is the case here,
@ -92,18 +94,16 @@ class ResourceContext internal constructor(
Set this to false again to prevent the ResTable from being flagged as sparse falsely.
*/
metadata.apkInfo.sparseResources = false
metadata.apkInfo.sparseResources = false
}
}
}
}
}
operator fun get(path: String) = options.resourceCachePath.resolve(path)
override fun iterator() = options.resourceCachePath.walkTopDown().iterator()
/**
* Compile resources from the [ResourceContext].
*
@ -116,14 +116,17 @@ class ResourceContext internal constructor(
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 }
val aaptFile =
cacheDirectory.resolve("aapt_temp_file").also {
Files.deleteIfExists(it.toPath())
}.also { resourceFile = it }
try {
AaptInvoker(
options.resourceConfig, context.packageMetadata.apkInfo
).invokeAapt(aaptFile,
options.resourceConfig,
context.packageMetadata.apkInfo,
).invokeAapt(
aaptFile,
cacheDirectory.resolve("AndroidManifest.xml").also {
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(it)
},
@ -134,7 +137,8 @@ class ResourceContext internal constructor(
usesFramework.ids.map { id ->
Framework(options.resourceConfig).getFrameworkApk(id, usesFramework.tag)
}.toTypedArray()
})
},
)
} finally {
cacheDirectory.close()
}
@ -159,12 +163,10 @@ class ResourceContext internal constructor(
}
inner class XmlFileHolder {
operator fun get(inputStream: InputStream) =
DomFileEditor(inputStream)
operator fun get(inputStream: InputStream) = DomFileEditor(inputStream)
operator fun get(path: String): DomFileEditor {
return DomFileEditor(this@ResourceContext[path])
}
}
}
}

View File

@ -11,11 +11,13 @@ internal object AnnotationExtensions {
*/
fun <T : Annotation> Class<*>.findAnnotationRecursively(targetAnnotation: KClass<T>): T? {
fun <T : Annotation> Class<*>.findAnnotationRecursively(
targetAnnotation: Class<T>, traversed: MutableSet<Annotation>
targetAnnotation: Class<T>,
traversed: MutableSet<Annotation>,
): T? {
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
@Suppress("UNCHECKED_CAST") if (found != null) return found as T
@Suppress("UNCHECKED_CAST")
if (found != null) return found as T
for (annotation in this.annotations) {
if (traversed.contains(annotation)) continue
@ -30,4 +32,4 @@ internal object AnnotationExtensions {
return this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
}
}
}

View File

@ -30,4 +30,4 @@ infix fun Int.or(other: AccessFlags) = this or other.value
*
* @param other The [AccessFlags] to perform the operation with.
*/
infix fun AccessFlags.or(other: Int) = value or other
infix fun AccessFlags.or(other: Int) = value or other

View File

@ -12,7 +12,6 @@ import com.android.tools.smali.dexlib2.builder.instruction.*
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
object InstructionExtensions {
/**
* Add instructions to a method at the given index.
*
@ -21,7 +20,7 @@ object InstructionExtensions {
*/
fun MutableMethodImplementation.addInstructions(
index: Int,
instructions: List<BuilderInstruction>
instructions: List<BuilderInstruction>,
) = instructions.asReversed().forEach { addInstruction(index, it) }
/**
@ -39,7 +38,10 @@ object InstructionExtensions {
* @param index The index to remove the instructions at.
* @param count The amount of instructions to remove.
*/
fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) = repeat(count) {
fun MutableMethodImplementation.removeInstructions(
index: Int,
count: Int,
) = repeat(count) {
removeInstruction(index)
}
@ -57,7 +59,10 @@ object InstructionExtensions {
* @param index The index to replace the instructions at.
* @param instructions The instructions to replace the instructions with.
*/
fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) {
fun MutableMethodImplementation.replaceInstructions(
index: Int,
instructions: List<BuilderInstruction>,
) {
// Remove the instructions at the given index.
removeInstructions(index, instructions.size)
@ -71,16 +76,17 @@ object InstructionExtensions {
* @param index The index to add the instruction at.
* @param instruction The instruction to add.
*/
fun MutableMethod.addInstruction(index: Int, instruction: BuilderInstruction) =
implementation!!.addInstruction(index, instruction)
fun MutableMethod.addInstruction(
index: Int,
instruction: BuilderInstruction,
) = implementation!!.addInstruction(index, instruction)
/**
* Add an instruction to a method.
*
* @param instruction The instructions to add.
*/
fun MutableMethod.addInstruction(instruction: BuilderInstruction) =
implementation!!.addInstruction(instruction)
fun MutableMethod.addInstruction(instruction: BuilderInstruction) = implementation!!.addInstruction(instruction)
/**
* Add an instruction to a method at the given index.
@ -88,17 +94,17 @@ object InstructionExtensions {
* @param index The index to add the instruction at.
* @param smaliInstructions The instruction to add.
*/
fun MutableMethod.addInstruction(index: Int, smaliInstructions: String) =
implementation!!.addInstruction(index, smaliInstructions.toInstruction(this))
fun MutableMethod.addInstruction(
index: Int,
smaliInstructions: String,
) = implementation!!.addInstruction(index, smaliInstructions.toInstruction(this))
/**
* Add an instruction to a method.
*
* @param smaliInstructions The instruction to add.
*/
fun MutableMethod.addInstruction(smaliInstructions: String) =
implementation!!.addInstruction(smaliInstructions.toInstruction(this))
fun MutableMethod.addInstruction(smaliInstructions: String) = implementation!!.addInstruction(smaliInstructions.toInstruction(this))
/**
* Add instructions to a method at the given index.
@ -106,32 +112,34 @@ object InstructionExtensions {
* @param index The index to add the instructions at.
* @param instructions The instructions to add.
*/
fun MutableMethod.addInstructions(index: Int, instructions: List<BuilderInstruction>) =
implementation!!.addInstructions(index, instructions)
fun MutableMethod.addInstructions(
index: Int,
instructions: List<BuilderInstruction>,
) = implementation!!.addInstructions(index, instructions)
/**
* Add instructions to a method.
*
* @param instructions The instructions to add.
*/
fun MutableMethod.addInstructions(instructions: List<BuilderInstruction>) =
implementation!!.addInstructions(instructions)
fun MutableMethod.addInstructions(instructions: List<BuilderInstruction>) = implementation!!.addInstructions(instructions)
/**
* Add instructions to a method.
*
* @param smaliInstructions The instructions to add.
*/
fun MutableMethod.addInstructions(index: Int, smaliInstructions: String) =
implementation!!.addInstructions(index, smaliInstructions.toInstructions(this))
fun MutableMethod.addInstructions(
index: Int,
smaliInstructions: String,
) = implementation!!.addInstructions(index, smaliInstructions.toInstructions(this))
/**
* Add instructions to a method.
*
* @param smaliInstructions The instructions to add.
*/
fun MutableMethod.addInstructions(smaliInstructions: String) =
implementation!!.addInstructions(smaliInstructions.toInstructions(this))
fun MutableMethod.addInstructions(smaliInstructions: String) = implementation!!.addInstructions(smaliInstructions.toInstructions(this))
/**
* Add instructions to a method at the given index.
@ -144,14 +152,15 @@ object InstructionExtensions {
fun MutableMethod.addInstructionsWithLabels(
index: Int,
smaliInstructions: String,
vararg externalLabels: ExternalLabel
vararg externalLabels: ExternalLabel,
) {
// Create reference dummy instructions for the instructions.
val nopSmali = StringBuilder(smaliInstructions).also { builder ->
externalLabels.forEach { (name, _) ->
builder.append("\n:$name\nnop")
}
}.toString()
val nopSmali =
StringBuilder(smaliInstructions).also { builder ->
externalLabels.forEach { (name, _) ->
builder.append("\n:$name\nnop")
}
}.toString()
// Compile the instructions with the dummy labels
val compiledInstructions = nopSmali.toInstructions(this)
@ -159,7 +168,7 @@ object InstructionExtensions {
// Add the compiled list of instructions to the method.
addInstructions(
index,
compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size)
compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size),
)
implementation!!.apply {
@ -174,22 +183,24 @@ object InstructionExtensions {
*/
fun Instruction.makeNewLabel() {
fun replaceOffset(
i: BuilderOffsetInstruction, label: Label
i: BuilderOffsetInstruction,
label: Label,
): BuilderOffsetInstruction {
return when (i) {
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
is BuilderInstruction22t -> BuilderInstruction22t(
i.opcode,
i.registerA,
i.registerB,
label
)
is BuilderInstruction22t ->
BuilderInstruction22t(
i.opcode,
i.registerA,
i.registerB,
label,
)
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
else -> throw IllegalStateException(
"A non-offset instruction was given, this should never happen!"
"A non-offset instruction was given, this should never happen!",
)
}
}
@ -198,9 +209,11 @@ object InstructionExtensions {
val label = newLabelForIndex(this@apply.instructions.indexOf(this))
// Create the final instruction with the new label.
val newInstruction = replaceOffset(
compiledInstruction, label
)
val newInstruction =
replaceOffset(
compiledInstruction,
label,
)
// Replace the instruction pointing to the dummy label
// with the new instruction pointing to the real instruction.
@ -233,8 +246,7 @@ object InstructionExtensions {
*
* @param index The index to remove the instruction at.
*/
fun MutableMethod.removeInstruction(index: Int) =
implementation!!.removeInstruction(index)
fun MutableMethod.removeInstruction(index: Int) = implementation!!.removeInstruction(index)
/**
* Remove instructions at the given index.
@ -242,16 +254,17 @@ object InstructionExtensions {
* @param index The index to remove the instructions at.
* @param count The amount of instructions to remove.
*/
fun MutableMethod.removeInstructions(index: Int, count: Int) =
implementation!!.removeInstructions(index, count)
fun MutableMethod.removeInstructions(
index: Int,
count: Int,
) = implementation!!.removeInstructions(index, count)
/**
* Remove instructions at the given index.
*
* @param count The amount of instructions to remove.
*/
fun MutableMethod.removeInstructions(count: Int) =
implementation!!.removeInstructions(count)
fun MutableMethod.removeInstructions(count: Int) = implementation!!.removeInstructions(count)
/**
* Replace an instruction at the given index.
@ -259,8 +272,10 @@ object InstructionExtensions {
* @param index The index to replace the instruction at.
* @param instruction The instruction to replace the instruction with.
*/
fun MutableMethod.replaceInstruction(index: Int, instruction: BuilderInstruction) =
implementation!!.replaceInstruction(index, instruction)
fun MutableMethod.replaceInstruction(
index: Int,
instruction: BuilderInstruction,
) = implementation!!.replaceInstruction(index, instruction)
/**
* Replace an instruction at the given index.
@ -268,8 +283,10 @@ object InstructionExtensions {
* @param index The index to replace the instruction at.
* @param smaliInstruction The smali instruction to replace the instruction with.
*/
fun MutableMethod.replaceInstruction(index: Int, smaliInstruction: String) =
implementation!!.replaceInstruction(index, smaliInstruction.toInstruction(this))
fun MutableMethod.replaceInstruction(
index: Int,
smaliInstruction: String,
) = implementation!!.replaceInstruction(index, smaliInstruction.toInstruction(this))
/**
* Replace instructions at the given index.
@ -277,8 +294,10 @@ object InstructionExtensions {
* @param index The index to replace the instructions at.
* @param instructions The instructions to replace the instructions with.
*/
fun MutableMethod.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) =
implementation!!.replaceInstructions(index, instructions)
fun MutableMethod.replaceInstructions(
index: Int,
instructions: List<BuilderInstruction>,
) = implementation!!.replaceInstructions(index, instructions)
/**
* Replace instructions at the given index.
@ -286,8 +305,10 @@ object InstructionExtensions {
* @param index The index to replace the instructions at.
* @param smaliInstructions The smali instructions to replace the instructions with.
*/
fun MutableMethod.replaceInstructions(index: Int, smaliInstructions: String) =
implementation!!.replaceInstructions(index, smaliInstructions.toInstructions(this))
fun MutableMethod.replaceInstructions(
index: Int,
smaliInstructions: String,
) = implementation!!.replaceInstructions(index, smaliInstructions.toInstructions(this))
/**
* Get an instruction at the given index.
@ -327,4 +348,4 @@ object InstructionExtensions {
* @return The instructions.
*/
fun MutableMethod.getInstructions(): MutableList<BuilderInstruction> = implementation!!.instructions
}
}

View File

@ -1,8 +1,8 @@
package app.revanced.patcher.extensions
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
object MethodFingerprintExtensions {
// TODO: Make this a property.
@ -11,4 +11,4 @@ object MethodFingerprintExtensions {
*/
val MethodFingerprint.fuzzyPatternScanMethod
get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class)
}
}

View File

@ -20,7 +20,7 @@ internal class LookupMap : MutableMap<String, LookupMap.MethodClassList> by muta
*/
fun add(
key: String,
methodClassPair: MethodClassPair
methodClassPair: MethodClassPair,
) {
getOrPut(key) { MethodClassList() }.add(methodClassPair)
}
@ -73,13 +73,14 @@ internal class LookupMap : MutableMap<String, LookupMap.MethodClassList> by muta
append(accessFlagsReturnKey)
appendParameters(method.parameterTypes)
},
methodClassPair
methodClassPair,
)
// Add strings contained in the method as the key.
method.implementation?.instructions?.forEach instructions@{ instruction ->
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO)
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO) {
return@instructions
}
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
@ -120,6 +121,5 @@ internal class LookupMap : MutableMap<String, LookupMap.MethodClassList> by muta
append(parameter.first())
}
}
}
}
}

View File

@ -34,7 +34,7 @@ abstract class MethodFingerprint(
internal val parameters: Iterable<String>? = null,
internal val opcodes: Iterable<Opcode?>? = null,
internal val strings: Iterable<String>? = null,
internal val customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null
internal val customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null,
) {
/**
* The result of the [MethodFingerprint].
@ -86,11 +86,12 @@ abstract class MethodFingerprint(
}
}
val key = buildString {
append(accessFlags)
append(returnTypeValue.first())
if (parameters != null) appendParameters(parameters)
}
val key =
buildString {
append(accessFlags)
append(returnTypeValue.first())
if (parameters != null) appendParameters(parameters)
}
return methodSignatureLookupMap[key] ?: return LookupMap.MethodClassList()
}
@ -107,7 +108,11 @@ abstract class MethodFingerprint(
}
val methodsWithSameStrings = methodStringsLookup()
if (methodsWithSameStrings != null) if (resolveUsingMethodClassPair(methodsWithSameStrings)) return true
if (methodsWithSameStrings != null) {
if (resolveUsingMethodClassPair(methodsWithSameStrings)) {
return true
}
}
// No strings declared or none matched (partial matches are allowed).
// Use signature matching.
@ -121,10 +126,14 @@ abstract class MethodFingerprint(
* @param context The [BytecodeContext] to host proxies.
* @return True if the resolution was successful, false otherwise.
*/
fun resolve(context: BytecodeContext, forClass: ClassDef): Boolean {
fun resolve(
context: BytecodeContext,
forClass: ClassDef,
): Boolean {
for (method in forClass.methods)
if (resolve(context, method, forClass))
if (resolve(context, method, forClass)) {
return true
}
return false
}
@ -136,20 +145,26 @@ abstract class MethodFingerprint(
* @param context The [BytecodeContext] to host proxies.
* @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise.
*/
fun resolve(context: BytecodeContext, method: Method, forClass: ClassDef): Boolean {
fun resolve(
context: BytecodeContext,
method: Method,
forClass: ClassDef,
): Boolean {
val methodFingerprint = this
if (methodFingerprint.result != null) return true
if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType))
if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType)) {
return false
}
if (methodFingerprint.accessFlags != null && methodFingerprint.accessFlags != method.accessFlags)
if (methodFingerprint.accessFlags != null && methodFingerprint.accessFlags != method.accessFlags) {
return false
}
fun parametersEqual(
parameters1: Iterable<CharSequence>, parameters2: Iterable<CharSequence>
parameters1: Iterable<CharSequence>,
parameters2: Iterable<CharSequence>,
): Boolean {
if (parameters1.count() != parameters2.count()) return false
val iterator1 = parameters1.iterator()
@ -159,15 +174,19 @@ abstract class MethodFingerprint(
return true
}
if (methodFingerprint.parameters != null && !parametersEqual(
if (methodFingerprint.parameters != null &&
!parametersEqual(
methodFingerprint.parameters, // TODO: parseParameters()
method.parameterTypes
method.parameterTypes,
)
) return false
) {
return false
}
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method, forClass))
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method, forClass)) {
return false
}
val stringsScanResult: StringsScanResult? =
if (methodFingerprint.strings != null) {
@ -181,7 +200,9 @@ abstract class MethodFingerprint(
if (
instruction.opcode != Opcode.CONST_STRING &&
instruction.opcode != Opcode.CONST_STRING_JUMBO
) return@forEachIndexed
) {
return@forEachIndexed
}
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
val index = stringsList.indexOfFirst(string::contains)
@ -192,91 +213,98 @@ abstract class MethodFingerprint(
}
if (stringsList.isNotEmpty()) return false
}
},
)
} else null
val patternScanResult = if (methodFingerprint.opcodes != null) {
method.implementation?.instructions ?: return false
fun MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.newWarnings(
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
) = buildList {
for ((patternIndex, instructionIndex) in (this@newWarnings.startIndex until this@newWarnings.endIndex).withIndex()) {
val originalOpcode = instructions.elementAt(instructionIndex).opcode
val patternOpcode = pattern.elementAt(patternIndex)
if (patternOpcode == null || patternOpcode.ordinal == originalOpcode.ordinal) continue
this.add(
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.Warning(
originalOpcode,
patternOpcode,
instructionIndex,
patternIndex
)
)
}
} else {
null
}
fun Method.patternScan(
fingerprint: MethodFingerprint
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
val instructions = this.implementation!!.instructions
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
val patternScanResult =
if (methodFingerprint.opcodes != null) {
method.implementation?.instructions ?: return false
val pattern = fingerprint.opcodes!!
val instructionLength = instructions.count()
val patternLength = pattern.count()
for (index in 0 until instructionLength) {
var patternIndex = 0
var threshold = fingerprintFuzzyPatternScanThreshold
while (index + patternIndex < instructionLength) {
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
fun MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.newWarnings(
pattern: Iterable<Opcode?>,
instructions: Iterable<Instruction>,
) = buildList {
for ((patternIndex, instructionIndex) in (this@newWarnings.startIndex until this@newWarnings.endIndex).withIndex()) {
val originalOpcode = instructions.elementAt(instructionIndex).opcode
val patternOpcode = pattern.elementAt(patternIndex)
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
// reaching maximum threshold (0) means,
// the pattern does not match to the current instructions
if (threshold-- == 0) break
}
if (patternOpcode == null || patternOpcode.ordinal == originalOpcode.ordinal) continue
if (patternIndex < patternLength - 1) {
// if the entire pattern has not been scanned yet
// continue the scan
patternIndex++
continue
}
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
val result =
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult(
index,
index + patternIndex
)
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
result.warnings = result.newWarnings(pattern, instructions)
return result
this.add(
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.Warning(
originalOpcode,
patternOpcode,
instructionIndex,
patternIndex,
),
)
}
}
return null
fun Method.patternScan(
fingerprint: MethodFingerprint,
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
val instructions = this.implementation!!.instructions
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
val pattern = fingerprint.opcodes!!
val instructionLength = instructions.count()
val patternLength = pattern.count()
for (index in 0 until instructionLength) {
var patternIndex = 0
var threshold = fingerprintFuzzyPatternScanThreshold
while (index + patternIndex < instructionLength) {
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
val patternOpcode = pattern.elementAt(patternIndex)
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
// reaching maximum threshold (0) means,
// the pattern does not match to the current instructions
if (threshold-- == 0) break
}
if (patternIndex < patternLength - 1) {
// if the entire pattern has not been scanned yet
// continue the scan
patternIndex++
continue
}
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
val result =
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult(
index,
index + patternIndex,
)
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
result.warnings = result.newWarnings(pattern, instructions)
return result
}
}
return null
}
method.patternScan(methodFingerprint) ?: return false
} else {
null
}
method.patternScan(methodFingerprint) ?: return false
} else null
methodFingerprint.result = MethodFingerprintResult(
method,
forClass,
MethodFingerprintResult.MethodFingerprintScanResult(
patternScanResult,
stringsScanResult
),
context
)
methodFingerprint.result =
MethodFingerprintResult(
method,
forClass,
MethodFingerprintResult.MethodFingerprintScanResult(
patternScanResult,
stringsScanResult,
),
context,
)
return true
}
@ -309,12 +337,13 @@ abstract class MethodFingerprint(
* @param context The [BytecodeContext] to host proxies.
* @return True if the resolution was successful, false otherwise.
*/
fun Iterable<MethodFingerprint>.resolve(context: BytecodeContext, classes: Iterable<ClassDef>) =
forEach { fingerprint ->
for (classDef in classes) {
if (fingerprint.resolve(context, classDef)) break
}
fun Iterable<MethodFingerprint>.resolve(
context: BytecodeContext,
classes: Iterable<ClassDef>,
) = forEach { fingerprint ->
for (classDef in classes) {
if (fingerprint.resolve(context, classDef)) break
}
}
}
}

View File

@ -20,7 +20,7 @@ class MethodFingerprintResult(
val method: Method,
val classDef: ClassDef,
val scanResult: MethodFingerprintScanResult,
internal val context: BytecodeContext
internal val context: BytecodeContext,
) {
/**
* Returns a mutable clone of [classDef]
@ -50,7 +50,7 @@ class MethodFingerprintResult(
*/
class MethodFingerprintScanResult(
val patternScanResult: PatternScanResult?,
val stringsScanResult: StringsScanResult?
val stringsScanResult: StringsScanResult?,
) {
/**
* The result of scanning strings on the [MethodFingerprint].
@ -74,7 +74,7 @@ class MethodFingerprintResult(
class PatternScanResult(
val startIndex: Int,
val endIndex: Int,
var warnings: List<Warning>? = null
var warnings: List<Warning>? = null,
) {
/**
* Represents warnings of the pattern scan.
@ -91,4 +91,4 @@ class MethodFingerprintResult(
)
}
}
}
}

View File

@ -8,5 +8,5 @@ import app.revanced.patcher.fingerprint.MethodFingerprint
*/
@Target(AnnotationTarget.CLASS)
annotation class FuzzyPatternScanMethod(
val threshold: Int = 1
)
val threshold: Int = 1,
)

View File

@ -9,5 +9,5 @@ import app.revanced.patcher.fingerprint.MethodFingerprint
* @param fingerprints A list of [MethodFingerprint]s which will be resolved before the patch is executed.
*/
abstract class BytecodePatch(
internal val fingerprints : Set<MethodFingerprint> = emptySet(),
) : Patch<BytecodeContext>()
internal val fingerprints: Set<MethodFingerprint> = emptySet(),
) : Patch<BytecodeContext>()

View File

@ -47,7 +47,6 @@ sealed class Patch<out T : Context<*>> {
var use = true
private set
// TODO: Remove this property, once integrations are coupled with patches.
/**
* Weather or not the patch requires integrations.
@ -64,9 +63,10 @@ sealed class Patch<out T : Context<*>> {
this::class.findAnnotation<app.revanced.patcher.patch.annotation.Patch>()?.let { annotation ->
name = annotation.name.ifEmpty { null }
description = annotation.description.ifEmpty { null }
compatiblePackages = annotation.compatiblePackages
.map { CompatiblePackage(it.name, it.versions.toSet().ifEmpty { null }) }
.toSet().ifEmpty { null }
compatiblePackages =
annotation.compatiblePackages
.map { CompatiblePackage(it.name, it.versions.toSet().ifEmpty { null }) }
.toSet().ifEmpty { null }
dependencies = annotation.dependencies.toSet().ifEmpty { null }
use = annotation.use
requiresIntegrations = annotation.requiresIntegrations
@ -104,4 +104,4 @@ sealed class Patch<out T : Context<*>> {
val name: String,
val versions: Set<String>? = null,
)
}
}

View File

@ -9,4 +9,4 @@ package app.revanced.patcher.patch
class PatchException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
constructor(errorMessage: String) : this(errorMessage, null)
constructor(cause: Throwable) : this(cause.message, cause)
}
}

View File

@ -6,4 +6,4 @@ package app.revanced.patcher.patch
* @param patch The [Patch] that was executed.
* @param exception The [PatchException] thrown, if any.
*/
class PatchResult internal constructor(val patch: Patch<*>, val exception: PatchException? = null)
class PatchResult internal constructor(val patch: Patch<*>, val exception: PatchException? = null)

View File

@ -5,4 +5,4 @@ import app.revanced.patcher.data.ResourceContext
/**
* A ReVanced [Patch] that works on [ResourceContext].
*/
abstract class ResourcePatch : Patch<ResourceContext>()
abstract class ResourcePatch : Patch<ResourceContext>()

View File

@ -34,4 +34,4 @@ annotation class Patch(
annotation class CompatiblePackage(
val name: String,
val versions: Array<String> = [],
)
)

View File

@ -25,7 +25,7 @@ open class PatchOption<T>(
val description: String?,
val required: Boolean,
val valueType: String,
val validator: PatchOption<T>.(T?) -> Boolean
val validator: PatchOption<T>.(T?) -> Boolean,
) {
/**
* The value of the [PatchOption].
@ -45,6 +45,7 @@ open class PatchOption<T>(
uncheckedValue = value
}
/**
* Get the value of the [PatchOption].
*
@ -81,9 +82,16 @@ open class PatchOption<T>(
override fun toString() = value.toString()
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
operator fun getValue(
thisRef: Any?,
property: KProperty<*>,
) = value
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
operator fun setValue(
thisRef: Any?,
property: KProperty<*>,
value: T?,
) {
this.value = value
}
@ -111,9 +119,16 @@ open class PatchOption<T>(
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<String>.(String?) -> Boolean = { true }
validator: PatchOption<String>.(String?) -> Boolean = { true },
) = PatchOption(
key, default, values, title, description, required, "String", validator
key,
default,
values,
title,
description,
required,
"String",
validator,
).also { registerOption(it) }
/**
@ -138,9 +153,16 @@ open class PatchOption<T>(
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Int?>.(Int?) -> Boolean = { true }
validator: PatchOption<Int?>.(Int?) -> Boolean = { true },
) = PatchOption(
key, default, values, title, description, required, "Int", validator
key,
default,
values,
title,
description,
required,
"Int",
validator,
).also { registerOption(it) }
/**
@ -165,7 +187,7 @@ open class PatchOption<T>(
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Boolean?>.(Boolean?) -> Boolean = { true }
validator: PatchOption<Boolean?>.(Boolean?) -> Boolean = { true },
) = PatchOption(key, default, values, title, description, required, "Boolean", validator).also {
registerOption(it)
}
@ -192,9 +214,16 @@ open class PatchOption<T>(
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Float?>.(Float?) -> Boolean = { true }
validator: PatchOption<Float?>.(Float?) -> Boolean = { true },
) = PatchOption(
key, default, values, title, description, required, "Float", validator
key,
default,
values,
title,
description,
required,
"Float",
validator,
).also { registerOption(it) }
/**
@ -219,9 +248,16 @@ open class PatchOption<T>(
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Long?>.(Long?) -> Boolean = { true }
validator: PatchOption<Long?>.(Long?) -> Boolean = { true },
) = PatchOption(
key, default, values, title, description, required, "Long", validator
key,
default,
values,
title,
description,
required,
"Long",
validator,
).also { registerOption(it) }
/**
@ -246,9 +282,16 @@ open class PatchOption<T>(
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<String>?>.(Array<String>?) -> Boolean = { true }
validator: PatchOption<Array<String>?>.(Array<String>?) -> Boolean = { true },
) = PatchOption(
key, default, values, title, description, required, "StringArray", validator
key,
default,
values,
title,
description,
required,
"StringArray",
validator,
).also { registerOption(it) }
/**
@ -273,9 +316,16 @@ open class PatchOption<T>(
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Int>?>.(Array<Int>?) -> Boolean = { true }
validator: PatchOption<Array<Int>?>.(Array<Int>?) -> Boolean = { true },
) = PatchOption(
key, default, values, title, description, required, "IntArray", validator
key,
default,
values,
title,
description,
required,
"IntArray",
validator,
).also { registerOption(it) }
/**
@ -300,9 +350,16 @@ open class PatchOption<T>(
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Boolean>?>.(Array<Boolean>?) -> Boolean = { true }
validator: PatchOption<Array<Boolean>?>.(Array<Boolean>?) -> Boolean = { true },
) = PatchOption(
key, default, values, title, description, required, "BooleanArray", validator
key,
default,
values,
title,
description,
required,
"BooleanArray",
validator,
).also { registerOption(it) }
/**
@ -327,9 +384,16 @@ open class PatchOption<T>(
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Float>?>.(Array<Float>?) -> Boolean = { true }
validator: PatchOption<Array<Float>?>.(Array<Float>?) -> Boolean = { true },
) = PatchOption(
key, default, values, title, description, required, "FloatArray", validator
key,
default,
values,
title,
description,
required,
"FloatArray",
validator,
).also { registerOption(it) }
/**
@ -354,11 +418,18 @@ open class PatchOption<T>(
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Long>?>.(Array<Long>?) -> Boolean = { true }
validator: PatchOption<Array<Long>?>.(Array<Long>?) -> Boolean = { true },
) = PatchOption(
key, default, values, title, description, required, "LongArray", validator
key,
default,
values,
title,
description,
required,
"LongArray",
validator,
).also { registerOption(it) }
private fun <P : Patch<*>> P.registerOption(option: PatchOption<*>) = option.also { options.register(it) }
}
}
}

View File

@ -36,6 +36,6 @@ sealed class PatchOptionException(errorMessage: String) : Exception(errorMessage
*
* @param key The key of the [PatchOption].
*/
class PatchOptionNotFoundException(key: String)
: PatchOptionException("No option with key $key")
}
class PatchOptionNotFoundException(key: String) :
PatchOptionException("No option with key $key")
}

View File

@ -1,13 +1,12 @@
package app.revanced.patcher.patch.options
/**
* A map of [PatchOption]s associated by their keys.
*
* @param options The [PatchOption]s to initialize with.
*/
class PatchOptions internal constructor(
private val options: MutableMap<String, PatchOption<*>> = mutableMapOf()
private val options: MutableMap<String, PatchOption<*>> = mutableMapOf(),
) : MutableMap<String, PatchOption<*>> by options {
/**
* Register a [PatchOption]. Acts like [MutableMap.put].
@ -23,7 +22,10 @@ class PatchOptions internal constructor(
* @param value The value.
* @throws PatchOptionException.PatchOptionNotFoundException If the option does not exist.
*/
operator fun <T : Any> set(key: String, value: T?) {
operator fun <T : Any> set(
key: String,
value: T?,
) {
val option = this[key]
try {
@ -40,6 +42,5 @@ class PatchOptions internal constructor(
/**
* Get an option.
*/
override operator fun get(key: String) =
options[key] ?: throw PatchOptionException.PatchOptionNotFoundException(key)
}
override operator fun get(key: String) = options[key] ?: throw PatchOptionException.PatchOptionNotFoundException(key)
}

View File

@ -34,9 +34,12 @@ internal object ClassMerger {
* @param context The context to traverse the class hierarchy in.
* @return The merged class or the original class if no merge was needed.
*/
fun ClassDef.merge(otherClass: ClassDef, context: BytecodeContext) = this
//.fixFieldAccess(otherClass)
//.fixMethodAccess(otherClass)
fun ClassDef.merge(
otherClass: ClassDef,
context: BytecodeContext,
) = this
// .fixFieldAccess(otherClass)
// .fixMethodAccess(otherClass)
.addMissingFields(otherClass)
.addMissingMethods(otherClass)
.publicize(otherClass, context)
@ -47,13 +50,14 @@ internal object ClassMerger {
* @param fromClass The class to add missing methods from.
*/
private fun ClassDef.addMissingMethods(fromClass: ClassDef): ClassDef {
val missingMethods = fromClass.methods.let { fromMethods ->
methods.filterNot { method ->
fromMethods.any { fromMethod ->
MethodUtil.methodSignaturesMatch(fromMethod, method)
val missingMethods =
fromClass.methods.let { fromMethods ->
methods.filterNot { method ->
fromMethods.any { fromMethod ->
MethodUtil.methodSignaturesMatch(fromMethod, method)
}
}
}
}
if (missingMethods.isEmpty()) return this
@ -70,9 +74,10 @@ internal object ClassMerger {
* @param fromClass The class to add missing fields from.
*/
private fun ClassDef.addMissingFields(fromClass: ClassDef): ClassDef {
val missingFields = fields.filterNotAny(fromClass.fields) { field, fromField ->
fromField.name == field.name
}
val missingFields =
fields.filterNotAny(fromClass.fields) { field, fromField ->
fromField.name == field.name
}
if (missingFields.isEmpty()) return this
@ -88,18 +93,22 @@ internal object ClassMerger {
* @param reference The class to check the [AccessFlags] of.
* @param context The context to traverse the class hierarchy in.
*/
private fun ClassDef.publicize(reference: ClassDef, context: BytecodeContext) =
if (reference.accessFlags.isPublic() && !accessFlags.isPublic())
this.asMutableClass().apply {
context.traverseClassHierarchy(this) {
if (accessFlags.isPublic()) return@traverseClassHierarchy
private fun ClassDef.publicize(
reference: ClassDef,
context: BytecodeContext,
) = if (reference.accessFlags.isPublic() && !accessFlags.isPublic()) {
this.asMutableClass().apply {
context.traverseClassHierarchy(this) {
if (accessFlags.isPublic()) return@traverseClassHierarchy
logger.fine("Publicizing ${this.type}")
logger.fine("Publicizing ${this.type}")
accessFlags = accessFlags.toPublic()
}
accessFlags = accessFlags.toPublic()
}
else this
}
} else {
this
}
/**
* Publicize fields if they are public in [reference].
@ -107,11 +116,12 @@ internal object ClassMerger {
* @param reference The class to check the [AccessFlags] of the fields in.
*/
private fun ClassDef.fixFieldAccess(reference: ClassDef): ClassDef {
val brokenFields = fields.filterAny(reference.fields) { field, referenceField ->
if (field.name != referenceField.name) return@filterAny false
val brokenFields =
fields.filterAny(reference.fields) { field, referenceField ->
if (field.name != referenceField.name) return@filterAny false
referenceField.accessFlags.isPublic() && !field.accessFlags.isPublic()
}
referenceField.accessFlags.isPublic() && !field.accessFlags.isPublic()
}
if (brokenFields.isEmpty()) return this
@ -135,11 +145,12 @@ internal object ClassMerger {
* @param reference The class to check the [AccessFlags] of the methods in.
*/
private fun ClassDef.fixMethodAccess(reference: ClassDef): ClassDef {
val brokenMethods = methods.filterAny(reference.methods) { method, referenceMethod ->
if (!MethodUtil.methodSignaturesMatch(method, referenceMethod)) return@filterAny false
val brokenMethods =
methods.filterAny(reference.methods) { method, referenceMethod ->
if (!MethodUtil.methodSignaturesMatch(method, referenceMethod)) return@filterAny false
referenceMethod.accessFlags.isPublic() && !method.accessFlags.isPublic()
}
referenceMethod.accessFlags.isPublic() && !method.accessFlags.isPublic()
}
if (brokenMethods.isEmpty()) return this
@ -164,7 +175,10 @@ internal object ClassMerger {
* @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) {
fun BytecodeContext.traverseClassHierarchy(
targetClass: MutableClass,
callback: MutableClass.() -> Unit,
) {
callback(targetClass)
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
traverseClassHierarchy(it, callback)
@ -195,7 +209,8 @@ internal object ClassMerger {
* @return The [this] filtered on [needles] matching the given [predicate].
*/
fun <HayType, NeedleType> Iterable<HayType>.filterAny(
needles: Iterable<NeedleType>, predicate: (HayType, NeedleType) -> Boolean
needles: Iterable<NeedleType>,
predicate: (HayType, NeedleType) -> Boolean,
) = Iterable<HayType>::filter.any(this, needles, predicate)
/**
@ -206,17 +221,18 @@ internal object ClassMerger {
* @return The [this] filtered on [needles] not matching the given [predicate].
*/
fun <HayType, NeedleType> Iterable<HayType>.filterNotAny(
needles: Iterable<NeedleType>, predicate: (HayType, NeedleType) -> Boolean
needles: Iterable<NeedleType>,
predicate: (HayType, NeedleType) -> Boolean,
) = Iterable<HayType>::filterNot.any(this, needles, predicate)
fun <HayType, NeedleType> KFunction2<Iterable<HayType>, (HayType) -> Boolean, List<HayType>>.any(
haystack: Iterable<HayType>,
needles: Iterable<NeedleType>,
predicate: (HayType, NeedleType) -> Boolean
predicate: (HayType, NeedleType) -> Boolean,
) = this(haystack) { hay ->
needles.any { needle ->
predicate(hay, needle)
}
}
}
}
}

View File

@ -31,9 +31,9 @@ class DomFileEditor internal constructor(
/**
* The document of the xml file
*/
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
.also(Document::normalize)
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
@ -58,12 +58,13 @@ class DomFileEditor internal constructor(
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
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) {
@ -84,4 +85,4 @@ class DomFileEditor internal constructor(
// map of concurrent open files
val locks = mutableMapOf<String, Int>()
}
}
}

View File

@ -19,15 +19,16 @@ class ProxyClassList internal constructor(classes: MutableSet<ClassDef>) : Mutab
/**
* 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.
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)
// 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 true to remove the proxy from the proxies list.
return@removeIf true
}
}
}

View File

@ -14,7 +14,7 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
*/
class MethodWalker internal constructor(
private val bytecodeContext: BytecodeContext,
private var currentMethod: Method
private var currentMethod: Method,
) {
/**
* Get the method which was walked last.
@ -36,7 +36,10 @@ class MethodWalker internal constructor(
* @param walkMutable If this is true, the class of the method will be resolved mutably.
* @return The same [MethodWalker] instance with the method at [offset].
*/
fun nextMethod(offset: Int, walkMutable: Boolean = false): MethodWalker {
fun nextMethod(
offset: Int,
walkMutable: Boolean = false,
): MethodWalker {
currentMethod.implementation?.instructions?.let { instructions ->
val instruction = instructions.elementAt(offset)
@ -44,13 +47,14 @@ class MethodWalker internal constructor(
val proxy = bytecodeContext.findClass(newMethod.definingClass)!!
val methods = if (walkMutable) proxy.mutableClass.methods else proxy.immutableClass.methods
currentMethod = methods.first {
return@first MethodUtil.methodSignaturesMatch(it, newMethod)
}
currentMethod =
methods.first {
return@first MethodUtil.methodSignaturesMatch(it, newMethod)
}
return this
}
throw MethodNotFoundException("This method can not be walked at offset $offset inside the method ${currentMethod.name}")
}
internal class MethodNotFoundException(exception: String) : Exception(exception)
}
}

View File

@ -27,7 +27,8 @@ class ClassProxy internal constructor(
resolved = true
if (immutableClass is MutableClass) {
immutableClass
} else
} else {
MutableClass(immutableClass)
}
}
}
}

View File

@ -31,4 +31,4 @@ class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnot
return MutableAnnotationElement(this)
}
}
}
}

View File

@ -3,11 +3,11 @@ package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import com.google.common.collect.Iterables
import com.android.tools.smali.dexlib2.base.reference.BaseTypeReference
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.util.FieldUtil
import com.android.tools.smali.dexlib2.util.MethodUtil
import com.google.common.collect.Iterables
class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
// Class
@ -100,4 +100,4 @@ class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
return MutableClass(this)
}
}
}
}

View File

@ -77,4 +77,4 @@ class MutableMethod(method: Method) : Method, BaseMethodReference() {
return MutableMethod(this)
}
}
}
}

View File

@ -5,7 +5,8 @@ import com.android.tools.smali.dexlib2.base.value.BaseAnnotationEncodedValue
import com.android.tools.smali.dexlib2.iface.AnnotationElement
import com.android.tools.smali.dexlib2.iface.value.AnnotationEncodedValue
class MutableAnnotationEncodedValue(annotationEncodedValue: AnnotationEncodedValue) : BaseAnnotationEncodedValue(),
class MutableAnnotationEncodedValue(annotationEncodedValue: AnnotationEncodedValue) :
BaseAnnotationEncodedValue(),
MutableEncodedValue {
private var type = annotationEncodedValue.type

View File

@ -3,7 +3,8 @@ package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseBooleanEncodedValue
import com.android.tools.smali.dexlib2.iface.value.BooleanEncodedValue
class MutableBooleanEncodedValue(booleanEncodedValue: BooleanEncodedValue) : BaseBooleanEncodedValue(),
class MutableBooleanEncodedValue(booleanEncodedValue: BooleanEncodedValue) :
BaseBooleanEncodedValue(),
MutableEncodedValue {
private var value = booleanEncodedValue.value
@ -20,4 +21,4 @@ class MutableBooleanEncodedValue(booleanEncodedValue: BooleanEncodedValue) : Bas
return MutableBooleanEncodedValue(this)
}
}
}
}

View File

@ -19,4 +19,4 @@ class MutableByteEncodedValue(byteEncodedValue: ByteEncodedValue) : BaseByteEnco
return MutableByteEncodedValue(this)
}
}
}
}

View File

@ -3,7 +3,8 @@ package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseDoubleEncodedValue
import com.android.tools.smali.dexlib2.iface.value.DoubleEncodedValue
class MutableDoubleEncodedValue(doubleEncodedValue: DoubleEncodedValue) : BaseDoubleEncodedValue(),
class MutableDoubleEncodedValue(doubleEncodedValue: DoubleEncodedValue) :
BaseDoubleEncodedValue(),
MutableEncodedValue {
private var value = doubleEncodedValue.value
@ -20,4 +21,4 @@ class MutableDoubleEncodedValue(doubleEncodedValue: DoubleEncodedValue) : BaseDo
return MutableDoubleEncodedValue(this)
}
}
}
}

View File

@ -29,4 +29,4 @@ interface MutableEncodedValue : EncodedValue {
}
}
}
}
}

View File

@ -19,4 +19,4 @@ class MutableFloatEncodedValue(floatEncodedValue: FloatEncodedValue) : BaseFloat
return MutableFloatEncodedValue(this)
}
}
}
}

View File

@ -19,4 +19,4 @@ class MutableIntEncodedValue(intEncodedValue: IntEncodedValue) : BaseIntEncodedV
return MutableIntEncodedValue(this)
}
}
}
}

View File

@ -4,7 +4,8 @@ import com.android.tools.smali.dexlib2.base.value.BaseMethodEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.value.MethodEncodedValue
class MutableMethodEncodedValue(methodEncodedValue: MethodEncodedValue) : BaseMethodEncodedValue(),
class MutableMethodEncodedValue(methodEncodedValue: MethodEncodedValue) :
BaseMethodEncodedValue(),
MutableEncodedValue {
private var value = methodEncodedValue.value

View File

@ -22,6 +22,4 @@ class MutableMethodHandleEncodedValue(methodHandleEncodedValue: MethodHandleEnco
return MutableMethodHandleEncodedValue(this)
}
}
}
}

View File

@ -4,7 +4,8 @@ import com.android.tools.smali.dexlib2.base.value.BaseMethodTypeEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.MethodProtoReference
import com.android.tools.smali.dexlib2.iface.value.MethodTypeEncodedValue
class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedValue) : BaseMethodTypeEncodedValue(),
class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedValue) :
BaseMethodTypeEncodedValue(),
MutableEncodedValue {
private var value = methodTypeEncodedValue.value
@ -21,6 +22,4 @@ class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedVal
return MutableMethodTypeEncodedValue(this)
}
}
}
}

View File

@ -9,4 +9,4 @@ class MutableNullEncodedValue : BaseNullEncodedValue(), MutableEncodedValue {
return MutableByteEncodedValue(this)
}
}
}
}

View File

@ -4,7 +4,8 @@ import com.android.tools.smali.dexlib2.base.value.BaseStringEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue
class MutableStringEncodedValue(stringEncodedValue: StringEncodedValue) : BaseStringEncodedValue(),
class MutableStringEncodedValue(stringEncodedValue: StringEncodedValue) :
BaseStringEncodedValue(),
MutableEncodedValue {
private var value = stringEncodedValue.value
@ -21,4 +22,4 @@ class MutableStringEncodedValue(stringEncodedValue: StringEncodedValue) : BaseSt
return MutableByteEncodedValue(this)
}
}
}
}

View File

@ -1,9 +1,6 @@
package app.revanced.patcher.util.smali
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import org.antlr.runtime.CommonTokenStream
import org.antlr.runtime.TokenSource
import org.antlr.runtime.tree.CommonTreeNodeStream
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcodes
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
@ -12,6 +9,9 @@ import com.android.tools.smali.smali.LexerErrorInterface
import com.android.tools.smali.smali.smaliFlexLexer
import com.android.tools.smali.smali.smaliParser
import com.android.tools.smali.smali.smaliTreeWalker
import org.antlr.runtime.CommonTokenStream
import org.antlr.runtime.TokenSource
import org.antlr.runtime.tree.CommonTreeNodeStream
import java.io.InputStreamReader
import java.util.Locale
@ -32,15 +32,23 @@ class InlineSmaliCompiler {
* if the parameters and registers of the method are passed.
*/
fun compile(
instructions: String, parameters: String, registers: Int, forStaticMethod: Boolean
instructions: String,
parameters: String,
registers: Int,
forStaticMethod: Boolean,
): List<BuilderInstruction> {
val input = METHOD_TEMPLATE.format(Locale.ENGLISH,
if (forStaticMethod) {
"static"
} else {
""
}, parameters, registers, instructions
)
val input =
METHOD_TEMPLATE.format(
Locale.ENGLISH,
if (forStaticMethod) {
"static"
} else {
""
},
parameters,
registers,
instructions,
)
val reader = InputStreamReader(input.byteInputStream())
val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15)
val tokens = CommonTokenStream(lexer as TokenSource)
@ -48,7 +56,7 @@ class InlineSmaliCompiler {
val result = parser.smali_file()
if (parser.numberOfSyntaxErrors > 0 || lexer.numberOfSyntaxErrors > 0) {
throw IllegalStateException(
"Encountered ${parser.numberOfSyntaxErrors} parser syntax errors and ${lexer.numberOfSyntaxErrors} lexer syntax errors!"
"Encountered ${parser.numberOfSyntaxErrors} parser syntax errors and ${lexer.numberOfSyntaxErrors} lexer syntax errors!",
)
}
val treeStream = CommonTreeNodeStream(result.tree)
@ -70,10 +78,11 @@ class InlineSmaliCompiler {
* @returns A list of instructions.
*/
fun String.toInstructions(method: MutableMethod? = null): List<BuilderInstruction> {
return InlineSmaliCompiler.compile(this,
return InlineSmaliCompiler.compile(
this,
method?.parameters?.joinToString("") { it } ?: "",
method?.implementation?.registerCount ?: 1,
method?.let { AccessFlags.STATIC.isSet(it.accessFlags) } ?: true
method?.let { AccessFlags.STATIC.isSet(it.accessFlags) } ?: true,
)
}
@ -82,4 +91,4 @@ fun String.toInstructions(method: MutableMethod? = null): List<BuilderInstructio
* @param templateMethod The method to compile the instructions against.
* @return The instruction.
*/
fun String.toInstruction(templateMethod: MutableMethod? = null) = this.toInstructions(templateMethod).first()
fun String.toInstruction(templateMethod: MutableMethod? = null) = this.toInstructions(templateMethod).first()

View File

@ -27,180 +27,202 @@ private object InstructionExtensionsTest {
private lateinit var testMethodImplementation: MutableMethodImplementation
@BeforeEach
fun createTestMethod() = ImmutableMethod(
"TestClass;",
"testMethod",
null,
"V",
AccessFlags.PUBLIC.value,
null,
null,
MutableMethodImplementation(16).also { testMethodImplementation = it }.apply {
repeat(10) { i -> this.addInstruction(TestInstruction(i)) }
},
).let { testMethod = it.toMutable() }
fun createTestMethod() =
ImmutableMethod(
"TestClass;",
"testMethod",
null,
"V",
AccessFlags.PUBLIC.value,
null,
null,
MutableMethodImplementation(16).also { testMethodImplementation = it }.apply {
repeat(10) { i -> this.addInstruction(TestInstruction(i)) }
},
).let { testMethod = it.toMutable() }
@Test
fun addInstructionsToImplementationIndexed() = applyToImplementation {
addInstructions(5, getTestInstructions(5..6)).also {
assertRegisterIs(5, 5)
assertRegisterIs(6, 6)
fun addInstructionsToImplementationIndexed() =
applyToImplementation {
addInstructions(5, getTestInstructions(5..6)).also {
assertRegisterIs(5, 5)
assertRegisterIs(6, 6)
assertRegisterIs(5, 7)
assertRegisterIs(5, 7)
}
}
}
@Test
fun addInstructionsToImplementation() = applyToImplementation {
addInstructions(getTestInstructions(10..11)).also {
assertRegisterIs(10, 10)
assertRegisterIs(11, 11)
fun addInstructionsToImplementation() =
applyToImplementation {
addInstructions(getTestInstructions(10..11)).also {
assertRegisterIs(10, 10)
assertRegisterIs(11, 11)
}
}
}
@Test
fun removeInstructionsFromImplementationIndexed() = applyToImplementation {
removeInstructions(5, 5).also { assertRegisterIs(4, 4) }
}
@Test
fun removeInstructionsFromImplementation() = applyToImplementation {
removeInstructions(0).also { assertRegisterIs(9, 9) }
removeInstructions(1).also { assertRegisterIs(1, 0) }
removeInstructions(2).also { assertRegisterIs(3, 0) }
}
@Test
fun replaceInstructionsInImplementationIndexed() = applyToImplementation {
replaceInstructions(5, getTestInstructions(0..1)).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
assertRegisterIs(7, 7)
fun removeInstructionsFromImplementationIndexed() =
applyToImplementation {
removeInstructions(5, 5).also { assertRegisterIs(4, 4) }
}
}
@Test
fun addInstructionToMethodIndexed() = applyToMethod {
addInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) }
}
@Test
fun addInstructionToMethod() = applyToMethod {
addInstruction(TestInstruction(0)).also { assertRegisterIs(0, 10) }
}
@Test
fun addSmaliInstructionToMethodIndexed() = applyToMethod {
addInstruction(5, getTestSmaliInstruction(0)).also { assertRegisterIs(0, 5) }
}
@Test
fun addSmaliInstructionToMethod() = applyToMethod {
addInstruction(getTestSmaliInstruction(0)).also { assertRegisterIs(0, 10) }
}
@Test
fun addInstructionsToMethodIndexed() = applyToMethod {
addInstructions(5, getTestInstructions(0..1)).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
assertRegisterIs(5, 7)
fun removeInstructionsFromImplementation() =
applyToImplementation {
removeInstructions(0).also { assertRegisterIs(9, 9) }
removeInstructions(1).also { assertRegisterIs(1, 0) }
removeInstructions(2).also { assertRegisterIs(3, 0) }
}
}
@Test
fun addInstructionsToMethod() = applyToMethod {
addInstructions(getTestInstructions(0..1)).also {
assertRegisterIs(0, 10)
assertRegisterIs(1, 11)
assertRegisterIs(9, 9)
fun replaceInstructionsInImplementationIndexed() =
applyToImplementation {
replaceInstructions(5, getTestInstructions(0..1)).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
assertRegisterIs(7, 7)
}
}
}
@Test
fun addSmaliInstructionsToMethodIndexed() = applyToMethod {
addInstructionsWithLabels(5, getTestSmaliInstructions(0..1)).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
assertRegisterIs(5, 7)
fun addInstructionToMethodIndexed() =
applyToMethod {
addInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) }
}
}
@Test
fun addSmaliInstructionsToMethod() = applyToMethod {
addInstructions(getTestSmaliInstructions(0..1)).also {
assertRegisterIs(0, 10)
assertRegisterIs(1, 11)
assertRegisterIs(9, 9)
fun addInstructionToMethod() =
applyToMethod {
addInstruction(TestInstruction(0)).also { assertRegisterIs(0, 10) }
}
}
@Test
fun addSmaliInstructionsWithExternalLabelToMethodIndexed() = applyToMethod {
val label = ExternalLabel("testLabel", getInstruction(5))
addInstructionsWithLabels(
5,
getTestSmaliInstructions(0..1).plus("\n").plus("goto :${label.name}"),
label
).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
assertRegisterIs(5, 8)
val gotoTarget = getInstruction<BuilderOffsetInstruction>(7)
.target.location.instruction as OneRegisterInstruction
assertEquals(5, gotoTarget.registerA)
fun addSmaliInstructionToMethodIndexed() =
applyToMethod {
addInstruction(5, getTestSmaliInstruction(0)).also { assertRegisterIs(0, 5) }
}
}
@Test
fun removeInstructionFromMethodIndexed() = applyToMethod {
removeInstruction(5).also {
assertRegisterIs(4, 4)
assertRegisterIs(6, 5)
fun addSmaliInstructionToMethod() =
applyToMethod {
addInstruction(getTestSmaliInstruction(0)).also { assertRegisterIs(0, 10) }
}
}
@Test
fun removeInstructionsFromMethodIndexed() = applyToMethod {
removeInstructions(5, 5).also { assertRegisterIs(4, 4) }
}
fun addInstructionsToMethodIndexed() =
applyToMethod {
addInstructions(5, getTestInstructions(0..1)).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
@Test
fun removeInstructionsFromMethod() = applyToMethod {
removeInstructions(0).also { assertRegisterIs(9, 9) }
removeInstructions(1).also { assertRegisterIs(1, 0) }
removeInstructions(2).also { assertRegisterIs(3, 0) }
}
@Test
fun replaceInstructionInMethodIndexed() = applyToMethod {
replaceInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) }
}
@Test
fun replaceInstructionsInMethodIndexed() = applyToMethod {
replaceInstructions(5, getTestInstructions(0..1)).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
assertRegisterIs(7, 7)
assertRegisterIs(5, 7)
}
}
}
@Test
fun replaceSmaliInstructionsInMethodIndexed() = applyToMethod {
replaceInstructions(5, getTestSmaliInstructions(0..1)).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
assertRegisterIs(7, 7)
fun addInstructionsToMethod() =
applyToMethod {
addInstructions(getTestInstructions(0..1)).also {
assertRegisterIs(0, 10)
assertRegisterIs(1, 11)
assertRegisterIs(9, 9)
}
}
@Test
fun addSmaliInstructionsToMethodIndexed() =
applyToMethod {
addInstructionsWithLabels(5, getTestSmaliInstructions(0..1)).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
assertRegisterIs(5, 7)
}
}
@Test
fun addSmaliInstructionsToMethod() =
applyToMethod {
addInstructions(getTestSmaliInstructions(0..1)).also {
assertRegisterIs(0, 10)
assertRegisterIs(1, 11)
assertRegisterIs(9, 9)
}
}
@Test
fun addSmaliInstructionsWithExternalLabelToMethodIndexed() =
applyToMethod {
val label = ExternalLabel("testLabel", getInstruction(5))
addInstructionsWithLabels(
5,
getTestSmaliInstructions(0..1).plus("\n").plus("goto :${label.name}"),
label,
).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
assertRegisterIs(5, 8)
val gotoTarget =
getInstruction<BuilderOffsetInstruction>(7)
.target.location.instruction as OneRegisterInstruction
assertEquals(5, gotoTarget.registerA)
}
}
@Test
fun removeInstructionFromMethodIndexed() =
applyToMethod {
removeInstruction(5).also {
assertRegisterIs(4, 4)
assertRegisterIs(6, 5)
}
}
@Test
fun removeInstructionsFromMethodIndexed() =
applyToMethod {
removeInstructions(5, 5).also { assertRegisterIs(4, 4) }
}
@Test
fun removeInstructionsFromMethod() =
applyToMethod {
removeInstructions(0).also { assertRegisterIs(9, 9) }
removeInstructions(1).also { assertRegisterIs(1, 0) }
removeInstructions(2).also { assertRegisterIs(3, 0) }
}
@Test
fun replaceInstructionInMethodIndexed() =
applyToMethod {
replaceInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) }
}
@Test
fun replaceInstructionsInMethodIndexed() =
applyToMethod {
replaceInstructions(5, getTestInstructions(0..1)).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
assertRegisterIs(7, 7)
}
}
@Test
fun replaceSmaliInstructionsInMethodIndexed() =
applyToMethod {
replaceInstructions(5, getTestSmaliInstructions(0..1)).also {
assertRegisterIs(0, 5)
assertRegisterIs(1, 6)
assertRegisterIs(7, 7)
}
}
}
// region Helper methods
@ -212,22 +234,29 @@ private object InstructionExtensionsTest {
testMethod.apply(block)
}
private fun MutableMethodImplementation.assertRegisterIs(register: Int, atIndex: Int) = assertEquals(
register, getInstruction<OneRegisterInstruction>(atIndex).registerA
private fun MutableMethodImplementation.assertRegisterIs(
register: Int,
atIndex: Int,
) = assertEquals(
register,
getInstruction<OneRegisterInstruction>(atIndex).registerA,
)
private fun MutableMethod.assertRegisterIs(register: Int, atIndex: Int) =
implementation!!.assertRegisterIs(register, atIndex)
private fun MutableMethod.assertRegisterIs(
register: Int,
atIndex: Int,
) = implementation!!.assertRegisterIs(register, atIndex)
private fun getTestInstructions(range: IntRange) = range.map { TestInstruction(it) }
private fun getTestSmaliInstruction(register: Int) = "const/16 v$register, 0"
private fun getTestSmaliInstructions(range: IntRange) = range.joinToString("\n") {
getTestSmaliInstruction(it)
}
private fun getTestSmaliInstructions(range: IntRange) =
range.joinToString("\n") {
getTestSmaliInstruction(it)
}
// endregion
private class TestInstruction(register: Int) : BuilderInstruction21s(Opcode.CONST_16, register, 0)
}
}

View File

@ -67,8 +67,7 @@ internal class PatchOptionsTest {
}
@Test
fun `should allow setting custom value`() =
assertDoesNotThrow { OptionsTestPatch.stringOptionWithChoices = "unknown" }
fun `should allow setting custom value`() = assertDoesNotThrow { OptionsTestPatch.stringOptionWithChoices = "unknown" }
@Test
fun `should allow resetting value`() = assertDoesNotThrow { OptionsTestPatch.stringOptionWithChoices = null }
@ -86,42 +85,42 @@ internal class PatchOptionsTest {
}
@Test
fun `option types should be known`() =
assertTrue(OptionsTestPatch.options["array"].valueType == "StringArray")
fun `option types should be known`() = assertTrue(OptionsTestPatch.options["array"].valueType == "StringArray")
@Test
fun `getting default value should work`() =
assertDoesNotThrow { assertNull(OptionsTestPatch.resettableOption.default) }
fun `getting default value should work`() = assertDoesNotThrow { assertNull(OptionsTestPatch.resettableOption.default) }
private object OptionsTestPatch : BytecodePatch() {
var booleanOption by booleanPatchOption(
"bool",
true
true,
)
var requiredStringOption by stringPatchOption(
"required",
"default",
required = true
)
var stringArrayOption = stringArrayPatchOption(
"array",
arrayOf("1", "2")
required = true,
)
var stringArrayOption =
stringArrayPatchOption(
"array",
arrayOf("1", "2"),
)
var stringOptionWithChoices by stringPatchOption(
"choices",
"value",
values = mapOf("Valid option value" to "valid")
values = mapOf("Valid option value" to "valid"),
)
var validatedOption by stringPatchOption(
"validated",
"default"
"default",
) { it == "valid" }
var resettableOption = stringPatchOption(
"resettable", null,
required = true
)
var resettableOption =
stringPatchOption(
"resettable",
null,
required = true,
)
override fun execute(context: BytecodeContext) {}
}
}
}

View File

@ -32,7 +32,7 @@ import com.google.common.collect.ImmutableList
name = "Example bytecode patch",
description = "Example demonstration of a bytecode patch.",
dependencies = [ExampleResourcePatch::class],
compatiblePackages = [CompatiblePackage("com.example.examplePackage", arrayOf("0.0.1", "0.0.2"))]
compatiblePackages = [CompatiblePackage("com.example.examplePackage", arrayOf("0.0.1", "0.0.2"))],
)
object ExampleBytecodePatch : BytecodePatch(setOf(ExampleFingerprint)) {
// Entry point of a patch. Supplied fingerprints are resolved at this point.
@ -60,7 +60,7 @@ object ExampleBytecodePatch : BytecodePatch(setOf(ExampleFingerprint)) {
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
move-result-object v1
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
"""
""",
)
}
@ -82,14 +82,14 @@ object ExampleBytecodePatch : BytecodePatch(setOf(ExampleFingerprint)) {
BuilderInstruction21c(
Opcode.CONST_STRING,
0,
ImmutableStringReference("Hello, ReVanced! Adding bytecode.")
ImmutableStringReference("Hello, ReVanced! Adding bytecode."),
),
BuilderInstruction11x(Opcode.RETURN_OBJECT, 0)
BuilderInstruction11x(Opcode.RETURN_OBJECT, 0),
),
null,
null
)
).toMutable()
null,
),
).toMutable(),
)
// Add a field in the main class.
@ -105,12 +105,12 @@ object ExampleBytecodePatch : BytecodePatch(setOf(ExampleFingerprint)) {
ImmutableFieldReference(
"Ljava/lang/System;",
"out",
"Ljava/io/PrintStream;"
)
"Ljava/io/PrintStream;",
),
),
null,
null
).toMutable()
null,
).toMutable(),
)
}
} ?: throw PatchException("Fingerprint failed to resolve.")
@ -121,7 +121,10 @@ object ExampleBytecodePatch : BytecodePatch(setOf(ExampleFingerprint)) {
* @param index The index of the instruction to replace.
* @param string The replacement string.
*/
private fun MutableMethod.replaceStringAt(index: Int, string: String) {
private fun MutableMethod.replaceStringAt(
index: Int,
string: String,
) {
val instruction = getInstruction(index)
// Utility method of dexlib2.
@ -139,8 +142,7 @@ object ExampleBytecodePatch : BytecodePatch(setOf(ExampleFingerprint)) {
// At last, use the method replaceInstruction to replace it at the given index startIndex.
replaceInstruction(
index,
"const-string ${strInstruction.registerA}, ${ImmutableStringReference(string)}"
"const-string ${strInstruction.registerA}, ${ImmutableStringReference(string)}",
)
}
}

View File

@ -1,7 +1,7 @@
package app.revanced.patcher.patch.usage
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@ -12,9 +12,9 @@ object ExampleFingerprint : MethodFingerprint(
listOf("[L"),
listOf(
Opcode.SGET_OBJECT,
null, // Matching unknown opcodes.
null, // Matching unknown opcodes.
Opcode.INVOKE_STATIC, // This is intentionally wrong to test fuzzy matching.
Opcode.RETURN_VOID
Opcode.RETURN_VOID,
),
null
)
null,
)

View File

@ -4,19 +4,19 @@ import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import org.w3c.dom.Element
class ExampleResourcePatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val element = editor // regular DomFileEditor
.file
.getElementsByTagName("application")
.item(0) as Element
val element =
editor // regular DomFileEditor
.file
.getElementsByTagName("application")
.item(0) as Element
element
.setAttribute(
"exampleAttribute",
"exampleValue"
"exampleValue",
)
}
}
}
}

View File

@ -33,16 +33,18 @@ internal class InlineSmaliCompilerTest {
val insnIndex = insnAmount - 2
val targetIndex = insnIndex - 1
method.addInstructions(arrayOfNulls<String>(insnAmount).also {
Arrays.fill(it, "const/4 v0, 0x0")
}.joinToString("\n"))
method.addInstructions(
arrayOfNulls<String>(insnAmount).also {
Arrays.fill(it, "const/4 v0, 0x0")
}.joinToString("\n"),
)
method.addInstructionsWithLabels(
targetIndex,
"""
:test
const/4 v0, 0x1
if-eqz v0, :test
"""
""",
)
val insn = method.getInstruction<BuilderInstruction21t>(insnIndex)
@ -59,7 +61,7 @@ internal class InlineSmaliCompilerTest {
"""
const/4 v0, 0x1
const/4 v0, 0x0
"""
""",
)
assertEquals(labelIndex, method.newLabel(labelIndex).location.index)
@ -71,7 +73,7 @@ internal class InlineSmaliCompilerTest {
if-eqz v0, :test
return-void
""",
ExternalLabel("test", method.getInstruction(1))
ExternalLabel("test", method.getInstruction(1)),
)
val insn = method.getInstruction<BuilderInstruction21t>(insnIndex)
@ -93,13 +95,16 @@ internal class InlineSmaliCompilerTest {
accessFlags,
emptySet(),
emptySet(),
MutableMethodImplementation(registerCount)
MutableMethodImplementation(registerCount),
).toMutable()
private fun instructionEquals(want: BuilderInstruction, have: BuilderInstruction) {
private fun instructionEquals(
want: BuilderInstruction,
have: BuilderInstruction,
) {
assertEquals(want.opcode, have.opcode)
assertEquals(want.format, have.format)
assertEquals(want.codeUnits, have.codeUnits)
}
}
}
}