1
mirror of https://github.com/revanced/revanced-patcher synced 2025-09-06 16:38:50 +02:00

Compare commits

...

30 Commits

Author SHA1 Message Date
semantic-release-bot
167bd83f4e chore(release): 18.0.0-dev.6 [skip ci]
# [18.0.0-dev.6](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.5...v18.0.0-dev.6) (2023-10-22)

### Bug Fixes

* Only allow setting `MethodFingerprint#result` privately ([aed1eac](aed1eac315))

### Code Refactoring

* Change data classes to actual classes ([6192089](6192089b71))
* Convert extension functions to member functions ([e2ca507](e2ca50729d))
* Move files to simplify package structure ([124a2e9](124a2e9d3e))
* Remove deprecated classes and members ([a4212f6](a4212f6bf9))

* refactor!: Remove `Fingerprint` interface ([54a2f8f](54a2f8f16f))

### BREAKING CHANGES

* The `MethodFingerprint#result` member can now only be set inside `MethodFingerprint`.
* The `Fingerprint` interface is no longer present.
* Some extension functions are now member functions.
* This gets rid of data class members.
* Some deprecated classes and members are not present anymore.
* Classes and members have changed packages.
2023-10-22 23:54:59 +00:00
oSumAtrIX
aed1eac315 fix: Only allow setting MethodFingerprint#result privately
BREAKING CHANGE: The `MethodFingerprint#result` member can now only be set inside `MethodFingerprint`.
2023-10-23 01:52:43 +02:00
oSumAtrIX
54a2f8f16f refactor!: Remove Fingerprint interface
It served no purpose so far.

BREAKING CHANGE: The `Fingerprint` interface is no longer present.
2023-10-23 01:52:04 +02:00
oSumAtrIX
2ca543ffb9 refactor: Move lookup related classes and members to own file 2023-10-23 01:51:48 +02:00
oSumAtrIX
58e7f815a5 refactor: Move MethodFingerprintResult to own file 2023-10-23 01:51:34 +02:00
oSumAtrIX
15b38fc841 refactor: Simplify method implementation 2023-10-23 01:50:54 +02:00
oSumAtrIX
e2ca50729d refactor: Convert extension functions to member functions
BREAKING CHANGE: Some extension functions are now member functions.
2023-10-23 01:50:53 +02:00
oSumAtrIX
6192089b71 refactor: Change data classes to actual classes
The data class members did not serve any actual purpose.

BREAKING CHANGE: This gets rid of data class members.
2023-10-23 01:49:57 +02:00
oSumAtrIX
a4212f6bf9 refactor: Remove deprecated classes and members
BREAKING CHANGE: Some deprecated classes and members are not present anymore.
2023-10-23 01:49:23 +02:00
oSumAtrIX
124a2e9d3e refactor: Move files to simplify package structure
BREAKING CHANGE: Classes and members have changed packages.
2023-10-23 01:48:52 +02:00
semantic-release-bot
f77624b3b9 chore(release): 18.0.0-dev.5 [skip ci]
# [18.0.0-dev.5](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.4...v18.0.0-dev.5) (2023-10-22)

### Bug Fixes

* Do not set patch fields if they are empty ([a76ac04](a76ac04214))
2023-10-22 16:10:54 +00:00
oSumAtrIX
a76ac04214 fix: Do not set patch fields if they are empty 2023-10-22 18:08:28 +02:00
semantic-release-bot
0447fa9c28 chore(release): 18.0.0-dev.4 [skip ci]
# [18.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.3...v18.0.0-dev.4) (2023-10-22)

### Features

* Use a map for `PatchOption#values` ([54ac139](54ac1394a9))
2023-10-22 15:39:57 +00:00
oSumAtrIX
54ac1394a9 feat: Use a map for PatchOption#values
This allows to display a string representation associated to the option value.
2023-10-22 17:07:22 +02:00
oSumAtrIX
0b04c73ac5 build: Bump Kotlin Gradle plugin version 2023-10-22 16:14:46 +02:00
semantic-release-bot
079de45238 chore(release): 18.0.0-dev.3 [skip ci]
# [18.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.2...v18.0.0-dev.3) (2023-10-22)

