mirror of
https://github.com/ReVanced/revanced-library.git
synced 2026-01-26 04:31:04 +00:00
feat: Add networking module
This commit is contained in:
143
library-networking/api/library-networking.api
Normal file
143
library-networking/api/library-networking.api
Normal 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;
|
||||
}
|
||||
|
||||
97
library-networking/build.gradle.kts
Normal file
97
library-networking/build.gradle.kts
Normal 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"])
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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())) }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
version=${projectVersion}
|
||||
Reference in New Issue
Block a user