Continuing the legacy of Vanced
@@ -54,7 +73,7 @@ This document describes how to contribute to ReVanced Library.
Features can be requested by opening an issue using the
[Feature request issue template](https://github.com/ReVanced/revanced-cli/issues/new?assignees=&labels=Feature+request&projects=&template=feature-request.yml&title=feat%3A+).
-> **Note**
+> [!NOTE]
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Library.
> Good motivation has to be provided for a request to be accepted.
@@ -76,4 +95,4 @@ If you encounter a bug while using ReVanced Library, open an issue using the
it will be merged into the `dev` branch and will be included in the next release of ReVanced Library
❤️ Thank you for considering contributing to ReVanced Library,
-ReVanced
\ No newline at end of file
+ReVanced
diff --git a/api/revanced-library.api b/api/revanced-library.api
index e020e1e..ba73750 100644
--- a/api/revanced-library.api
+++ b/api/revanced-library.api
@@ -67,6 +67,47 @@ public final class app/revanced/library/PatchUtils {
public static synthetic fun getMostCommonCompatibleVersions$default (Lapp/revanced/library/PatchUtils;Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
}
+public final class app/revanced/library/PatchUtils$Json {
+ public static final field INSTANCE Lapp/revanced/library/PatchUtils$Json;
+ public final fun deserialize (Ljava/io/InputStream;Ljava/lang/Class;)Ljava/util/Set;
+ public final fun serialize (Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;)V
+ public static synthetic fun serialize$default (Lapp/revanced/library/PatchUtils$Json;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;ILjava/lang/Object;)V
+}
+
+public final class app/revanced/library/PatchUtils$Json$FullJsonPatch : app/revanced/library/PatchUtils$Json$JsonPatch {
+ public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$Companion;
+ public final fun getCompatiblePackages ()Ljava/util/Set;
+ public final fun getDependencies ()Ljava/util/Set;
+ public final fun getDescription ()Ljava/lang/String;
+ public final fun getName ()Ljava/lang/String;
+ public final fun getOptions ()Ljava/util/Map;
+ public final fun getRequiresIntegrations ()Z
+ public final fun getUse ()Z
+ public final fun setRequiresIntegrations (Z)V
+}
+
+public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$Companion {
+ public final fun fromPatch (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch;
+}
+
+public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption {
+ public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion;
+ public final fun getDefault ()Ljava/lang/Object;
+ public final fun getDescription ()Ljava/lang/String;
+ public final fun getKey ()Ljava/lang/String;
+ public final fun getRequired ()Z
+ public final fun getTitle ()Ljava/lang/String;
+ public final fun getValueType ()Ljava/lang/String;
+ public final fun getValues ()Ljava/util/Map;
+}
+
+public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion {
+ public final fun fromPatchOption (Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption;
+}
+
+public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch {
+}
+
public abstract class app/revanced/library/adb/AdbManager {
public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion;
public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
diff --git a/gradle.properties b/gradle.properties
index 70d1281..67fc117 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
-version = 1.4.0
+version = 1.5.0-dev.2
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8d21415..a846306 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -3,9 +3,9 @@ apksig = "8.1.4"
bcpkix-jdk18on = "1.76"
jackson-module-kotlin = "2.14.3"
jadb = "1.2.1"
-kotlin-reflect = "1.9.10"
+kotlin-reflect = "1.9.20"
kotlin-test = "1.9.20"
-revanced-patcher = "19.0.0"
+revanced-patcher = "19.1.0"
binary-compatibility-validator = "0.13.2"
[libraries]
diff --git a/src/main/kotlin/app/revanced/library/Options.kt b/src/main/kotlin/app/revanced/library/Options.kt
index 75b1f0e..2233d9e 100644
--- a/src/main/kotlin/app/revanced/library/Options.kt
+++ b/src/main/kotlin/app/revanced/library/Options.kt
@@ -3,24 +3,22 @@
package app.revanced.library
import app.revanced.library.Options.Patch.Option
-import app.revanced.patcher.PatchClass
import app.revanced.patcher.PatchSet
import app.revanced.patcher.patch.options.PatchOptionException
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import java.io.File
import java.util.logging.Logger
-private typealias PatchList = List
-
+@Suppress("unused")
object Options {
private val logger = Logger.getLogger(Options::class.java.name)
- private var mapper = jacksonObjectMapper()
+ private val mapper = jacksonObjectMapper()
/**
- * Serializes the options for the patches in the list.
+ * Serializes the options for a set of patches.
*
- * @param patches The list of patches to serialize.
+ * @param patches The set of patches to serialize.
* @param prettyPrint Whether to pretty print the JSON.
* @return The JSON string containing the options.
*/
@@ -57,17 +55,16 @@ object Options {
}
/**
- * Deserializes the options for the patches in the list.
+ * Deserializes the options to a set of patches.
*
* @param json The JSON string containing the options.
- * @return The list of [Patch]s.
+ * @return A set of [Patch]s.
* @see Patch
- * @see PatchList
*/
fun deserialize(json: String): Array = mapper.readValue(json, Array::class.java)
/**
- * Sets the options for the patches in the list.
+ * Sets the options for a set of patches.
*
* @param json The JSON string containing the options.
*/
@@ -95,7 +92,7 @@ object Options {
}
/**
- * Sets the options for the patches in the list.
+ * Sets the options for a set of patches.
*
* @param file The file containing the JSON string containing the options.
* @see setOptions
diff --git a/src/main/kotlin/app/revanced/library/PatchUtils.kt b/src/main/kotlin/app/revanced/library/PatchUtils.kt
index 1cc700b..1a6fa1d 100644
--- a/src/main/kotlin/app/revanced/library/PatchUtils.kt
+++ b/src/main/kotlin/app/revanced/library/PatchUtils.kt
@@ -1,7 +1,12 @@
package app.revanced.library
+import app.revanced.patcher.PatchClass
import app.revanced.patcher.PatchSet
import app.revanced.patcher.patch.Patch
+import app.revanced.patcher.patch.options.PatchOption
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import java.io.InputStream
+import java.io.OutputStream
typealias PackageName = String
typealias Version = String
@@ -90,4 +95,104 @@ object PatchUtils {
.associate { it.key to it.value } as VersionMap
}
}
+
+ object Json {
+ private val mapper = jacksonObjectMapper()
+
+ /**
+ * Serializes a set of [Patch]es to a JSON string and writes it to an output stream.
+ *
+ * @param patches The set of [Patch]es to serialize.
+ * @param transform A function to transform the [Patch]es to [JsonPatch]es.
+ * @param prettyPrint Whether to pretty print the JSON.
+ * @param outputStream The output stream to write the JSON to.
+ */
+ fun serialize(
+ patches: PatchSet,
+ transform: (Patch<*>) -> JsonPatch = { patch -> FullJsonPatch.fromPatch(patch) },
+ prettyPrint: Boolean = false,
+ outputStream: OutputStream,
+ ) {
+ patches.map(transform).let { transformed ->
+ if (prettyPrint) {
+ mapper.writerWithDefaultPrettyPrinter().writeValue(outputStream, transformed)
+ } else {
+ mapper.writeValue(outputStream, transformed)
+ }
+ }
+ }
+
+ /**
+ * Deserializes a JSON string to a set of [FullJsonPatch]es from an input stream.
+ *
+ * @param inputStream The input stream to read the JSON from.
+ * @param jsonPatchElementClass The class of the [JsonPatch]es to deserialize.
+ * @return A set of [JsonPatch]es.
+ * @see FullJsonPatch
+ */
+ fun deserialize(
+ inputStream: InputStream,
+ jsonPatchElementClass: Class,
+ ): Set =
+ mapper.readValue(
+ inputStream,
+ mapper.typeFactory.constructCollectionType(Set::class.java, jsonPatchElementClass),
+ )
+
+ interface JsonPatch
+
+ /**
+ * A JSON representation of a [Patch].
+ * @see Patch
+ */
+ class FullJsonPatch internal constructor(
+ val name: String?,
+ val description: String?,
+ val compatiblePackages: Set?,
+ val dependencies: Set?,
+ val use: Boolean,
+ var requiresIntegrations: Boolean,
+ val options: Map>,
+ ) : JsonPatch {
+ companion object {
+ fun fromPatch(patch: Patch<*>) =
+ FullJsonPatch(
+ patch.name,
+ patch.description,
+ patch.compatiblePackages,
+ patch.dependencies,
+ patch.use,
+ patch.requiresIntegrations,
+ patch.options.mapValues { FullJsonPatchOption.fromPatchOption(it.value) },
+ )
+ }
+
+ /**
+ * A JSON representation of a [PatchOption].
+ * @see PatchOption
+ */
+ class FullJsonPatchOption internal constructor(
+ val key: String,
+ val default: T?,
+ val values: Map?,
+ val title: String?,
+ val description: String?,
+ val required: Boolean,
+ val valueType: String,
+ ) {
+ companion object {
+ fun fromPatchOption(option: PatchOption<*>) =
+ FullJsonPatchOption(
+ option.key,
+ option.default,
+ option.values,
+ option.title,
+ option.description,
+ option.required,
+ option.valueType,
+ )
+ }
+ }
+ }
+ }
}
diff --git a/src/main/kotlin/app/revanced/library/adb/AdbManager.kt b/src/main/kotlin/app/revanced/library/adb/AdbManager.kt
index d6d868f..82a71f5 100644
--- a/src/main/kotlin/app/revanced/library/adb/AdbManager.kt
+++ b/src/main/kotlin/app/revanced/library/adb/AdbManager.kt
@@ -5,10 +5,11 @@ import app.revanced.library.adb.Constants.CREATE_DIR
import app.revanced.library.adb.Constants.DELETE
import app.revanced.library.adb.Constants.GET_INSTALLED_PATH
import app.revanced.library.adb.Constants.INSTALLATION_PATH
-import app.revanced.library.adb.Constants.INSTALL_MOUNT
+import app.revanced.library.adb.Constants.INSTALL_MOUNT_SCRIPT
import app.revanced.library.adb.Constants.INSTALL_PATCHED_APK
-import app.revanced.library.adb.Constants.MOUNT_PATH
+import app.revanced.library.adb.Constants.KILL
import app.revanced.library.adb.Constants.MOUNT_SCRIPT
+import app.revanced.library.adb.Constants.MOUNT_SCRIPT_PATH
import app.revanced.library.adb.Constants.PATCHED_APK_PATH
import app.revanced.library.adb.Constants.PLACEHOLDER
import app.revanced.library.adb.Constants.RESTART
@@ -104,9 +105,8 @@ sealed class AdbManager private constructor(deviceSerial: String?) {
device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement(packageName))
- device.run(INSTALL_MOUNT, packageName).waitFor()
- device.run(UMOUNT, packageName).waitFor() // Sanity check.
- device.run(MOUNT_PATH, packageName).waitFor()
+ device.run(INSTALL_MOUNT_SCRIPT, packageName).waitFor()
+ device.run(MOUNT_SCRIPT_PATH, packageName).waitFor()
device.run(RESTART, packageName)
device.run(DELETE, TMP_PATH)
@@ -118,9 +118,9 @@ sealed class AdbManager private constructor(deviceSerial: String?) {
device.run(UMOUNT, packageName)
device.run(DELETE.applyReplacement(PATCHED_APK_PATH), packageName)
- device.run(DELETE, MOUNT_PATH.applyReplacement(packageName))
+ device.run(DELETE, MOUNT_SCRIPT_PATH.applyReplacement(packageName))
device.run(DELETE, TMP_PATH)
- device.run(RESTART, packageName)
+ device.run(KILL, packageName)
super.uninstall(packageName)
}
diff --git a/src/main/kotlin/app/revanced/library/adb/Constants.kt b/src/main/kotlin/app/revanced/library/adb/Constants.kt
index 74641a0..ada0a2f 100644
--- a/src/main/kotlin/app/revanced/library/adb/Constants.kt
+++ b/src/main/kotlin/app/revanced/library/adb/Constants.kt
@@ -6,11 +6,12 @@ internal object Constants {
internal const val TMP_PATH = "/data/local/tmp/revanced.tmp"
internal const val INSTALLATION_PATH = "/data/adb/revanced/"
internal const val PATCHED_APK_PATH = "$INSTALLATION_PATH$PLACEHOLDER.apk"
- internal const val MOUNT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
+ internal const val MOUNT_SCRIPT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
internal const val DELETE = "rm -rf $PLACEHOLDER"
internal const val CREATE_DIR = "mkdir -p"
internal const val RESTART = "am start -S $PLACEHOLDER"
+ internal const val KILL = "am force-stop $PLACEHOLDER"
internal const val GET_INSTALLED_PATH = "pm path $PLACEHOLDER"
internal const val INSTALL_PATCHED_APK =
@@ -23,7 +24,7 @@ internal object Constants {
internal const val UMOUNT =
"grep $PLACEHOLDER /proc/mounts | while read -r line; do echo ${'$'}line | cut -d ' ' -f 2 | sed 's/apk.*/apk/' | xargs -r umount -l; done"
- internal const val INSTALL_MOUNT = "mv $TMP_PATH $MOUNT_PATH && chmod +x $MOUNT_PATH"
+ internal const val INSTALL_MOUNT_SCRIPT = "mv $TMP_PATH $MOUNT_SCRIPT_PATH && chmod +x $MOUNT_SCRIPT_PATH"
internal val MOUNT_SCRIPT =
"""
@@ -33,11 +34,17 @@ internal object Constants {
until [ "$( getprop sys.boot_completed )" = 1 ]; do sleep 3; done
until [ -d "/sdcard/Android" ]; do sleep 1; done
-
+
+ # Unmount any existing mount as a safety measure
+ $UMOUNT
+
base_path="$PATCHED_APK_PATH"
stock_path=$( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
- chcon u:object_r:apk_data_file:s0 ${'$'}base_path
+ chcon u:object_r:apk_data_file:s0 ${'$'}base_path
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
+
+ # Kill the app to force it to restart the mounted APK in case it's already running
+ $KILL
""".trimIndent()
}
diff --git a/src/test/kotlin/app/revanced/library/PatchUtilsTest.kt b/src/test/kotlin/app/revanced/library/PatchUtilsTest.kt
index fc635cf..7e63358 100644
--- a/src/test/kotlin/app/revanced/library/PatchUtilsTest.kt
+++ b/src/test/kotlin/app/revanced/library/PatchUtilsTest.kt
@@ -4,19 +4,24 @@ import app.revanced.patcher.PatchSet
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.Patch
+import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
+import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.intArrayPatchOption
+import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import org.junit.jupiter.api.Test
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
import kotlin.test.assertEquals
internal object PatchUtilsTest {
private val patches =
arrayOf(
- newPatch("some.package", setOf("a")),
+ newPatch("some.package", setOf("a")) { stringPatchOption("string", "value") },
newPatch("some.package", setOf("a", "b"), use = false),
newPatch("some.package", setOf("a", "b", "c"), use = false),
newPatch("some.other.package", setOf("b"), use = false),
- newPatch("some.other.package", setOf("b", "c")),
+ newPatch("some.other.package", setOf("b", "c")) { booleanPatchOption("bool", true) },
newPatch("some.other.package", setOf("b", "c", "d")),
- newPatch("some.other.other.package"),
+ newPatch("some.other.other.package") { intArrayPatchOption("intArray", arrayOf(1, 2, 3)) },
newPatch("some.other.other.package", setOf("a")),
newPatch("some.other.other.package", setOf("b")),
newPatch("some.other.other.other.package", use = false),
@@ -136,6 +141,20 @@ internal object PatchUtilsTest {
assertEqualsVersion(null, patches, "other.package")
}
+ @Test
+ fun `serializes to and deserializes from JSON string correctly`() {
+ val out = ByteArrayOutputStream()
+ PatchUtils.Json.serialize(patches, outputStream = out)
+
+ val deserialized =
+ PatchUtils.Json.deserialize(
+ ByteArrayInputStream(out.toByteArray()),
+ PatchUtils.Json.FullJsonPatch::class.java,
+ )
+
+ assert(patches.size == deserialized.size)
+ }
+
private fun assertEqualsVersions(
expected: PackageNameMap,
patches: PatchSet,
@@ -170,19 +189,14 @@ internal object PatchUtilsTest {
packageName: String,
versions: Set? = null,
use: Boolean = true,
- ) = object : BytecodePatch() {
+ options: Patch<*>.() -> Unit = {},
+ ) = object : BytecodePatch(
+ name = "test",
+ compatiblePackages = setOf(CompatiblePackage(packageName, versions?.toSet())),
+ use = use,
+ ) {
init {
- // Set the compatible packages field to the supplied package name and versions reflectively,
- // because the setter is private but needed for testing.
- val compatiblePackagesField = Patch::class.java.getDeclaredField("compatiblePackages")
-
- compatiblePackagesField.isAccessible = true
- compatiblePackagesField.set(this, setOf(CompatiblePackage(packageName, versions?.toSet())))
-
- val useField = Patch::class.java.getDeclaredField("use")
-
- useField.isAccessible = true
- useField.set(this, use)
+ options()
}
override fun execute(context: BytecodeContext) {}