feat: Add networking module

This commit is contained in:
oSumAtrIX
2024-04-07 22:33:03 +02:00
parent fe3e1c9dc8
commit 42de60e95b
58 changed files with 1880 additions and 189 deletions

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}