### Features

* Make `PatchOption#values` nullable ([56ce9ec](56ce9ec2f9))
2023-10-22 14:09:28 +00:00
oSumAtrIX
56ce9ec2f9 feat: Make PatchOption#values nullable
There is no difference semantically, but this change allows passing null as a parameter which is simpler than having to use `emptySet()`.
2023-10-22 16:07:01 +02:00
semantic-release-bot
1b52e4b0f9 chore(release): 18.0.0-dev.2 [skip ci]
# [18.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.1...v18.0.0-dev.2) (2023-10-22)

### Code Refactoring

* Change `PatchOption` from abstract to open class ([09cd6aa](09cd6aa568))

### Features

* Add function to reset options to their default value ([ebbaafb](ebbaafb78e))
* Add function to reset options to their default value ([e6de90d](e6de90d300))
* Add getter for default option value ([c7922e9](c7922e90d0))
* Name patch option value validator property correctly ([caa634f](caa634fac6))

### BREAKING CHANGES

* This gets rid of the existing basic implementations of the `PatchOptions` type and moves extension functions.
* This changes the getter name of the property.
2023-10-22 00:56:40 +00:00
oSumAtrIX
098c2c1efa chore: Test default option value 2023-10-22 02:50:26 +02:00
oSumAtrIX
64343e5a7c chore: Test resetting options 2023-10-22 02:49:44 +02:00
oSumAtrIX
0caf6caeb9 refactor: Simplify patch option tests 2023-10-22 02:46:51 +02:00
oSumAtrIX
55f6c2a9fc feat!: Add property PatchOption#values 2023-10-22 02:38:12 +02:00
oSumAtrIX
c6095bc38a feat!: Add PatchOption#toString 2023-10-22 02:04:49 +02:00
oSumAtrIX
09cd6aa568 refactor: Change PatchOption from abstract to open class
BREAKING CHANGE: This gets rid of the existing basic implementations of the `PatchOptions` type and moves extension functions.
2023-10-22 01:55:05 +02:00
oSumAtrIX
ebbaafb78e feat: Add function to reset options to their default value 2023-10-22 01:14:48 +02:00
oSumAtrIX
e6de90d300 feat: Add function to reset options to their default value 2023-10-22 01:10:15 +02:00
oSumAtrIX
c7922e90d0 feat: Add getter for default option value 2023-10-22 01:02:43 +02:00
oSumAtrIX
c1f4c0445a chore: Fix inline docs wording 2023-10-21 23:52:47 +02:00
oSumAtrIX
caa634fac6 feat: Name patch option value validator property correctly
BREAKING CHANGE: This changes the getter name of the property.
2023-10-21 23:51:44 +02:00
oSumAtrIX
f28bfe0dbd chore: Add missing inline docs 2023-10-21 23:49:40 +02:00
30 changed files with 1064 additions and 1270 deletions

View File

