diff --git a/build.gradle b/build.gradle index 841c94d..04aabb3 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,13 @@ def ENV = System.getenv() version = mod_version group = maven_group +repositories { + maven { + name = "Andante's Maven" + url = "https://maven.andante.dev/releases/" + } +} + dependencies { minecraft "com.mojang:minecraft:$minecraft_version" mappings "net.fabricmc:yarn:$minecraft_version+build.$yarn_build:v2" @@ -19,6 +26,7 @@ dependencies { modImplementation "net.fabricmc:fabric-language-kotlin:$fabric_kotlin_version" include modImplementation ("com.github.yundom:kache:$kache_version") + include modImplementation ("net.mcbrawls:inject:$inject_version") } sourceSets { diff --git a/gradle.properties b/gradle.properties index 70c1a89..139e4dc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,8 @@ loader_version=0.16.7 fabric_version=0.106.0+1.21.1 fabric_kotlin_version=1.10.20+kotlin.1.9.24 kache_version=1.0.5 +inject_version=1.0 # Mod Properties -mod_version=2.11.1 +mod_version=2.12 maven_group=dev.andante diff --git a/src/main/kotlin/dev/andante/audience/AudienceInitializer.kt b/src/main/kotlin/dev/andante/audience/AudienceInitializer.kt index b7dd5f4..7c03ab1 100644 --- a/src/main/kotlin/dev/andante/audience/AudienceInitializer.kt +++ b/src/main/kotlin/dev/andante/audience/AudienceInitializer.kt @@ -1,5 +1,6 @@ package dev.andante.audience +import dev.andante.audience.resource.ResourcePackHandler import net.fabricmc.api.ModInitializer import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents import net.minecraft.server.MinecraftServer @@ -15,5 +16,7 @@ object AudienceInitializer : ModInitializer { override fun onInitialize() { // register event to capture server ServerLifecycleEvents.SERVER_STARTING.register { _minecraftServer = it } + + ResourcePackHandler.register() } } diff --git a/src/main/kotlin/dev/andante/audience/resource/ResourcePack.kt b/src/main/kotlin/dev/andante/audience/resource/ByteResourcePack.kt similarity index 90% rename from src/main/kotlin/dev/andante/audience/resource/ResourcePack.kt rename to src/main/kotlin/dev/andante/audience/resource/ByteResourcePack.kt index a2e76c2..5f311eb 100644 --- a/src/main/kotlin/dev/andante/audience/resource/ResourcePack.kt +++ b/src/main/kotlin/dev/andante/audience/resource/ByteResourcePack.kt @@ -5,7 +5,7 @@ import com.google.common.hash.Hashing /** * A complete resource pack. */ -data class ResourcePack( +data class ByteResourcePack( /** * All bytes composting the resource pack. */ @@ -20,7 +20,7 @@ data class ResourcePack( if (this === other) return true if (javaClass != other?.javaClass) return false - other as ResourcePack + other as ByteResourcePack return hash == other.hash } @@ -33,7 +33,6 @@ data class ResourcePack( /** * Hashes a byte array to a sha1 string. */ - @Suppress("DEPRECATION") private fun hashSha1(bytes: ByteArray): String { return Hashing.sha1().hashBytes(bytes).toString() } diff --git a/src/main/kotlin/dev/andante/audience/resource/ResourcePackHandler.kt b/src/main/kotlin/dev/andante/audience/resource/ResourcePackHandler.kt new file mode 100644 index 0000000..6a07aa2 --- /dev/null +++ b/src/main/kotlin/dev/andante/audience/resource/ResourcePackHandler.kt @@ -0,0 +1,56 @@ +package dev.andante.audience.resource + +import io.netty.channel.ChannelHandlerContext +import net.mcbrawls.inject.InjectorContext +import net.mcbrawls.inject.http.HttpByteBuf +import net.mcbrawls.inject.http.HttpRequest +import net.mcbrawls.inject.http.HttpInjector +import net.mcbrawls.inject.http.httpBuffer + +object ResourcePackHandler : HttpInjector() { + private val resourcePacks: MutableMap = mutableMapOf() + + private val SHA1_REGEX = Regex("^[a-fA-F0-9]{40}$") + + override fun isRelevant(ctx: InjectorContext, request: HttpRequest): Boolean = true + + override fun intercept(ctx: ChannelHandlerContext, request: HttpRequest): HttpByteBuf { + val response = ctx.httpBuffer() + + val path = request.requestURI.removePrefix("/") + if (!path.matches(SHA1_REGEX)) { + return response + } + + val pack = resourcePacks[path] ?: return response + response.writeStatusLine("1.1", 200, "OK") + + response.writeHeader("Content-Type", "application/zip") + response.writeBytes(pack) + + return response + } + + /** + * Adds a resource pack to be served. + */ + fun add(pack: ByteResourcePack) { + resourcePacks[pack.hash] = pack.bytes + } + + /** + * Removes a resource pack from service. + * @return whether a pack was removed + */ + fun remove(pack: ByteResourcePack): Boolean { + return remove(pack.hash) + } + + /** + * Removes a resource pack from service. + * @return whether a pack was removed + */ + fun remove(hash: String): Boolean { + return resourcePacks.remove(hash) != null + } +} diff --git a/src/main/kotlin/dev/andante/audience/resource/ResourcePackRequestHandler.kt b/src/main/kotlin/dev/andante/audience/resource/ResourcePackRequestHandler.kt deleted file mode 100644 index 56810a6..0000000 --- a/src/main/kotlin/dev/andante/audience/resource/ResourcePackRequestHandler.kt +++ /dev/null @@ -1,72 +0,0 @@ -package dev.andante.audience.resource - -import com.sun.net.httpserver.HttpExchange -import com.sun.net.httpserver.HttpHandler -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import org.apache.http.HttpHeaders -import org.apache.http.client.methods.HttpGet -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import java.io.IOException - -/** - * Handles requests for a [ResourcePackServer]. - */ -class ResourcePackRequestHandler( - /** - * The resource pack to be sent/received. - */ - private val resourcePack: ResourcePack -) : HttpHandler { - /** - * The length of the bytes in [resourcePack]. - */ - private val responseLength = resourcePack.bytes.size.toLong() - - @OptIn(DelicateCoroutinesApi::class) - override fun handle(exchange: HttpExchange?) { - // throw npe if null - exchange ?: run { return } - - // check for get - if (exchange.requestMethod != HttpGet.METHOD_NAME) { - exchange.close() - return - } - - // handle request - GlobalScope.async { - try { - sendResourcePack(exchange) - } catch (exception: Exception) { - onException(exception) - } finally { - exchange.close() - } - } - } - - /** - * Sends the resource pack as a response to the request. - */ - @Throws(IOException::class) - private fun sendResourcePack(exchange: HttpExchange) { - // write zip as response - exchange.responseHeaders[HttpHeaders.CONTENT_TYPE] = "application/zip" - exchange.sendResponseHeaders(200, responseLength) - exchange.responseBody.write(resourcePack.bytes) - } - - /** - * Handles exceptions caught during the exchange. - */ - private fun onException(exception: Exception) { - logger.error("Exception caught when serving a resource pack", exception) - } - - companion object { - private val logger: Logger = LoggerFactory.getLogger("Resource Pack Request Handler") - } -} diff --git a/src/main/kotlin/dev/andante/audience/resource/ResourcePackServer.kt b/src/main/kotlin/dev/andante/audience/resource/ResourcePackServer.kt deleted file mode 100644 index 3b5b88f..0000000 --- a/src/main/kotlin/dev/andante/audience/resource/ResourcePackServer.kt +++ /dev/null @@ -1,86 +0,0 @@ -package dev.andante.audience.resource - -import com.sun.net.httpserver.HttpServer -import net.minecraft.server.MinecraftServer.ServerResourcePackProperties -import net.minecraft.text.Text -import java.net.InetSocketAddress -import java.util.UUID - -/** - * A wrapper around an http server for serving resource packs. - */ -class ResourcePackServer( - /** - * The root address of this server. - */ - rootAddress: String, - - /** - * The port of this server. - */ - port: Int = 0 -) { - /** - * The socket address of this server. - */ - private val socketAddress = InetSocketAddress(rootAddress, port) - - /** - * The underlying http server. - */ - private val httpServer: HttpServer = HttpServer.create(socketAddress, 0) - - /** - * The compiled root address. - */ - val address = "http://$rootAddress:$port" - - /** - * The port of this server. - */ - val port = socketAddress.port - - /** - * Starts the http server. - */ - fun startServer() { - httpServer.start() - } - - /** - * Stops the http server. - */ - fun stopServer(delay: Int = 0) { - httpServer.stop(delay) - } - - /** - * Registers a resource pack to be handled by this server. - */ - fun registerResourcePack(resourcePack: ResourcePack): ServerResourcePackProperties { - val hash = resourcePack.hash - httpServer.createContext("/$hash", ResourcePackRequestHandler(resourcePack)) - return createResourcePackProperties(resourcePack) - } - - /** - * Creates a resource pack properties object based on [resourcePack] and this server. - */ - fun createResourcePackProperties( - resourcePack: ResourcePack, - required: Boolean = true, - prompt: Text? = null - ): ServerResourcePackProperties { - val url = getUrl(resourcePack) - return ServerResourcePackProperties(UUID.nameUUIDFromBytes(resourcePack.bytes), url, resourcePack.hash, required, prompt) - } - - /** - * Gets the address of the given [resourcePack]. - * @return a url string - */ - fun getUrl(resourcePack: ResourcePack): String { - val hash = resourcePack.hash - return "$address/$hash" - } -} diff --git a/src/test/kotlin/dev/andante/audience/test/AudienceTest.kt b/src/test/kotlin/dev/andante/audience/test/AudienceTest.kt index 1c35d8c..ab11f03 100644 --- a/src/test/kotlin/dev/andante/audience/test/AudienceTest.kt +++ b/src/test/kotlin/dev/andante/audience/test/AudienceTest.kt @@ -5,8 +5,7 @@ import com.mojang.serialization.JsonOps import dev.andante.audience.Audience import dev.andante.audience.player.PlayerSet import dev.andante.audience.player.StandalonePlayerReference -import dev.andante.audience.resource.ResourcePack -import dev.andante.audience.resource.ResourcePackServer +import dev.andante.audience.resource.ByteResourcePack import net.fabricmc.api.ModInitializer import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents @@ -41,7 +40,7 @@ object AudienceTest : ModInitializer { throw exception } - val resourcePack = ResourcePack(byteArray) + val resourcePack = ByteResourcePack(byteArray) val otherByteArray = try { Path.of("resources2.zip").readBytes() @@ -50,7 +49,7 @@ object AudienceTest : ModInitializer { throw exception } - val otherResourcePack = ResourcePack(otherByteArray) + val otherResourcePack = ByteResourcePack(otherByteArray) val resourcePackServer = ResourcePackServer("localhost", 25566).apply { registerResourcePack(resourcePack)