diff --git a/CHANGELOG.md b/CHANGELOG.md index 73e2c47..128ae37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# [1.5.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.5.0-dev.1...v1.5.0-dev.2) (2023-12-07) + + +### Features + +* Improve mount reliability by unmounting existing mounts and killing running apps ([9fda407](https://github.com/ReVanced/revanced-library/commit/9fda40744173669c84b0c2599ae5ac5d39591798)) + +# [1.5.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.4.0...v1.5.0-dev.1) (2023-12-07) + + +### Features + +* Add JSON de- and serialization of patches ([ecff6fe](https://github.com/ReVanced/revanced-library/commit/ecff6fe0d3889d729a0badcfa28b89610bd27d48)) + # [1.4.0](https://github.com/ReVanced/revanced-library/compare/v1.3.0...v1.4.0) (2023-11-27) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 203daaa..b1ad077 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,34 +6,53 @@ srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg" >
- + + + +     - +     - + + + +     - + + + +     - + + + +     - - + + + + +     - -     + + + + +

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) {}