Compare commits

...

1 Commits

Author SHA1 Message Date
oSumAtrIX
42de60e95b feat: Add networking module 2024-04-17 05:22:41 +02:00
58 changed files with 1880 additions and 189 deletions

View File

@@ -1,143 +1,30 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.library)
alias(libs.plugins.binary.compatibility.validator)
`maven-publish`
signing
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.binary.compatibility.validator) apply false
alias(libs.plugins.ktor) apply false
}
group = "app.revanced"
// Because access to the project is necessary to authenticate with GitHub,
// the following block must be placed in the root build.gradle.kts file
// instead of the settings.gradle.kts file inside the dependencyResolutionManagement block.
repositories {
mavenCentral()
mavenLocal()
google()
maven {
// A repository must be specified for some reason. "registry" is a dummy.
url = uri("https://maven.pkg.github.com/revanced/registry")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
}
}
maven { url = uri("https://jitpack.io") }
}
kotlin {
jvm {
compilations.all {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
}
}
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
}
publishLibraryVariants("release")
}
sourceSets {
androidMain.dependencies {
implementation(libs.libsu.nio)
implementation(libs.libsu.service)
implementation(libs.core.ktx)
}
commonMain.dependencies {
implementation(libs.revanced.patcher)
implementation(libs.kotlin.reflect)
implementation(libs.jadb) // Fork with Shell v2 support.
implementation(libs.bcpkix.jdk15on)
implementation(libs.jackson.module.kotlin)
implementation(libs.apkzlib)
implementation(libs.apksig)
implementation(libs.guava)
}
commonTest.dependencies {
implementation(libs.revanced.patcher)
implementation(libs.kotlin.test.junit)
}
}
}
android {
namespace = "app.revanced.library"
compileSdk = 34
defaultConfig {
minSdk = 26
}
buildFeatures {
aidl = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
java {
targetCompatibility = JavaVersion.VERSION_11
}
publishing {
subprojects {
// Because access to the project is necessary to authenticate with GitHub,
// the following block must be placed in the root build.gradle.kts file
// instead of the settings.gradle.kts file inside the dependencyResolutionManagement block.
repositories {
mavenCentral()
mavenLocal()
google()
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-library")
// A repository must be specified for some reason. "registry" is a dummy.
url = uri("https://maven.pkg.github.com/revanced/registry")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
publications {
create<MavenPublication>("revanced-library-publication") {
version = project.version.toString()
pom {
name = "ReVanced Library"
description = "Library containing common utilities for ReVanced"
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-library.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
url = "https://github.com/revanced/revanced-library"
}
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
}
}
maven { url = uri("https://jitpack.io") }
}
}
signing {
useGpgCmd()
sign(publishing.publications["revanced-library-publication"])
}

View File

@@ -2,13 +2,18 @@
jackson-module-kotlin = "2.15.0"
jadb = "1.2.1"
kotlin = "1.9.22"
ktor-client = "2.3.10"
ktor-server-test-host = "2.3.9"
revanced-patcher = "19.3.1"
binary-compatibility-validator = "0.14.0"
android = "8.3.0"
android = "8.3.2"
bcpkix-jdk15on = "1.70"
guava = "33.0.0-jre"
libsu = "5.2.2"
core-ktx = "1.12.0"
ktor = "2.3.9"
koin = "3.5.3"
logback = "1.4.14"
[libraries]
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" }
@@ -24,9 +29,26 @@ libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu"
libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" }
libsu-service = { module = "com.github.topjohnwu.libsu:service", version.ref = "libsu" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor-client" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor-client" }
ktor-server-conditional-headers = { module = "io.ktor:ktor-server-conditional-headers" }
ktor-server-core = { module = "io.ktor:ktor-server-core" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation" }
ktor-server-auth = { module = "io.ktor:ktor-server-auth" }
ktor-server-auth-jwt = { module = "io.ktor:ktor-server-auth-jwt" }
ktor-server-cors = { module = "io.ktor:ktor-server-cors" }
ktor-server-caching-headers = { module = "io.ktor:ktor-server-caching-headers" }
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty" }
ktor-server-websockets = { module = "io.ktor:ktor-server-websockets" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json" }
koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" }
[plugins]
ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
android-library = { id = "com.android.library", version.ref = "android" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

View File

@@ -0,0 +1,143 @@
public final class app/revanced/library/networking/Server {
public final fun start ()Lio/ktor/server/engine/ApplicationEngine;
public final fun stop ()V
}
public final class app/revanced/library/networking/Server$DependenciesConfiguration {
public fun <init> (Lapp/revanced/library/networking/configuration/repository/StorageRepository;Lapp/revanced/library/networking/configuration/repository/PatchSetRepository;Lapp/revanced/library/networking/configuration/repository/AppRepository;Lapp/revanced/library/networking/configuration/repository/InstallerRepository;)V
}
public final class app/revanced/library/networking/Server$SecurityConfiguration {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/library/networking/Server$SerializersConfiguration {
public fun <init> ()V
public fun <init> (Ljava/util/Map;)V
public synthetic fun <init> (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/library/networking/ServerBuilder {
public fun <init> ()V
public final fun configureDependencies (Lkotlin/jvm/functions/Function1;)Lapp/revanced/library/networking/ServerBuilder;
public final fun configureSecurity (Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/networking/ServerBuilder;
public final fun configureSerializers (Lkotlin/jvm/functions/Function1;)Lapp/revanced/library/networking/ServerBuilder;
}
public final class app/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder {
public final fun build ()Lapp/revanced/library/networking/Server$DependenciesConfiguration;
public final fun configureAppRepository (Lapp/revanced/library/networking/configuration/repository/AppRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
public final fun configureInstallerRepository (Lapp/revanced/library/networking/configuration/repository/InstallerRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
public final fun configurePatchSetRepository (Lapp/revanced/library/networking/configuration/repository/PatchSetRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
public final fun configureStorageRepository (Lapp/revanced/library/networking/configuration/repository/StorageRepository;)Lapp/revanced/library/networking/ServerBuilder$DependenciesConfigurationBuilder;
}
public final class app/revanced/library/networking/ServerBuilder$SerializersConfigurationBuilder {
public final fun build ()Lapp/revanced/library/networking/Server$SerializersConfiguration;
public final fun configurePatchOptionSerializers ([Lkotlin/Pair;)V
}
public final class app/revanced/library/networking/ServerKt {
public static final fun main ()V
public static synthetic fun main ([Ljava/lang/String;)V
public static final fun server (Ljava/lang/String;ILio/ktor/server/engine/ApplicationEngineFactory;Lkotlin/jvm/functions/Function1;)Lapp/revanced/library/networking/Server;
public static synthetic fun server$default (Ljava/lang/String;ILio/ktor/server/engine/ApplicationEngineFactory;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/library/networking/Server;
}
public final class app/revanced/library/networking/configuration/SerializationKt {
public static final fun configureSerialization (Lio/ktor/server/application/Application;Lapp/revanced/library/networking/Server$SerializersConfiguration;)V
}
public abstract class app/revanced/library/networking/configuration/repository/AppRepository {
public fun <init> ()V
}
public abstract class app/revanced/library/networking/configuration/repository/InstallerRepository {
public fun <init> ()V
}
public abstract class app/revanced/library/networking/configuration/repository/PatchSetRepository {
public fun <init> (Lapp/revanced/library/networking/configuration/repository/StorageRepository;)V
}
public abstract class app/revanced/library/networking/configuration/repository/StorageRepository {
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/io/File;)V
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAaptBinaryPath ()Ljava/io/File;
public final fun getKeystoreFilePath ()Ljava/io/File;
public final fun getOutputFilePath ()Ljava/io/File;
public final fun getTemporaryFilesPath ()Ljava/io/File;
}
public class app/revanced/library/networking/models/App {
public static final field Companion Lapp/revanced/library/networking/models/App$Companion;
public synthetic fun <init> (ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public static final synthetic fun write$Self (Lapp/revanced/library/networking/models/App;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}
public final class app/revanced/library/networking/models/App$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lapp/revanced/library/networking/models/App$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lapp/revanced/library/networking/models/App;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lapp/revanced/library/networking/models/App;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/App$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/Patch {
public static final field Companion Lapp/revanced/library/networking/models/Patch$Companion;
}
public final class app/revanced/library/networking/models/Patch$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lapp/revanced/library/networking/models/Patch$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lapp/revanced/library/networking/models/Patch;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lapp/revanced/library/networking/models/Patch;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/Patch$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/Patch$KeyValuePatchOption {
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;)V
public final fun getKey ()Ljava/lang/String;
public final fun getValue ()Ljava/lang/Object;
public final fun getValueType ()Ljava/lang/String;
}
public final class app/revanced/library/networking/models/Patch$PatchOption {
public static final field Companion Lapp/revanced/library/networking/models/Patch$PatchOption$Companion;
}
public final class app/revanced/library/networking/models/Patch$PatchOption$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public synthetic fun <init> (Lkotlinx/serialization/KSerializer;)V
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lapp/revanced/library/networking/models/Patch$PatchOption;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lapp/revanced/library/networking/models/Patch$PatchOption;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/Patch$PatchOption$Companion {
public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
}
public final class app/revanced/library/networking/models/PatchBundle {
public final fun getPatchBundleFile ()Ljava/io/File;
public final fun getPatchBundleIntegrationsFile ()Ljava/io/File;
}

View File

@@ -0,0 +1,97 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.binary.compatibility.validator)
alias(libs.plugins.ktor)
`maven-publish`
signing
}
dependencies {
implementation(project(":library"))
implementation(libs.revanced.patcher)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio)
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.content.negotiation)
implementation(libs.ktor.server.auth)
implementation(libs.ktor.server.auth.jwt)
implementation(libs.ktor.server.cors)
implementation(libs.ktor.server.caching.headers)
implementation(libs.ktor.server.host.common)
implementation(libs.ktor.server.netty)
implementation(libs.ktor.server.conditional.headers)
implementation(libs.ktor.server.websockets)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.koin.ktor)
implementation(libs.logback.classic)
}
tasks {
processResources {
expand("projectVersion" to project.version)
}
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
java {
targetCompatibility = JavaVersion.VERSION_11
}
publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-library")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
publications {
create<MavenPublication>("revanced-library-networking-publication") {
version = project.version.toString()
pom {
name = "ReVanced Networking Library"
description = "Library to interface to common utilities for ReVanced over a network."
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-library.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
url = "https://github.com/revanced/revanced-library"
}
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications["revanced-library-networking-publication"])
}

View File

@@ -0,0 +1,6 @@
package app.revanced.library.networking
import io.ktor.server.application.*
import io.ktor.util.pipeline.*
internal val PipelineContext<*, ApplicationCall>.parameters get() = call.parameters

View File

@@ -0,0 +1,281 @@
@file:Suppress("unused")
package app.revanced.library.networking
import app.revanced.library.installation.installer.AdbInstaller
import app.revanced.library.networking.configuration.configureDependencies
import app.revanced.library.networking.configuration.configureHTTP
import app.revanced.library.networking.configuration.configureSecurity
import app.revanced.library.networking.configuration.configureSerialization
import app.revanced.library.networking.configuration.repository.AppRepository
import app.revanced.library.networking.configuration.repository.InstallerRepository
import app.revanced.library.networking.configuration.repository.PatchSetRepository
import app.revanced.library.networking.configuration.repository.StorageRepository
import app.revanced.library.networking.configuration.routing.configureRouting
import app.revanced.library.networking.models.App
import app.revanced.library.networking.models.PatchBundle
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.PatchSet
import app.revanced.patcher.patch.options.PatchOption
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import java.io.File
import java.time.LocalDateTime
import kotlin.reflect.KType
import kotlin.reflect.typeOf
/**
* A server.
*
* @param host The host.
* @param port The port.
* @param engineFactory The engine factory.
* @param securityConfiguration The security configuration.
* @param dependenciesConfiguration The dependencies configuration.
* @param serializersConfiguration The serializers configuration.
*/
class Server internal constructor(
host: String,
port: Int,
engineFactory: ApplicationEngineFactory<*, *>,
securityConfiguration: SecurityConfiguration,
dependenciesConfiguration: DependenciesConfiguration,
serializersConfiguration: SerializersConfiguration,
) {
private val applicationEngine = embeddedServer(engineFactory, port, host) {
configureHTTP(allowedHost = host)
configureSecurity(securityConfiguration)
configureDependencies(dependenciesConfiguration)
configureSerialization(serializersConfiguration)
configureRouting()
}
/**
* Starts the server and blocks the current thread.
*/
fun start() = applicationEngine.start(wait = true)
/**
* Stops the server.
*/
fun stop() = applicationEngine.stop()
/**
* The security configuration.
*
* @property username The username.
* @property password The password.
*/
class SecurityConfiguration(
internal val username: String,
internal val password: String,
)
/**
* The dependencies configuration.
*
* @property storageRepository The storage repository.
* @property patchSetRepository The patch set repository.
* @property appRepository The app repository.
* @property installerRepository The installer repository.
*/
class DependenciesConfiguration(
internal val storageRepository: StorageRepository,
internal val patchSetRepository: PatchSetRepository,
internal val appRepository: AppRepository,
internal val installerRepository: InstallerRepository,
)
/**
* The serializers configuration.
*
* @property patchOptionValueTypes A map of [PatchOption.valueType] to [KType] to add serializers for patch options
* additional to the default ones.
*/
class SerializersConfiguration(
internal val patchOptionValueTypes: Map<String, KType> = emptyMap(),
)
}
/**
* A server builder.
*
* @property host The host.
* @property port The port.
* @property engineFactory The engine factory.
* @property securityConfiguration The security configuration.
* @property dependenciesConfiguration The dependencies configuration.
*/
class ServerBuilder internal constructor(
private val host: String = "localhost",
private val port: Int = 8080,
private val engineFactory: ApplicationEngineFactory<*, *> = Netty,
) {
private lateinit var securityConfiguration: Server.SecurityConfiguration
private lateinit var dependenciesConfiguration: Server.DependenciesConfiguration
private var serializersConfiguration = Server.SerializersConfiguration()
/**
* Configures the security.
*
* @param basicUsername The basic username.
* @param basicPassword The basic password.
*
* @return The server builder.
*/
fun configureSecurity(
basicUsername: String,
basicPassword: String,
) = apply {
securityConfiguration = Server.SecurityConfiguration(
username = basicUsername,
password = basicPassword,
)
}
/**
* Configures the dependencies.
*
* @param block The block to configure the dependencies.
*
* @return The server builder.
*/
fun configureDependencies(block: DependenciesConfigurationBuilder.() -> Unit) = apply {
dependenciesConfiguration = DependenciesConfigurationBuilder().apply(block).build()
}
/**
* Configures the serializers.
*
* @param block The block to configure the serializers.
*
* @return The server builder.
*/
fun configureSerializers(block: SerializersConfigurationBuilder.() -> Unit) = apply {
serializersConfiguration = SerializersConfigurationBuilder().apply(block).build()
}
class DependenciesConfigurationBuilder internal constructor() {
private lateinit var storageRepository: StorageRepository
private lateinit var patchSetRepository: PatchSetRepository
private lateinit var appRepository: AppRepository
private lateinit var installerRepository: InstallerRepository
fun configureStorageRepository(storageRepository: StorageRepository) = apply {
this.storageRepository = storageRepository
}
fun configurePatchSetRepository(patchSetRepository: PatchSetRepository) = apply {
this.patchSetRepository = patchSetRepository
}
fun configureAppRepository(appRepository: AppRepository) = apply {
this.appRepository = appRepository
}
fun configureInstallerRepository(installerRepository: InstallerRepository) = apply {
this.installerRepository = installerRepository
}
fun build() = Server.DependenciesConfiguration(
storageRepository,
patchSetRepository,
appRepository,
installerRepository,
)
}
class SerializersConfigurationBuilder internal constructor() {
private lateinit var patchOptionValueTypes: Map<String, KType>
fun configurePatchOptionSerializers(vararg pairs: Pair<String, KType>) {
this.patchOptionValueTypes = mapOf(*pairs)
}
fun build() = Server.SerializersConfiguration(patchOptionValueTypes)
}
/**
* Builds the server.
*
* @return The server.
*/
internal fun build() = Server(
host,
port,
engineFactory,
securityConfiguration,
dependenciesConfiguration,
serializersConfiguration,
)
}
/**
* Creates a server.
*
* @param host The host.
* @param port The port.
* @param engineFactory The engine factory.
* @param block The block to build the server.
*
* @return The server.
*/
fun server(
host: String = "localhost",
port: Int = 8080,
engineFactory: ApplicationEngineFactory<*, *> = Netty,
block: ServerBuilder.() -> Unit = {},
) = ServerBuilder(host, port, engineFactory).apply(block).build()
fun main() {
server {
configureSecurity("username", "password")
val storageRepository = object : StorageRepository(
temporaryFilesPath = File("temp"),
keystoreFilePath = File("keystore.jks"),
) {
override fun readPatchBundles() = setOf(
PatchBundle(
"ReVanced Patches",
File("D:\\ReVanced\\revanced-patches\\build\\libs\\revanced-patches-4.7.0-dev.2.jar"),
),
)
override fun writePatchBundles(patchBundles: Set<PatchBundle>) {
// TODO("Not yet implemented")
}
override fun newPatchBundle(patchBundleName: String, withIntegrations: Boolean): PatchBundle {
TODO("Not yet implemented")
}
}
val patchSetRepository = object : PatchSetRepository(storageRepository) {
override fun readPatchSet(patchBundles: Set<PatchBundle>): PatchSet {
return PatchBundleLoader.Jar(*patchBundles.map { it.patchBundleFile }.toTypedArray())
}
}
val appRepository = object : AppRepository() {
override fun readInstalledApps() = emptySet<App>()
}
val installerRepository = object : InstallerRepository() {
override val installer = AdbInstaller("127.0.0.1:58526")
}
configureDependencies {
configureStorageRepository(storageRepository)
configurePatchSetRepository(patchSetRepository)
configureAppRepository(appRepository)
configureInstallerRepository(installerRepository)
}
configureSerializers {
configurePatchOptionSerializers(
"LocalDateTime" to typeOf<PatchOption<LocalDateTime>>(),
)
}
}.start()
}

View File

@@ -0,0 +1,43 @@
package app.revanced.library.networking.configuration
import app.revanced.library.networking.Server
import app.revanced.library.networking.services.HttpClientService
import app.revanced.library.networking.services.PatchBundleService
import app.revanced.library.networking.services.PatcherService
import io.ktor.server.application.*
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
import org.koin.ktor.plugin.Koin
/**
* Configure the dependencies for the application.
*
* @param dependenciesConfiguration The dependencies configuration.
*/
internal fun Application.configureDependencies(
dependenciesConfiguration: Server.DependenciesConfiguration,
) {
val globalModule = module {
single { dependenciesConfiguration.storageRepository }
single { dependenciesConfiguration.patchSetRepository }
single { dependenciesConfiguration.appRepository }
single { dependenciesConfiguration.installerRepository }
}
val patchBundleModule = module {
single { HttpClientService() }
singleOf(::PatchBundleService)
}
val patcherModule = module {
singleOf(::PatcherService)
}
install(Koin) {
modules(
globalModule,
patchBundleModule,
patcherModule,
)
}
}

View File

@@ -0,0 +1,33 @@
package app.revanced.library.networking.configuration
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.plugins.cachingheaders.*
import io.ktor.server.plugins.conditionalheaders.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.websocket.*
import kotlin.time.Duration.Companion.minutes
/**
* Configures HTTP for the application.
*
* @param allowedHost The allowed host for the application.
*/
internal fun Application.configureHTTP(
allowedHost: String,
) {
install(ConditionalHeaders)
install(CORS) {
allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
allowMethod(HttpMethod.Patch)
allowHeader(HttpHeaders.Authorization)
allowHost(allowedHost)
}
install(WebSockets)
install(CachingHeaders) {
options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) }
}
}

View File

@@ -0,0 +1,28 @@
package app.revanced.library.networking.configuration
import app.revanced.library.networking.Server
import io.ktor.server.application.*
import io.ktor.server.auth.*
/**
* Configures the security for the application.
*
* @param securityConfiguration The security configuration.
*/
internal fun Application.configureSecurity(
securityConfiguration: Server.SecurityConfiguration,
) {
install(Authentication) {
basic {
validate { credentials ->
if (credentials.name == securityConfiguration.username &&
credentials.password == securityConfiguration.password
) {
UserIdPrincipal(credentials.name)
} else {
null
}
}
}
}
}

View File

@@ -0,0 +1,92 @@
package app.revanced.library.networking.configuration
import app.revanced.library.networking.Server
import app.revanced.library.networking.models.Patch
import app.revanced.patcher.patch.options.PatchOption
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.decodeStructure
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.SerializersModuleBuilder
import kotlinx.serialization.modules.contextual
import kotlinx.serialization.serializer
import java.io.Serializable
import kotlin.reflect.KType
import kotlin.reflect.typeOf
/**
* Configures the serialization for the application.
*
* @param serializersConfiguration The serializers configuration.
*/
fun Application.configureSerialization(serializersConfiguration: Server.SerializersConfiguration) {
install(ContentNegotiation) {
json(
Json {
serializersModule = SerializersModule {
configurePatchOptionSerializers(serializersConfiguration.patchOptionValueTypes)
}
},
)
}
}
/**
* Configures the patch option serializers.
*
* @param patchOptionValueTypes A map of [PatchOption.valueType] to [KType] to add serializers for patch options
* additional to the default ones.
*/
private fun SerializersModuleBuilder.configurePatchOptionSerializers(patchOptionValueTypes: Map<String, KType>) {
val knownPatchOptionValueTypes = mapOf(
"String" to typeOf<Patch.PatchOption<String>>(),
"Int" to typeOf<Patch.PatchOption<Int>>(),
"Boolean" to typeOf<Patch.PatchOption<Boolean>>(),
"Long" to typeOf<Patch.PatchOption<Long>>(),
"Float" to typeOf<Patch.PatchOption<Float>>(),
"StringArray" to typeOf<Patch.PatchOption<Array<String>>>(),
"IntArray" to typeOf<Patch.PatchOption<IntArray>>(),
"BooleanArray" to typeOf<Patch.PatchOption<BooleanArray>>(),
"LongArray" to typeOf<Patch.PatchOption<LongArray>>(),
"FloatArray" to typeOf<Patch.PatchOption<FloatArray>>(),
) + patchOptionValueTypes
/**
* Gets the [KType] for a patch option value type.
*
* @param valueType The value type of the patch option.
*
* @return The [KType] for the patch option value type.
*/
fun patchOptionTypeOf(valueType: String) = knownPatchOptionValueTypes[valueType]
?: error("Unknown patch option value type: $valueType")
/**
* Serializer for [Patch.PatchOption].
* Uses the [Patch.PatchOption.valueType] to determine the serializer for the generic type.
*/
val patchOptionSerializer = object : KSerializer<Patch.PatchOption<*>> {
override val descriptor = serializer(typeOf<Patch.PatchOption<Serializable>>()).descriptor
override fun serialize(encoder: Encoder, value: Patch.PatchOption<*>) = serializer(
patchOptionTypeOf(value.valueType),
).serialize(encoder, value)
override fun deserialize(decoder: Decoder) = serializer(
patchOptionTypeOf(
decoder.decodeStructure(descriptor) {
decodeStringElement(descriptor, descriptor.getElementIndex("valueType"))
},
),
).deserialize(decoder) as Patch.PatchOption<*>
}
contextual(patchOptionSerializer)
contextual(SetSerializer(patchOptionSerializer))
}

View File

@@ -0,0 +1,32 @@
package app.revanced.library.networking.configuration.repository
import app.revanced.library.networking.models.App
/**
* A repository for apps and installers.
*/
abstract class AppRepository {
/**
* The set of [App] installed.
*/
internal lateinit var installedApps: Set<App>
private set
init {
readAndSetInstalledApps()
}
/**
* Read a set of [App] from a storage.
*
* @return The set of [App] read.
*/
internal abstract fun readInstalledApps(): Set<App>
/**
* Read a set of [App] using [readInstalledApps] and set [installedApps] to it.
*/
internal fun readAndSetInstalledApps() {
this.installedApps = readInstalledApps()
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.library.networking.configuration.repository
import app.revanced.library.installation.installer.Installer
import app.revanced.library.installation.installer.MountInstaller
import app.revanced.library.networking.models.App
abstract class InstallerRepository {
/**
* The installer to use for installing and uninstalling [App]s.
*/
internal abstract val installer: Installer<*, *>
/**
* The root installer to use for mounting and unmounting [App]s.
*/
internal open val mountInstaller: MountInstaller? = null
}

View File

@@ -0,0 +1,41 @@
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package app.revanced.library.networking.configuration.repository
import app.revanced.library.networking.models.PatchBundle
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.PatchSet
import app.revanced.patcher.patch.Patch
/**
* A repository for patches from a set of [PatchBundle]s.
*
* @param storageRepository The [StorageRepository] to read the [PatchBundle]s from.
*/
abstract class PatchSetRepository(
private val storageRepository: StorageRepository,
) {
/**
* The set of [Patch]es loaded from [StorageRepository.patchBundles].
*/
internal lateinit var patchSet: PatchSet
private set
init {
readAndSetPatchSet()
}
/**
* Read a [PatchSet] from a set of [patchBundles] using a [PatchBundleLoader].
*
* @param patchBundles The set of [PatchBundle]s to read the [PatchSet] from.
*/
internal abstract fun readPatchSet(patchBundles: Set<PatchBundle>): PatchSet
/**
* Read a [PatchSet] from patch bundles from [storageRepository] using [readPatchSet] and set [patchSet] to it.
*/
internal fun readAndSetPatchSet() {
this.patchSet = readPatchSet(storageRepository.patchBundles.values.toSet())
}
}

View File

@@ -0,0 +1,93 @@
package app.revanced.library.networking.configuration.repository
import app.revanced.library.networking.models.PatchBundle
import app.revanced.patcher.Patcher
import java.io.File
/**
* A repository for storage.
*
* @param temporaryFilesPath The path to the temporary files for [Patcher].
* @param outputFilePath The path to the output file to save patched APKs to.
* @param keystoreFilePath The path to the keystore file to sign patched APKs with.
* @param aaptBinaryPath The path to the aapt binary to use by [Patcher].
*/
abstract class StorageRepository(
val temporaryFilesPath: File,
val outputFilePath: File = File(temporaryFilesPath, "output.apk"),
val keystoreFilePath: File,
val aaptBinaryPath: File? = null,
) {
/**
* The stored [PatchBundle]s mapped by their name.
*/
internal lateinit var patchBundles: MutableMap<String, PatchBundle>
private set
/**
* The path to save the patched, but unsigned APK to.
*/
internal val unsignedApkFilePath = File(temporaryFilesPath, "unsigned.apk")
init {
readAndSetPatchBundles()
}
/**
* Read a set of [patchBundles] from a storage.
*
* @return The set of [PatchBundle] read.
*/
internal abstract fun readPatchBundles(): Set<PatchBundle>
/**
* Write a set of [patchBundles] to a storage.
*
* @param patchBundles The set of patch bundles to write.
*/
internal abstract fun writePatchBundles(patchBundles: Set<PatchBundle>)
/**
* Create a new [PatchBundle] in a storage to write to.
*
* @param patchBundleName The name of the patch bundle.
* @param withIntegrations Whether the patch bundle also has integrations.
*
* @return The new [PatchBundle] created.
*/
internal abstract fun newPatchBundle(patchBundleName: String, withIntegrations: Boolean): PatchBundle
/**
* Read the set of [patchBundles] stored and set it to [patchBundles].
*/
internal fun readAndSetPatchBundles() {
patchBundles = readPatchBundles().associateBy { it.name }.toMutableMap()
}
/**
* Add a [patchBundle] to the map of the stored [patchBundles] and write the set to a storage using [writePatchBundles].
*
* @param patchBundle The patch bundle to add.
*/
internal fun addPersistentlyPatchBundle(patchBundle: PatchBundle) {
patchBundles[patchBundle.name] = patchBundle
writePatchBundles(patchBundles.values.toSet())
}
/**
* Remove a path bundle from the map of [patchBundles] stored and write the set to a storage using [writePatchBundles].
*
* @param patchBundleName The name of the patch bundle to remove.
*/
internal fun removePersistentlyPatchBundle(patchBundleName: String) {
patchBundles.remove(patchBundleName)
writePatchBundles(patchBundles.values.toSet())
}
/**
* Delete the temporary files.
*/
internal fun deleteTemporaryFiles() {
temporaryFilesPath.deleteRecursively()
}
}

View File

@@ -0,0 +1,23 @@
package app.revanced.library.networking.configuration.routing
import app.revanced.library.networking.configuration.routing.routes.configurePatchBundlesRoute
import app.revanced.library.networking.configuration.routing.routes.configurePatcherRoute
import app.revanced.library.networking.configuration.routing.routes.configurePingRoute
import app.revanced.library.networking.configuration.routing.routes.configureRootRoute
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.routing.*
/**
* Configures the routing for the application.
*/
internal fun Application.configureRouting() {
routing {
authenticate {
configureRootRoute()
configurePingRoute()
configurePatchBundlesRoute()
configurePatcherRoute()
}
}
}

View File

@@ -0,0 +1,57 @@
package app.revanced.library.networking.configuration.routing.routes
import app.revanced.library.networking.parameters
import app.revanced.library.networking.services.PatchBundleService
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.routing.get
import io.ktor.server.util.*
import org.koin.ktor.ext.get
/**
* Route to handle all patch bundle related requests such as creating, reading, updating and deleting patch bundles.
*/
internal fun Route.configurePatchBundlesRoute() {
val patchBundleService = get<PatchBundleService>()
route("/patch-bundles") {
get {
call.respond(patchBundleService.patchBundleNames)
}
post("/add") {
val patchBundleName: String by parameters
val patchBundleFilePath = parameters["patchBundleFilePath"]
if (patchBundleFilePath != null) {
val patchBundleIntegrationsFilePath = parameters["patchBundleIntegrationsFilePath"]
patchBundleService.addPersistentlyLocalPatchBundle(
patchBundleName,
patchBundleFilePath,
patchBundleIntegrationsFilePath,
)
} else {
val patchBundleDownloadLink: String by parameters
val patchBundleIntegrationsDownloadLink = parameters["patchBundleIntegrationsDownloadLink"]
patchBundleService.addPersistentlyDownloadPatchBundle(
patchBundleName,
patchBundleDownloadLink,
patchBundleIntegrationsDownloadLink,
)
}
}
post("/remove") {
val patchBundleName: String by parameters
patchBundleService.removePersistentlyPatchBundle(patchBundleName)
}
post("/refresh") {
patchBundleService.refresh()
}
}
}

View File

@@ -0,0 +1,179 @@
package app.revanced.library.networking.configuration.routing.routes
import app.revanced.library.networking.configuration.repository.InstallerRepository
import app.revanced.library.networking.models.Patch
import app.revanced.library.networking.parameters
import app.revanced.library.networking.services.PatcherService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.routing.get
import io.ktor.server.util.*
import org.koin.ktor.ext.get
import java.io.File
/**
* Route to the patcher to handles all patcher related requests such as patching, signing and installing patched apps.
*/
internal fun Route.configurePatcherRoute() {
route("/patcher") {
configureAppsRoute()
configurePatchesRoute()
configurePatchOptionsRoute()
configurePatchRoute()
configureSignRoute()
configureInstallationRoute()
configureCleanRoute()
}
}
/**
* Route to list all patchable apps that can be patched.
*/
private fun Route.configureAppsRoute() {
val patcherService = get<PatcherService>()
get("/apps") {
val universal = parameters.contains("universal")
call.respond(patcherService.getInstalledApps(universal))
}
}
/**
* Route to get all patches for a specific app and version.
*/
private fun Route.configurePatchesRoute() {
val patcherService = get<PatcherService>()
get("/patches") {
val app = parameters["app"]
val version = parameters["version"]
val universal = "universal" in parameters
call.respond(patcherService.getPatches(app, version, universal))
}
}
/**
* Route to get and set patch options.
*/
private fun Route.configurePatchOptionsRoute() {
val patcherService = get<PatcherService>()
route("/options") {
get {
val app: String by parameters
val patch: String by parameters
call.respond(patcherService.getPatchOptions(patchName = patch, app))
}
post {
// Abuse serialization capabilities of Patch.PatchOption
// because Patch.KeyValuePatchOption isn't serializable.
// ONLY the Patch.PatchOption.key and Patch.PatchOption.value properties are used here.
val patchOptions: Set<Patch.PatchOption<*>> by call.receive()
val patch: String by parameters
val app: String by parameters
patcherService.setPatchOptions(
// Use Patch.PatchOption.default for Patch.KeyValuePatchOption.value.
patchOptions = patchOptions.map { Patch.KeyValuePatchOption(it) }.toSet(),
patchName = patch,
app,
)
call.respond(HttpStatusCode.OK)
}
delete {
val patch: String by parameters
val app: String by parameters
patcherService.resetPatchOptions(patchName = patch, app)
call.respond(HttpStatusCode.OK)
}
}
}
/**
* Route to patch an app with a set of patches.
*/
private fun Route.configurePatchRoute() {
val installerRepository = get<InstallerRepository>()
val patcherService = get<PatcherService>()
post("/patch") {
val patchNames = parameters.getAll("patch")?.toSet() ?: emptySet()
val multithreading = "multithreading" in parameters
// TODO: The path to the APK must be local to the server, otherwise it will not work.
val apkPath = parameters["app"]?.let {
installerRepository.installer.getInstallation(it)?.apkFilePath
} ?: parameters["apkPath"]
val apkFile = File(apkPath ?: return@post call.respond(HttpStatusCode.BadRequest))
patcherService.patch(patchNames, multithreading, apkFile)
call.respond(HttpStatusCode.OK)
}
}
/**
* Route to sign the patched APK.
*/
private fun Route.configureSignRoute() {
val patcherService = get<PatcherService>()
post("/sign") {
val signer: String by parameters
val keyStorePassword = parameters["keyStorePassword"]
val keyStoreEntryAlias: String by parameters
val keyStoreEntryPassword: String by parameters
patcherService.sign(signer, keyStorePassword, keyStoreEntryAlias, keyStoreEntryPassword)
call.respond(HttpStatusCode.OK)
}
}
/**
* Route to install or uninstall a patched APK.
*/
private fun Route.configureInstallationRoute() {
val patcherService = get<PatcherService>()
post("/install") {
val mount = parameters["mount"]
patcherService.install(mount)
call.respond(HttpStatusCode.OK)
}
post("/uninstall") {
val packageName: String by parameters
val unmount = "unmount" in parameters
patcherService.uninstall(packageName, unmount)
call.respond(HttpStatusCode.OK)
}
}
/**
* Route to delete temporary files produced by the patcher.
*/
private fun Route.configureCleanRoute() {
val patcherService = get<PatcherService>()
post("/clean") {
patcherService.deleteTemporaryFiles()
call.respond(HttpStatusCode.OK)
}
}

View File

@@ -0,0 +1,15 @@
package app.revanced.library.networking.configuration.routing.routes
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
/**
* Route to check if the server is up.
*/
internal fun Route.configurePingRoute() {
head("/ping") {
call.respond(HttpStatusCode.OK)
}
}

View File

@@ -0,0 +1,60 @@
package app.revanced.library.networking.configuration.routing.routes
import app.revanced.library.logging.Logger
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.runBlocking
import java.util.*
internal fun Route.configureRootRoute() {
route("/") {
configureAboutRoute()
configureLoggingRoute()
}
}
/**
* Route to get information about the server.
*/
private fun Route.configureAboutRoute() {
val name = this::class.java.getResourceAsStream(
"/app/revanced/library/networking/version.properties",
)?.use { stream ->
Properties().apply {
load(stream)
}.let {
"ReVanced Networking Library v${it.getProperty("version")}"
}
} ?: "ReVanced Networking Library"
handle {
call.respondText(name)
}
}
// TODO: Fix clients disconnecting from the server.
/**
* Route to get logs from the server.
*/
private fun Route.configureLoggingRoute() {
val sessions = Collections.synchronizedSet<DefaultWebSocketSession?>(LinkedHashSet())
Logger.addHandler({ log: String, level: java.util.logging.Level, loggerName: String? ->
runBlocking {
sessions.forEach {
try {
it.send("[$loggerName] $level: $log")
} catch (e: Exception) {
sessions -= it
}
}
}
}, {}, {})
webSocket("/logs") {
sessions += this
}
}

View File

@@ -0,0 +1,57 @@
@file:Suppress("unused")
package app.revanced.library.networking.models
import kotlinx.serialization.*
import java.io.File
private typealias PackageName = String
private typealias PackageVersion = String
private typealias PackageVersions = Set<PackageVersion>
private typealias CompatiblePackages = Map<PackageName, PackageVersions?>
@Serializable
open class App(
internal val name: String,
internal val version: String,
internal val packageName: String,
)
@Serializable
class Patch internal constructor(
internal val name: String,
internal val description: String?,
internal val use: Boolean,
internal val compatiblePackages: CompatiblePackages?,
) {
@Serializable
class PatchOption<T> internal constructor(
internal val key: String,
internal val default: T?,
internal val values: Map<String, T?>?,
internal val title: String?,
internal val description: String?,
internal val required: Boolean,
internal val valueType: String,
)
class KeyValuePatchOption<T>(
val key: String,
val value: T?,
val valueType: String,
) {
// Abuse serialization capabilities of Patch.PatchOption which is used in request bodies.
// Use Patch.PatchOption.default as Patch.KeyValuePatchOption.value.
internal constructor(patchOption: PatchOption<T>) : this(
patchOption.key,
patchOption.default,
patchOption.valueType,
)
}
}
class PatchBundle internal constructor(
val name: String,
val patchBundleFile: File,
val patchBundleIntegrationsFile: File? = null,
)

View File

@@ -0,0 +1,26 @@
package app.revanced.library.networking.services
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.utils.io.*
import io.ktor.utils.io.jvm.javaio.*
import java.io.File
/**
* Service for HTTP client.
*/
internal class HttpClientService {
private val client by lazy { HttpClient(CIO) }
/**
* Download a file from a URL to a file.
*
* @param file The file to download to.
* @param url The URL to download from.
*/
internal suspend fun downloadToFile(file: File, url: String) {
client.get(url).body<ByteReadChannel>().copyTo(file.outputStream())
}
}

View File

@@ -0,0 +1,85 @@
package app.revanced.library.networking.services
import app.revanced.library.networking.configuration.repository.PatchSetRepository
import app.revanced.library.networking.configuration.repository.StorageRepository
import app.revanced.library.networking.models.PatchBundle
import java.io.File
/**
* Service for patch bundles.
*
* @property storageRepository The storage repository to get storage paths from.
* @property patchSetRepository The patch set repository to get patches from.
* @property httpClientService The HTTP client service to download patch bundles with.
*/
internal class PatchBundleService(
private val storageRepository: StorageRepository,
private val patchSetRepository: PatchSetRepository,
private val httpClientService: HttpClientService,
) {
/**
* Get the names of the patch bundles stored.
*
* @return The set of patch bundle names.
*/
internal val patchBundleNames: Set<String>
get() = storageRepository.patchBundles.keys.toSet()
/**
* Add a local patch bundle to storage persistently.
*
* @param patchBundleName The name of the patch bundle.
* @param patchBundleFilePath The path to the patch bundle file.
* @param patchBundleIntegrationsFilePath The path to the patch bundle integrations file.
*/
internal fun addPersistentlyLocalPatchBundle(
patchBundleName: String,
patchBundleFilePath: String,
patchBundleIntegrationsFilePath: String?,
) = storageRepository.addPersistentlyPatchBundle(
PatchBundle(
name = patchBundleName,
patchBundleFile = File(patchBundleFilePath),
patchBundleIntegrationsFile = patchBundleIntegrationsFilePath?.let { File(it) },
),
)
/**
* Add a patch bundle that needs to be downloaded to storage persistently.
*
* @param patchBundleName The name of the patch bundle.
* @param patchBundleDownloadLink The download link to the patch bundle.
* @param patchBundleIntegrationsDownloadLink The download link to the patch bundle integrations.
*/
internal suspend fun addPersistentlyDownloadPatchBundle(
patchBundleName: String,
patchBundleDownloadLink: String,
patchBundleIntegrationsDownloadLink: String?,
) {
val withIntegrations = patchBundleIntegrationsDownloadLink != null
storageRepository.newPatchBundle(patchBundleName, withIntegrations).apply {
httpClientService.downloadToFile(patchBundleFile, patchBundleDownloadLink)
if (withIntegrations) {
httpClientService.downloadToFile(patchBundleIntegrationsFile!!, patchBundleIntegrationsDownloadLink!!)
}
}
}
/**
* Remove a patch bundle from storage persistently.
*
* @param name The name of the patch bundle to remove.
*/
internal fun removePersistentlyPatchBundle(name: String) =
storageRepository.removePersistentlyPatchBundle(name)
/**
* Reload the patch bundles from storage and read the patch set from them.
*/
internal fun refresh() {
storageRepository.readAndSetPatchBundles()
patchSetRepository.readAndSetPatchSet()
}
}

View File

@@ -0,0 +1,250 @@
package app.revanced.library.networking.services
import app.revanced.library.ApkUtils
import app.revanced.library.ApkUtils.applyTo
import app.revanced.library.installation.installer.Installer
import app.revanced.library.networking.configuration.repository.AppRepository
import app.revanced.library.networking.configuration.repository.InstallerRepository
import app.revanced.library.networking.configuration.repository.PatchSetRepository
import app.revanced.library.networking.configuration.repository.StorageRepository
import app.revanced.library.networking.models.App
import app.revanced.library.networking.models.Patch
import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherConfig
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.util.logging.Logger
/**
* Service for patching and installing apps.
*
* @property storageRepository The storage repository to get storage paths from.
* @property patchSetRepository The patch set repository to get patches from.
* @property appRepository The app repository to get installed apps from.
* @property installerRepository The installer repository to install apps with.
*/
internal class PatcherService(
private val storageRepository: StorageRepository,
private val patchSetRepository: PatchSetRepository,
private val appRepository: AppRepository,
private val installerRepository: InstallerRepository,
) {
private val logger = Logger.getLogger(PatcherService::class.simpleName)
/**
* Get installed apps.
*
* @param universal Whether to show apps that only have universal patches.
*
* @return The installed apps.
*/
internal fun getInstalledApps(universal: Boolean = true): Set<App> {
// TODO: Show apps, that only have universal patches, only if universal is true.
return appRepository.installedApps
}
/**
* Get patches.
*
* @param app The app to get patches for.
* @param version The version of the app to get patches for.
* @param universal Whether to show patches that are compatible with all apps.
*
* @return The patches.
*/
internal fun getPatches(
app: String? = null,
version: String? = null,
universal: Boolean = true,
) = if (app != null) {
patchSetRepository.patchSet.filter { patch ->
patch.compatiblePackages?.any { pkg ->
pkg.name == app && (version == null || pkg.versions?.contains(version) ?: false)
} ?: universal
}
} else {
patchSetRepository.patchSet.filter { patch ->
patch.compatiblePackages != null || universal
}
}.map { patch ->
Patch(
patch.name!!,
patch.description,
patch.use,
patch.compatiblePackages?.associate { pkg -> pkg.name to pkg.versions },
)
}.toSet()
/**
* Patch an app.
* Due to the likely-hood, that patches for the same app have the same name, duplicates are unhandled.
*
* @param patchNames The names of the patches to apply.
* @param multithreading Whether to use multi-threading for dex file writing.
* @param apkFile The APK file to patch.
*/
internal suspend fun patch(
patchNames: Set<String>,
multithreading: Boolean = false,
apkFile: File,
) = Patcher(
PatcherConfig(
apkFile = apkFile,
temporaryFilesPath = storageRepository.temporaryFilesPath,
aaptBinaryPath = storageRepository.aaptBinaryPath?.absolutePath,
frameworkFileDirectory = storageRepository.temporaryFilesPath.absolutePath,
multithreadingDexFileWriter = multithreading,
),
).use { patcher ->
val packageName = patcher.context.packageMetadata.packageName
patcher.apply {
acceptPatches(
patchSetRepository.patchSet.filter { patch ->
patch.name in patchNames && patch.compatiblePackages?.any { it.name == packageName } ?: true
}.toSet(),
)
// TODO: Only accept integrations from patch bundles that contain selected patches.
acceptIntegrations(
storageRepository.patchBundles.values.mapNotNull {
it.patchBundleIntegrationsFile
}.toSet(),
)
}
patcher.apply(false).collect { patchResult ->
patchResult.exception?.let {
StringWriter().use { writer ->
it.printStackTrace(PrintWriter(writer))
logger.severe("${patchResult.patch.name} failed:\n$writer")
}
} ?: logger.info("${patchResult.patch.name} succeeded")
}
patcher.get()
}.let { patcherResult ->
apkFile.copyTo(storageRepository.unsignedApkFilePath, overwrite = true).apply {
patcherResult.applyTo(this)
}
}
/**
* Sign an APK.
*
* @param signer The signer to use.
* @param keyStorePassword The password of the keystore.
* @param keyStoreEntryAlias The alias of the keystore entry.
* @param keyStoreEntryPassword The password of the keystore entry.
*/
internal fun sign(
signer: String,
keyStorePassword: String?,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
) = ApkUtils.signApk(
storageRepository.unsignedApkFilePath,
storageRepository.outputFilePath,
signer,
ApkUtils.KeyStoreDetails(
storageRepository.keystoreFilePath,
keyStorePassword,
keyStoreEntryAlias,
keyStoreEntryPassword,
),
)
/**
* Install an APK.
*
* @param mount The package name to mount the APK to.
*/
internal suspend fun install(mount: String?) {
if (mount != null) {
if (installerRepository.mountInstaller == null) {
throw IllegalArgumentException("Mount installer not available")
}
installerRepository.mountInstaller!! to Installer.Apk(
storageRepository.unsignedApkFilePath,
packageName = mount,
)
} else {
installerRepository.installer to Installer.Apk(storageRepository.outputFilePath)
}.let { (installer, apk) ->
installer.install(apk)
}
}
/**
* Uninstall an APK.
*
* @param packageName The package name of the APK to uninstall.
* @param unmount Whether to uninstall a mounted APK.
*/
internal suspend fun uninstall(packageName: String, unmount: Boolean) = if (unmount) {
installerRepository.mountInstaller!!
} else {
installerRepository.installer
}.uninstall(packageName)
/**
* Get patch options from [PatchSetRepository.patchSet].
* The [app] parameter is necessary in case there are patches with the same name.
* Due to the likely-hood, that patches for the same app have the same name, duplicates are unhandled.
*
* @param patchName The name of the patch to get options for.
* @param app The app to get options for.
*
* @return The patch options for the patch.
*/
internal fun getPatchOptions(patchName: String, app: String) = patchSetRepository.patchSet.single { patch ->
patch.name == patchName && patch.compatiblePackages?.any { it.name == app } ?: true
}.options.map { (key, option) ->
Patch.PatchOption(
key,
option.default,
option.values,
option.title,
option.description,
option.required,
option.valueType,
)
}.toSet()
/**
* Set patch options.
* The [app] parameter is necessary in case there are patches with the same name.
* Due to the likely-hood, that patches for the same app have the same name, duplicates are unhandled.
*
* @param patchOptions The options to set.
* @param patchName The name of the patch to set options for.
* @param app The app to set options for.
*/
internal fun setPatchOptions(
patchOptions: Set<Patch.KeyValuePatchOption<*>>,
patchName: String,
app: String,
) = patchSetRepository.patchSet.single { patch ->
patch.name == patchName && patch.compatiblePackages?.any { it.name == app } ?: true
}.options.let { options ->
patchOptions.forEach { option ->
options[option.key] = option.value
}
}
/**
* Reset patch options and persist them to the storage.
*
* @param patchName The name of the patch to reset options for.
* @param app The app to reset options for.
*/
internal fun resetPatchOptions(patchName: String, app: String) {
patchSetRepository.patchSet.single { patch ->
patch.name == patchName && patch.compatiblePackages?.any { it.name == app } ?: true
}.options.forEach { (_, option) -> option.reset() }
}
internal fun deleteTemporaryFiles() = storageRepository.deleteTemporaryFiles()
}

View File

@@ -0,0 +1 @@
version=${projectVersion}

View File

@@ -249,7 +249,7 @@ public final class app/revanced/library/installation/installer/AdbInstallerResul
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
}
public final class app/revanced/library/installation/installer/AdbRootInstaller : app/revanced/library/installation/installer/RootInstaller {
public final class app/revanced/library/installation/installer/AdbMountInstaller : app/revanced/library/installation/installer/MountInstaller {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -297,18 +297,18 @@ public final class app/revanced/library/installation/installer/LocalInstallerSer
public fun onStartCommand (Landroid/content/Intent;II)I
}
public final class app/revanced/library/installation/installer/LocalRootInstaller : app/revanced/library/installation/installer/RootInstaller, java/io/Closeable {
public final class app/revanced/library/installation/installer/LocalMountInstaller : app/revanced/library/installation/installer/MountInstaller, java/io/Closeable {
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
}
public final class app/revanced/library/installation/installer/RootInstallation : app/revanced/library/installation/installer/Installation {
public final class app/revanced/library/installation/installer/MountInstallation : app/revanced/library/installation/installer/Installation {
public final fun getInstalledApkFilePath ()Ljava/lang/String;
public final fun getMounted ()Z
}
public abstract class app/revanced/library/installation/installer/RootInstaller : app/revanced/library/installation/installer/Installer {
public abstract class app/revanced/library/installation/installer/MountInstaller : app/revanced/library/installation/installer/Installer {
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -318,12 +318,12 @@ public abstract class app/revanced/library/installation/installer/RootInstaller
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/library/installation/installer/RootInstallerResult : java/lang/Enum {
public static final field FAILURE Lapp/revanced/library/installation/installer/RootInstallerResult;
public static final field SUCCESS Lapp/revanced/library/installation/installer/RootInstallerResult;
public final class app/revanced/library/installation/installer/MountInstallerResult : java/lang/Enum {
public static final field FAILURE Lapp/revanced/library/installation/installer/MountInstallerResult;
public static final field SUCCESS Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/RootInstallerResult;
public static fun values ()[Lapp/revanced/library/installation/installer/RootInstallerResult;
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun values ()[Lapp/revanced/library/installation/installer/MountInstallerResult;
}
public final class app/revanced/library/logging/Logger {

View File

@@ -225,7 +225,7 @@ public final class app/revanced/library/installation/installer/AdbInstallerResul
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
}
public final class app/revanced/library/installation/installer/AdbRootInstaller : app/revanced/library/installation/installer/RootInstaller {
public final class app/revanced/library/installation/installer/AdbMountInstaller : app/revanced/library/installation/installer/MountInstaller {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -249,12 +249,12 @@ public final class app/revanced/library/installation/installer/Installer$Apk {
public final fun getPackageName ()Ljava/lang/String;
}
public final class app/revanced/library/installation/installer/RootInstallation : app/revanced/library/installation/installer/Installation {
public final class app/revanced/library/installation/installer/MountInstallation : app/revanced/library/installation/installer/Installation {
public final fun getInstalledApkFilePath ()Ljava/lang/String;
public final fun getMounted ()Z
}
public abstract class app/revanced/library/installation/installer/RootInstaller : app/revanced/library/installation/installer/Installer {
public abstract class app/revanced/library/installation/installer/MountInstaller : app/revanced/library/installation/installer/Installer {
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -264,12 +264,12 @@ public abstract class app/revanced/library/installation/installer/RootInstaller
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/library/installation/installer/RootInstallerResult : java/lang/Enum {
public static final field FAILURE Lapp/revanced/library/installation/installer/RootInstallerResult;
public static final field SUCCESS Lapp/revanced/library/installation/installer/RootInstallerResult;
public final class app/revanced/library/installation/installer/MountInstallerResult : java/lang/Enum {
public static final field FAILURE Lapp/revanced/library/installation/installer/MountInstallerResult;
public static final field SUCCESS Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/RootInstallerResult;
public static fun values ()[Lapp/revanced/library/installation/installer/RootInstallerResult;
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun values ()[Lapp/revanced/library/installation/installer/MountInstallerResult;
}
public final class app/revanced/library/logging/Logger {

123
library/build.gradle.kts Normal file
View File

@@ -0,0 +1,123 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.library)
alias(libs.plugins.binary.compatibility.validator)
`maven-publish`
signing
}
kotlin {
jvm {
compilations.all {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
}
}
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
}
publishLibraryVariants("release")
}
sourceSets {
androidMain.dependencies {
implementation(libs.libsu.nio)
implementation(libs.libsu.service)
implementation(libs.core.ktx)
}
commonMain.dependencies {
implementation(libs.revanced.patcher)
implementation(libs.kotlin.reflect)
implementation(libs.jadb) // Fork with Shell v2 support.
implementation(libs.bcpkix.jdk15on)
implementation(libs.jackson.module.kotlin)
implementation(libs.apkzlib)
implementation(libs.apksig)
implementation(libs.guava)
}
commonTest.dependencies {
implementation(libs.revanced.patcher)
implementation(libs.kotlin.test.junit)
}
}
}
android {
namespace = "app.revanced.library"
compileSdk = 34
defaultConfig {
minSdk = 26
}
buildFeatures {
aidl = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
java {
targetCompatibility = JavaVersion.VERSION_11
}
publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-library")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
publications {
create<MavenPublication>("revanced-library-publication") {
version = project.version.toString()
pom {
name = "ReVanced Library"
description = "Library containing common utilities for ReVanced"
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-library.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
url = "https://github.com/revanced/revanced-library"
}
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications["revanced-library-publication"])
}

View File

@@ -3,15 +3,15 @@ package app.revanced.library.installation.installer
import android.content.Context
import app.revanced.library.installation.command.LocalShellCommandRunner
import app.revanced.library.installation.installer.Installer.Apk
import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException
import app.revanced.library.installation.installer.MountInstaller.NoRootPermissionException
import com.topjohnwu.superuser.ipc.RootService
import java.io.Closeable
/**
* [LocalRootInstaller] for installing and uninstalling [Apk] files locally with using root permissions by mounting.
* [LocalMountInstaller] for installing and uninstalling [Apk] files locally with using root permissions by mounting.
*
* @param context The [Context] to use for binding to the [RootService].
* @param onReady A callback to be invoked when [LocalRootInstaller] is ready to be used.
* @param onReady A callback to be invoked when [LocalMountInstaller] is ready to be used.
*
* @throws NoRootPermissionException If the device does not have root permission.
*
@@ -19,13 +19,13 @@ import java.io.Closeable
* @see LocalShellCommandRunner
*/
@Suppress("unused")
class LocalRootInstaller(
class LocalMountInstaller(
context: Context,
onReady: LocalRootInstaller.() -> Unit = {},
) : RootInstaller(
onReady: LocalMountInstaller.() -> Unit = {},
) : MountInstaller(
{ installer ->
LocalShellCommandRunner(context) {
(installer as LocalRootInstaller).onReady()
(installer as LocalMountInstaller).onReady()
}
},
),

View File

@@ -4,7 +4,7 @@ package app.revanced.library.adb
import app.revanced.library.adb.AdbManager.Apk
import app.revanced.library.installation.installer.AdbInstaller
import app.revanced.library.installation.installer.AdbRootInstaller
import app.revanced.library.installation.installer.AdbMountInstaller
import app.revanced.library.installation.installer.Constants.PLACEHOLDER
import app.revanced.library.installation.installer.Installer
import app.revanced.library.run
@@ -68,18 +68,18 @@ sealed class AdbManager private constructor(
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*/
@Deprecated("Use AdbRootInstaller instead.", ReplaceWith("AdbRootInstaller(deviceSerial)"))
@Deprecated("Use AdbMountInstaller instead.", ReplaceWith("AdbMountInstaller(deviceSerial)"))
class RootAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) {
override val installer = AdbRootInstaller(deviceSerial)
override val installer = AdbMountInstaller(deviceSerial)
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use AdbRootInstaller.install instead.")
@Deprecated("Use AdbMountInstaller.install instead.")
override fun install(apk: Apk) = suspend {
installer.install(Installer.Apk(apk.file, apk.packageName))
}
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use AdbRootInstaller.uninstall instead.")
@Deprecated("Use AdbMountInstaller.uninstall instead.")
override fun uninstall(packageName: String) = suspend {
installer.uninstall(packageName)
}
@@ -134,11 +134,11 @@ sealed class AdbManager private constructor(
} ?: "No ADB device found",
)
@Deprecated("Use RootInstaller.FailedToFindInstalledPackageException instead.")
@Deprecated("Use MountInstaller.FailedToFindInstalledPackageException instead.")
class FailedToFindInstalledPackageException internal constructor(packageName: String) :
Exception("Failed to find installed package \"$packageName\" because no activity was found")
@Deprecated("Use RootInstaller.PackageNameRequiredException instead.")
@Deprecated("Use MountInstaller.PackageNameRequiredException instead.")
class PackageNameRequiredException internal constructor() :
Exception("Package name is required")
}

View File

@@ -2,21 +2,21 @@ package app.revanced.library.installation.installer
import app.revanced.library.installation.command.AdbShellCommandRunner
import app.revanced.library.installation.installer.Installer.Apk
import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException
import app.revanced.library.installation.installer.MountInstaller.NoRootPermissionException
/**
* [AdbRootInstaller] for installing and uninstalling [Apk] files with using ADB root permissions by mounting.
* [AdbMountInstaller] for installing and uninstalling [Apk] files with using ADB root permissions by mounting.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*
* @throws NoRootPermissionException If the device does not have root permission.
*
* @see RootInstaller
* @see MountInstaller
* @see AdbShellCommandRunner
*/
class AdbRootInstaller(
class AdbMountInstaller(
deviceSerial: String? = null,
) : RootInstaller({ AdbShellCommandRunner(deviceSerial) }) {
) : MountInstaller({ AdbShellCommandRunner(deviceSerial) }) {
init {
logger.fine("Connected to $deviceSerial")
}

View File

@@ -1,14 +1,14 @@
package app.revanced.library.installation.installer
/**
* [RootInstallation] of the apk file that is mounted to [installedApkFilePath] with root permissions.
* [MountInstallation] of the apk file that is mounted to [installedApkFilePath] with root permissions.
*
* @param installedApkFilePath The installed apk file path or null if the apk is not installed.
* @param apkFilePath The mounting apk file path.
* @param mounted Whether the apk is mounted to [installedApkFilePath].
*/
@Suppress("MemberVisibilityCanBePrivate")
class RootInstallation internal constructor(
class MountInstallation internal constructor(
val installedApkFilePath: String?,
apkFilePath: String,
val mounted: Boolean,

View File

@@ -17,20 +17,20 @@ import app.revanced.library.installation.installer.Constants.TMP_FILE_PATH
import app.revanced.library.installation.installer.Constants.UMOUNT
import app.revanced.library.installation.installer.Constants.invoke
import app.revanced.library.installation.installer.Installer.Apk
import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException
import app.revanced.library.installation.installer.MountInstaller.NoRootPermissionException
import java.io.File
/**
* [RootInstaller] for installing and uninstalling [Apk] files using root permissions by mounting.
* [MountInstaller] for installing and uninstalling [Apk] files using root permissions by mounting.
*
* @param shellCommandRunnerSupplier A supplier for the [ShellCommandRunner] to use.
*
* @throws NoRootPermissionException If the device does not have root permission.
*/
@Suppress("MemberVisibilityCanBePrivate")
abstract class RootInstaller internal constructor(
shellCommandRunnerSupplier: (RootInstaller) -> ShellCommandRunner,
) : Installer<RootInstallerResult, RootInstallation>() {
abstract class MountInstaller internal constructor(
shellCommandRunnerSupplier: (MountInstaller) -> ShellCommandRunner,
) : Installer<MountInstallerResult, MountInstallation>() {
/**
* The command runner used to run commands on the device.
@@ -49,7 +49,7 @@ abstract class RootInstaller internal constructor(
*
* @throws PackageNameRequiredException If the [Apk] does not have a package name.
*/
override suspend fun install(apk: Apk): RootInstallerResult {
override suspend fun install(apk: Apk): MountInstallerResult {
logger.info("Installing ${apk.packageName} by mounting")
val packageName = apk.packageName?.also { it.assertInstalled() } ?: throw PackageNameRequiredException()
@@ -67,10 +67,10 @@ abstract class RootInstaller internal constructor(
DELETE(TMP_FILE_PATH)()
return RootInstallerResult.SUCCESS
return MountInstallerResult.SUCCESS
}
override suspend fun uninstall(packageName: String): RootInstallerResult {
override suspend fun uninstall(packageName: String): MountInstallerResult {
logger.info("Uninstalling $packageName by unmounting")
UMOUNT(packageName)()
@@ -81,16 +81,16 @@ abstract class RootInstaller internal constructor(
KILL(packageName)()
return RootInstallerResult.SUCCESS
return MountInstallerResult.SUCCESS
}
override suspend fun getInstallation(packageName: String): RootInstallation? {
override suspend fun getInstallation(packageName: String): MountInstallation? {
val patchedApkPath = MOUNTED_APK_PATH(packageName)
val patchedApkExists = EXISTS(patchedApkPath)().exitCode == 0
if (patchedApkExists) return null
return RootInstallation(
return MountInstallation(
INSTALLED_APK_PATH(packageName)().output.ifEmpty { null },
patchedApkPath,
MOUNT_GREP(patchedApkPath)().exitCode == 0,

View File

@@ -3,11 +3,11 @@ package app.revanced.library.installation.installer
import app.revanced.library.installation.installer.Installer.Apk
/**
* The result of installing or uninstalling an [Apk] with root permissions using [RootInstaller].
* The result of installing or uninstalling an [Apk] with root permissions using [MountInstaller].
*
* @see RootInstaller
* @see MountInstaller
*/
enum class RootInstallerResult {
enum class MountInstallerResult {
/**
* The result of installing an [Apk] successfully.
*/

View File

@@ -1,6 +1,3 @@
// TODO: Figure out why this causes problems.
rootProject.name = "revanced-library"
buildCache {
local {
isEnabled = "CI" !in System.getenv()
@@ -9,7 +6,10 @@ buildCache {
pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
google()
}
}
include(":library", ":library-networking")