@@ -1,3 +1,73 @@
# [18.0.0-dev.6](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.5...v18.0.0-dev.6) (2023-10-22)
### Bug Fixes
* Only allow setting `MethodFingerprint#result` privately ([aed1eac](https://github.com/ReVanced/revanced-patcher/commit/aed1eac3157317acf87f522750cf2f41509606c3))
### Code Refactoring
* Change data classes to actual classes ([6192089](https://github.com/ReVanced/revanced-patcher/commit/6192089b71bdca15765369f3e607ddd1f8266205))
* Convert extension functions to member functions ([e2ca507](https://github.com/ReVanced/revanced-patcher/commit/e2ca50729da7085799c0ff6fc4f7afaf82579738))
* Move files to simplify package structure ([124a2e9](https://github.com/ReVanced/revanced-patcher/commit/124a2e9d3efb88f0f038ae306d941e918ad3ad3c))
* Remove deprecated classes and members ([a4212f6](https://github.com/ReVanced/revanced-patcher/commit/a4212f6bf952971541c4550e20f6bf57a382e19a))
* refactor!: Remove `Fingerprint` interface ([54a2f8f](https://github.com/ReVanced/revanced-patcher/commit/54a2f8f16fddf2b2ed47eb23717ba3734c4a6c5d))
### BREAKING CHANGES
* The `MethodFingerprint#result` member can now only be set inside `MethodFingerprint`.
* The `Fingerprint` interface is no longer present.
* Some extension functions are now member functions.
* This gets rid of data class members.
* Some deprecated classes and members are not present anymore.
* Classes and members have changed packages.
# [18.0.0-dev.5](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.4...v18.0.0-dev.5) (2023-10-22)
### Bug Fixes
* Do not set patch fields if they are empty ([a76ac04](https://github.com/ReVanced/revanced-patcher/commit/a76ac04214a2ab91e3b2f9dddb13ed52816fe723))
# [18.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.3...v18.0.0-dev.4) (2023-10-22)
### Features
* Use a map for `PatchOption#values` ([54ac139](https://github.com/ReVanced/revanced-patcher/commit/54ac1394a914d3eed7865ec697e8016834134911))
# [18.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.2...v18.0.0-dev.3) (2023-10-22)
### Features
* Make `PatchOption#values` nullable ([56ce9ec](https://github.com/ReVanced/revanced-patcher/commit/56ce9ec2f98ff351c3d42df71b49e5c88f07e665))
# [18.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.1...v18.0.0-dev.2) (2023-10-22)
### Code Refactoring
* Change `PatchOption` from abstract to open class ([09cd6aa](https://github.com/ReVanced/revanced-patcher/commit/09cd6aa568988dd5241bfa6a2e12b7926a7b0683))
### Features
* Add function to reset options to their default value ([ebbaafb](https://github.com/ReVanced/revanced-patcher/commit/ebbaafb78e88f34faeafe9ff8532afe29231bd79))
* Add function to reset options to their default value ([e6de90d](https://github.com/ReVanced/revanced-patcher/commit/e6de90d300bc9c82ca1696cb898db04c65a1cd5b))
* Add getter for default option value ([c7922e9](https://github.com/ReVanced/revanced-patcher/commit/c7922e90d0c6ae83f513611c706ebea33c1a2b63))
* Name patch option value validator property correctly ([caa634f](https://github.com/ReVanced/revanced-patcher/commit/caa634fac6d7a717f54e3b015827c8858fd637b9))
### BREAKING CHANGES
* This gets rid of the existing basic implementations of the `PatchOptions` type and moves extension functions.
* This changes the getter name of the property.
# [18.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v17.0.1-dev.1...v18.0.0-dev.1) (2023-10-14)

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
plugins {
kotlin("jvm") version "1.9.0"
kotlin("jvm") version "1.9.10"
alias(libs.plugins.binary.compatibility.validator)
`maven-publish`
signing

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
version = 18.0.0-dev.1
version = 18.0.0-dev.6

View File

@@ -2,8 +2,9 @@ package app.revanced.patcher
import app.revanced.patcher.PatchBundleLoader.Utils.getInstance
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolveUsingLookupMap
import app.revanced.patcher.fingerprint.LookupMap.Maps.clearLookupMaps
import app.revanced.patcher.fingerprint.LookupMap.Maps.initializeLookupMaps
import app.revanced.patcher.fingerprint.MethodFingerprint.Companion.resolveUsingLookupMap
import app.revanced.patcher.patch.*
import kotlinx.coroutines.flow.flow
import java.io.Closeable
@@ -187,7 +188,7 @@ class Patcher(
if (context.bytecodeContext.integrations.merge) context.bytecodeContext.integrations.flush()
MethodFingerprint.initializeFingerprintResolutionLookupMaps(context.bytecodeContext)
initializeLookupMaps(context.bytecodeContext)
// Prevent from decoding the app manifest twice if it is not needed.
if (options.resourceDecodingMode == ResourceContext.ResourceDecodingMode.FULL)
@@ -249,7 +250,7 @@ class Patcher(
}
}
override fun close() = MethodFingerprint.clearFingerprintResolutionLookupMaps()
override fun close() = clearLookupMaps()
/**
* Compile and save the patched APK file.

View File

@@ -38,27 +38,6 @@ data class PatcherOptions(
frameworkDirectory = frameworkFileDirectory
}
/**
* Options for ReVanced [Patcher].
* @param inputFile The input file to patch.
* @param resourceCachePath The path to the directory to use for caching resources.
* @param aaptBinaryPath The path to a custom aapt binary.
* @param frameworkFileDirectory The path to the directory to cache the framework file in.
*/
@Deprecated("Use the constructor with the multithreadingDexFileWriter parameter instead")
constructor(
inputFile: File,
resourceCachePath: File = File("revanced-resource-cache"),
aaptBinaryPath: String? = null,
frameworkFileDirectory: String? = null,
) : this(
inputFile,
resourceCachePath,
aaptBinaryPath,
frameworkFileDirectory,
false,
)
fun recreateResourceCacheDirectory() = resourceCachePath.also {
if (it.exists()) {
logger.info("Deleting existing resource cache directory")

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher.extensions
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.MethodFingerprint
object MethodFingerprintExtensions {
// TODO: Make this a property.

View File

@@ -1,9 +0,0 @@
package app.revanced.patcher.fingerprint
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
/**
* A ReVanced fingerprint.
* Can be a [MethodFingerprint].
*/
interface Fingerprint

View File

@@ -0,0 +1,125 @@
package app.revanced.patcher.fingerprint
import app.revanced.patcher.data.BytecodeContext
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference
import java.util.*
internal typealias MethodClassPair = Pair<Method, ClassDef>
/**
* Lookup map for methods.
*/
internal class LookupMap : MutableMap<String, LookupMap.MethodClassList> by mutableMapOf() {
/**
* Adds a [MethodClassPair] to the list associated with the given key.
* If the key does not exist, a new list is created and the [MethodClassPair] is added to it.
*/
fun add(
key: String,
methodClassPair: MethodClassPair
) {
getOrPut(key) { MethodClassList() }.add(methodClassPair)
}
/**
* List of methods and the class they are a member of.
*/
internal class MethodClassList : LinkedList<MethodClassPair>()
companion object Maps {
/**
* A list of methods and the class they are a member of.
*/
internal val methods = MethodClassList()
/**
* Lookup map for methods keyed to the methods access flags, return type and parameter.
*/
internal val methodSignatureLookupMap = LookupMap()
/**
* Lookup map for methods associated by strings referenced in the method.
*/
internal val methodStringsLookupMap = LookupMap()
/**
* Initializes lookup maps for [MethodFingerprint] resolution
* using attributes of methods such as the method signature or strings.
*
* @param context The [BytecodeContext] containing the classes to initialize the lookup maps with.
*/
internal fun initializeLookupMaps(context: BytecodeContext) {
if (methods.isNotEmpty()) clearLookupMaps()
context.classes.forEach { classDef ->
classDef.methods.forEach { method ->
val methodClassPair = method to classDef
// For fingerprints with no access or return type specified.
methods += methodClassPair
val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
// Add <access><returnType> as the key.
methodSignatureLookupMap.add(accessFlagsReturnKey, methodClassPair)
// Add <access><returnType>[parameters] as the key.
methodSignatureLookupMap.add(
buildString {
append(accessFlagsReturnKey)
appendParameters(method.parameterTypes)
},
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)
return@instructions
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
methodStringsLookupMap.add(string, methodClassPair)
}
// In the future, the class type could be added to the lookup map.
// This would require MethodFingerprint to be changed to include the class type.
}
}
}
/**
* Clears the internal lookup maps created in [initializeLookupMaps]
*/
internal fun clearLookupMaps() {
methods.clear()
methodSignatureLookupMap.clear()
methodStringsLookupMap.clear()
}
/**
* Appends a string based on the parameter reference types of this method.
*/
internal fun StringBuilder.appendParameters(parameters: Iterable<CharSequence>) {
// Maximum parameters to use in the signature key.
// Some apps have methods with an incredible number of parameters (over 100 parameters have been seen).
// To keep the signature map from becoming needlessly bloated,
// group together in the same map entry all methods with the same access/return and 5 or more parameters.
// The value of 5 was chosen based on local performance testing and is not set in stone.
val maxSignatureParameters = 5
// Must append a unique value before the parameters to distinguish this key includes the parameters.
// If this is not appended, then methods with no parameters
// will collide with different keys that specify access/return but omit the parameters.
append("p:")
parameters.forEachIndexed { index, parameter ->
if (index >= maxSignatureParameters) return
append(parameter.first())
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,94 @@
package app.revanced.patcher.fingerprint
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.util.proxy.ClassProxy
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.util.MethodUtil
/**
* Represents the result of a [MethodFingerprintResult].
*
* @param method The matching method.
* @param classDef The [ClassDef] that contains the matching [method].
* @param scanResult The result of scanning for the [MethodFingerprint].
* @param context The [BytecodeContext] this [MethodFingerprintResult] is attached to, to create proxies.
*/
@Suppress("MemberVisibilityCanBePrivate")
class MethodFingerprintResult(
val method: Method,
val classDef: ClassDef,
val scanResult: MethodFingerprintScanResult,
internal val context: BytecodeContext
) {
/**
* Returns a mutable clone of [classDef]
*
* Please note, this method allocates a [ClassProxy].
* Use [classDef] where possible.
*/
@Suppress("MemberVisibilityCanBePrivate")
val mutableClass by lazy { context.proxy(classDef).mutableClass }
/**
* Returns a mutable clone of [method]
*
* Please note, this method allocates a [ClassProxy].
* Use [method] where possible.
*/
val mutableMethod by lazy {
mutableClass.methods.first {
MethodUtil.methodSignaturesMatch(it, this.method)
}
}
/**
* The result of scanning on the [MethodFingerprint].
* @param patternScanResult The result of the pattern scan.
* @param stringsScanResult The result of the string scan.
*/
class MethodFingerprintScanResult(
val patternScanResult: PatternScanResult?,
val stringsScanResult: StringsScanResult?
) {
/**
* The result of scanning strings on the [MethodFingerprint].
* @param matches The list of strings that were matched.
*/
class StringsScanResult(val matches: List<StringMatch>) {
/**
* Represents a match for a string at an index.
* @param string The string that was matched.
* @param index The index of the string.
*/
class StringMatch(val string: String, val index: Int)
}
/**
* The result of a pattern scan.
* @param startIndex The start index of the instructions where to which this pattern matches.
* @param endIndex The end index of the instructions where to which this pattern matches.
* @param warnings A list of warnings considering this [PatternScanResult].
*/
class PatternScanResult(
val startIndex: Int,
val endIndex: Int,
var warnings: List<Warning>? = null
) {
/**
* Represents warnings of the pattern scan.
* @param correctOpcode The opcode the instruction list has.
* @param wrongOpcode The opcode the pattern list of the signature currently has.
* @param instructionIndex The index of the opcode relative to the instruction list.
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
*/
class Warning(
val correctOpcode: Opcode,
val wrongOpcode: Opcode,
val instructionIndex: Int,
val patternIndex: Int,
)
}
}
}

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.fingerprint.method.annotation
package app.revanced.patcher.fingerprint.annotation
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.MethodFingerprint
/**
* Annotations to scan a pattern [MethodFingerprint] with fuzzy algorithm.

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.patch
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.MethodFingerprint
/**
* A ReVanced [Patch] that works on [BytecodeContext].

View File

@@ -65,7 +65,8 @@ sealed class Patch<out T : Context<*>> {
name = annotation.name.ifEmpty { null }
description = annotation.description.ifEmpty { null }
compatiblePackages = annotation.compatiblePackages
.map { CompatiblePackage(it.name, it.versions.toSet()) }.toSet()
.map { CompatiblePackage(it.name, it.versions.toSet().ifEmpty { null }) }
.toSet().ifEmpty { null }
dependencies = annotation.dependencies.toSet().ifEmpty { null }
use = annotation.use
requiresIntegrations = annotation.requiresIntegrations

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.patch.options.types
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
/**
* A [PatchOption] representing a [Boolean].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
*
* @see PatchOption
*/
class BooleanPatchOption private constructor(
key: String,
default: Boolean?,
title: String?,
description: String?,
required: Boolean,
validator: (Boolean?) -> Boolean
) : PatchOption<Boolean>(key, default, title, description, required, validator) {
companion object {
/**
* Create a new [BooleanPatchOption] and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @return The created [BooleanPatchOption].
*
* @see BooleanPatchOption
* @see PatchOption
*/
fun <T : Patch<*>> T.booleanPatchOption(
key: String,
default: Boolean? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: (Boolean?) -> Boolean = { true }
) = BooleanPatchOption(key, default, title, description, required, validator).also { options.register(it) }
}
}

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.patch.options.types
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
/**
* A [PatchOption] representing a [Float].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
*
* @see PatchOption
*/
class FloatPatchOption private constructor(
key: String,
default: Float?,
title: String?,
description: String?,
required: Boolean,
validator: (Float?) -> Boolean
) : PatchOption<Float>(key, default, title, description, required, validator) {
companion object {
/**
* Create a new [FloatPatchOption] and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @return The created [FloatPatchOption].
*
* @see FloatPatchOption
* @see PatchOption
*/
fun <T : Patch<*>> T.floatPatchOption(
key: String,
default: Float? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: (Float?) -> Boolean = { true }
) = FloatPatchOption(key, default, title, description, required, validator).also { options.register(it) }
}
}

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.patch.options.types
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
/**
* A [PatchOption] representing an [Integer].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
*
* @see PatchOption
*/
class IntPatchOption private constructor(
key: String,
default: Int?,
title: String?,
description: String?,
required: Boolean,
validator: (Int?) -> Boolean
) : PatchOption<Int>(key, default, title, description, required, validator) {
companion object {
/**
* Create a new [IntPatchOption] and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @return The created [IntPatchOption].
*
* @see IntPatchOption
* @see PatchOption
*/
fun <T : Patch<*>> T.intPatchOption(
key: String,
default: Int? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: (Int?) -> Boolean = { true }
) = IntPatchOption(key, default, title, description, required, validator).also { options.register(it) }
}
}

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.patch.options.types
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
/**
* A [PatchOption] representing a [Long].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
*
* @see PatchOption
*/
class LongPatchOption private constructor(
key: String,
default: Long?,
title: String?,
description: String?,
required: Boolean,
validator: (Long?) -> Boolean
) : PatchOption<Long>(key, default, title, description, required, validator) {
companion object {
/**
* Create a new [LongPatchOption] and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @return The created [LongPatchOption].
*
* @see LongPatchOption
* @see PatchOption
*/
fun <T : Patch<*>> T.longPatchOption(
key: String,
default: Long? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: (Long?) -> Boolean = { true }
) = LongPatchOption(key, default, title, description, required, validator).also { options.register(it) }
}
}

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.patch.options.types
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
/**
* A [PatchOption] representing a [String].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
*
* @see PatchOption
*/
class StringPatchOption private constructor(
key: String,
default: String?,
title: String?,
description: String?,
required: Boolean,
validator: (String?) -> Boolean
) : PatchOption<String>(key, default, title, description, required, validator) {
companion object {
/**
* Create a new [StringPatchOption] and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @return The created [StringPatchOption].
*
* @see StringPatchOption
* @see PatchOption
*/
fun <T : Patch<*>> T.stringPatchOption(
key: String,
default: String? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: (String?) -> Boolean = { true }
) = StringPatchOption(key, default, title, description, required, validator).also { options.register(it) }
}
}

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.patch.options.types.array
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
/**
* A [PatchOption] representing a [Boolean] array.
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
*
* @see PatchOption
*/
class BooleanArrayPatchOption private constructor(
key: String,
default: Array<Boolean>?,
title: String?,
description: String?,
required: Boolean,
validator: (Array<Boolean>?) -> Boolean
) : PatchOption<Array<Boolean>>(key, default, title, description, required, validator) {
companion object {
/**
* Create a new [BooleanArrayPatchOption] and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @return The created [BooleanArrayPatchOption].
*
* @see BooleanArrayPatchOption
* @see PatchOption
*/
fun <T : Patch<*>> T.booleanArrayPatchOption(
key: String,
default: Array<Boolean>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: (Array<Boolean>?) -> Boolean = { true }
) = BooleanArrayPatchOption(key, default, title, description, required, validator).also { options.register(it) }
}
}

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.patch.options.types.array
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
/**
* A [PatchOption] representing a [Float] array.
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
*
* @see PatchOption
*/
class FloatArrayPatchOption private constructor(
key: String,
default: Array<Float>?,
title: String?,
description: String?,
required: Boolean,
validator: (Array<Float>?) -> Boolean
) : PatchOption<Array<Float>>(key, default, title, description, required, validator) {
companion object {
/**
* Create a new [FloatArrayPatchOption] and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @return The created [FloatArrayPatchOption].
*
* @see FloatArrayPatchOption
* @see PatchOption
*/
fun <T : Patch<*>> T.floatArrayPatchOption(
key: String,
default: Array<Float>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: (Array<Float>?) -> Boolean = { true }
) = FloatArrayPatchOption(key, default, title, description, required, validator).also { options.register(it) }
}
}

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.patch.options.types.array
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
/**
* A [PatchOption] representing an [Integer] array.
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
*
* @see PatchOption
*/
class IntArrayPatchOption private constructor(
key: String,
default: Array<Int>?,
title: String?,
description: String?,
required: Boolean,
validator: (Array<Int>?) -> Boolean
) : PatchOption<Array<Int>>(key, default, title, description, required, validator) {
companion object {
/**
* Create a new [IntArrayPatchOption] and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @return The created [IntArrayPatchOption].
*
* @see IntArrayPatchOption
* @see PatchOption
*/
fun <T : Patch<*>> T.intArrayPatchOption(
key: String,
default: Array<Int>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: (Array<Int>?) -> Boolean = { true }
) = IntArrayPatchOption(key, default, title, description, required, validator).also { options.register(it) }
}
}

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.patch.options.types.array
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
/**
* A [PatchOption] representing a [Long] array.
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
*
* @see PatchOption
*/
class LongArrayPatchOption private constructor(
key: String,
default: Array<Long>?,
title: String?,
description: String?,
required: Boolean,
validator: (Array<Long>?) -> Boolean
) : PatchOption<Array<Long>>(key, default, title, description, required, validator) {
companion object {
/**
* Create a new [LongArrayPatchOption] and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @return The created [LongArrayPatchOption].
*
* @see LongArrayPatchOption
* @see PatchOption
*/
fun <T : Patch<*>> T.longArrayPatchOption(
key: String,
default: Array<Long>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: (Array<Long>?) -> Boolean = { true }
) = LongArrayPatchOption(key, default, title, description, required, validator).also { options.register(it) }
}
}

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.patch.options.types.array
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
/**
* A [PatchOption] representing a [String] array.
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
*
* @see PatchOption
*/
class StringArrayPatchOption private constructor(
key: String,
default: Array<String>?,
title: String?,
description: String?,
required: Boolean,
validator: (Array<String>?) -> Boolean
) : PatchOption<Array<String>>(key, default, title, description, required, validator) {
companion object {
/**
* Create a new [StringArrayPatchOption] and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @return The created [StringArrayPatchOption].
*
* @see StringArrayPatchOption
* @see PatchOption
*/
fun <T : Patch<*>> T.stringArrayPatchOption(
key: String,
default: Array<String>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: (Array<String>?) -> Boolean = { true }
) = StringArrayPatchOption(key, default, title, description, required, validator).also { options.register(it) }
}
}

View File

@@ -2,31 +2,43 @@ package app.revanced.patcher.patch.options
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.options.types.BooleanPatchOption.Companion.booleanPatchOption
import app.revanced.patcher.patch.options.types.StringPatchOption.Companion.stringPatchOption
import app.revanced.patcher.patch.options.types.array.StringArrayPatchOption.Companion.stringArrayPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringArrayPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import kotlin.test.Test
import kotlin.test.assertNull
import kotlin.test.assertTrue
internal class PatchOptionsTest {
@Test
fun `should not fail because default value is unvalidated`() {
assertDoesNotThrow {
OptionsTestPatch.options["required"].value
}
assertDoesNotThrow { OptionsTestPatch.requiredStringOption }
}
@Test
fun `should not allow setting custom value with validation`() {
// Getter validation on incorrect value.
assertThrows<PatchOptionException.ValueValidationException> { OptionsTestPatch.validatedOption }
// Setter validation on incorrect value.
assertThrows<PatchOptionException.ValueValidationException> { OptionsTestPatch.validatedOption = "invalid" }
// Setter validation on correct value.
assertDoesNotThrow { OptionsTestPatch.validatedOption = "valid" }
}
@Test
fun `should throw due to incorrect type`() {
assertThrows<PatchOptionException.InvalidValueTypeException> {
OptionsTestPatch.options["bool"] = 0
OptionsTestPatch.options["bool"] = "not a boolean"
}
}
@Test
fun `should be nullable`() {
OptionsTestPatch.options["bool"] = null
OptionsTestPatch.booleanOption = null
}
@Test
@@ -46,13 +58,65 @@ internal class PatchOptionsTest {
}
}
private object OptionsTestPatch : BytecodePatch() {
private var stringOption by stringPatchOption("string", "default")
private var booleanOption by booleanPatchOption("bool", true)
private var requiredStringOption by stringPatchOption("required", "default", required = true)
private var nullDefaultRequiredOption by stringPatchOption("null", null, required = true)
@Suppress("UNCHECKED_CAST")
@Test
fun `should allow setting value from values`() =
with(OptionsTestPatch.options["choices"] as PatchOption<String>) {
value = values!!.values.last()
assertTrue(value == "valid")
}
val stringArrayOption = stringArrayPatchOption("array", arrayOf("1", "2"))
@Test
fun `should allow setting custom value`() =
assertDoesNotThrow { OptionsTestPatch.stringOptionWithChoices = "unknown" }
@Test
fun `should allow resetting value`() = assertDoesNotThrow { OptionsTestPatch.stringOptionWithChoices = null }
@Test
fun `reset should not fail`() {
assertDoesNotThrow {
OptionsTestPatch.resettableOption.value = "test"
OptionsTestPatch.resettableOption.reset()
}
assertThrows<PatchOptionException.ValueRequiredException> {
OptionsTestPatch.resettableOption.value
}
}
@Test
fun `getting default value should work`() =
assertDoesNotThrow { assertNull(OptionsTestPatch.resettableOption.default) }
private object OptionsTestPatch : BytecodePatch() {
var booleanOption by booleanPatchOption(
"bool",
true
)
var requiredStringOption by stringPatchOption(
"required",
"default",
required = true
)
var stringArrayOption = stringArrayPatchOption(
"array",
arrayOf("1", "2")
)
var stringOptionWithChoices by stringPatchOption(
"choices",
"value",
values = mapOf("Valid option value" to "valid")
)
var validatedOption by stringPatchOption(
"validated",
"default"
) { it == "valid" }
var resettableOption = stringPatchOption(
"resettable", null,
required = true
)
override fun execute(context: BytecodeContext) {}
}

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.patch.usage
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode