From bc018a3b0f00bec06570dffa52003dc5bcf90170 Mon Sep 17 00:00:00 2001 From: Graham Date: Sun, 16 May 2021 13:53:41 +0100 Subject: [PATCH] Add initial JAGGRAB, JS5 and HTTP servers Signed-off-by: Graham --- game/build.gradle.kts | 8 ++ .../kotlin/org/openrs2/game/GameModule.kt | 17 ++- .../kotlin/org/openrs2/game/GameServer.kt | 12 +- .../org/openrs2/game/net/FileProvider.kt | 53 ++++++++ .../org/openrs2/game/net/NetworkService.kt | 62 +++++++++ .../openrs2/game/net/Rs2ChannelInitializer.kt | 24 ++++ .../game/net/http/HttpChannelHandler.kt | 52 +++++++ .../game/net/http/HttpChannelInitializer.kt | 27 ++++ .../game/net/jaggrab/JaggrabChannelHandler.kt | 34 +++++ .../openrs2/game/net/js5/Js5ChannelHandler.kt | 40 ++++++ .../org/openrs2/game/net/js5/Js5Client.kt | 43 ++++++ .../org/openrs2/game/net/js5/Js5Service.kt | 128 ++++++++++++++++++ .../game/net/login/LoginChannelHandler.kt | 79 +++++++++++ gradle/libs.versions.toml | 3 +- protocol/build.gradle.kts | 2 +- 15 files changed, 579 insertions(+), 5 deletions(-) create mode 100644 game/src/main/kotlin/org/openrs2/game/net/FileProvider.kt create mode 100644 game/src/main/kotlin/org/openrs2/game/net/NetworkService.kt create mode 100644 game/src/main/kotlin/org/openrs2/game/net/Rs2ChannelInitializer.kt create mode 100644 game/src/main/kotlin/org/openrs2/game/net/http/HttpChannelHandler.kt create mode 100644 game/src/main/kotlin/org/openrs2/game/net/http/HttpChannelInitializer.kt create mode 100644 game/src/main/kotlin/org/openrs2/game/net/jaggrab/JaggrabChannelHandler.kt create mode 100644 game/src/main/kotlin/org/openrs2/game/net/js5/Js5ChannelHandler.kt create mode 100644 game/src/main/kotlin/org/openrs2/game/net/js5/Js5Client.kt create mode 100644 game/src/main/kotlin/org/openrs2/game/net/js5/Js5Service.kt create mode 100644 game/src/main/kotlin/org/openrs2/game/net/login/LoginChannelHandler.kt diff --git a/game/build.gradle.kts b/game/build.gradle.kts index a3300bb4..f3439337 100644 --- a/game/build.gradle.kts +++ b/game/build.gradle.kts @@ -11,7 +11,15 @@ application { dependencies { api(libs.clikt) + implementation(projects.buffer) + implementation(projects.cache) + implementation(projects.conf) implementation(projects.inject) + implementation(projects.net) + implementation(projects.protocol) + implementation(projects.util) + implementation(libs.guava) + implementation(libs.netty.codec.http) } publishing { diff --git a/game/src/main/kotlin/org/openrs2/game/GameModule.kt b/game/src/main/kotlin/org/openrs2/game/GameModule.kt index 7149ec40..68e65319 100644 --- a/game/src/main/kotlin/org/openrs2/game/GameModule.kt +++ b/game/src/main/kotlin/org/openrs2/game/GameModule.kt @@ -1,9 +1,24 @@ package org.openrs2.game +import com.google.common.util.concurrent.Service import com.google.inject.AbstractModule +import com.google.inject.multibindings.Multibinder +import org.openrs2.buffer.BufferModule +import org.openrs2.cache.CacheModule +import org.openrs2.conf.ConfigModule +import org.openrs2.game.net.NetworkService +import org.openrs2.game.net.js5.Js5Service +import org.openrs2.net.NetworkModule public object GameModule : AbstractModule() { override fun configure() { - // empty + install(BufferModule) + install(CacheModule) + install(ConfigModule) + install(NetworkModule) + + val binder = Multibinder.newSetBinder(binder(), Service::class.java) + binder.addBinding().to(Js5Service::class.java) + binder.addBinding().to(NetworkService::class.java) } } diff --git a/game/src/main/kotlin/org/openrs2/game/GameServer.kt b/game/src/main/kotlin/org/openrs2/game/GameServer.kt index 18687559..d733b121 100644 --- a/game/src/main/kotlin/org/openrs2/game/GameServer.kt +++ b/game/src/main/kotlin/org/openrs2/game/GameServer.kt @@ -1,10 +1,18 @@ package org.openrs2.game +import com.google.common.util.concurrent.Service +import com.google.common.util.concurrent.ServiceManager +import javax.inject.Inject import javax.inject.Singleton @Singleton -public class GameServer { +public class GameServer @Inject constructor( + services: Set +) { + private val serviceManager = ServiceManager(services) + public fun run() { - TODO() + serviceManager.startAsync().awaitHealthy() + serviceManager.awaitStopped() } } diff --git a/game/src/main/kotlin/org/openrs2/game/net/FileProvider.kt b/game/src/main/kotlin/org/openrs2/game/net/FileProvider.kt new file mode 100644 index 00000000..dfd1b3d6 --- /dev/null +++ b/game/src/main/kotlin/org/openrs2/game/net/FileProvider.kt @@ -0,0 +1,53 @@ +package org.openrs2.game.net + +import io.netty.channel.DefaultFileRegion +import io.netty.channel.FileRegion +import java.nio.channels.FileChannel +import java.nio.file.Files +import java.nio.file.Path +import javax.inject.Singleton + +@Singleton +public class FileProvider { + public fun get(uri: String): FileRegion? { + if (!uri.startsWith("/")) { + return null + } + + var path = ROOT.resolve(uri.substring(1)).toAbsolutePath().normalize() + if (!path.startsWith(ROOT)) { + return null + } + + if (!Files.exists(path)) { + path = stripChecksum(path) + } + + if (!Files.isRegularFile(path)) { + return null + } + + val channel = FileChannel.open(path) + return DefaultFileRegion(channel, 0, channel.size()) + } + + private fun stripChecksum(path: Path): Path { + val name = path.fileName.toString() + + val extensionIndex = name.lastIndexOf('.') + if (extensionIndex == -1) { + return path + } + + val checksumIndex = name.lastIndexOf('_', extensionIndex) + if (checksumIndex == -1) { + return path + } + + return path.resolveSibling(name.substring(0, checksumIndex) + name.substring(extensionIndex)) + } + + private companion object { + private val ROOT = Path.of("nonfree/var/cache/client").toAbsolutePath().normalize() + } +} diff --git a/game/src/main/kotlin/org/openrs2/game/net/NetworkService.kt b/game/src/main/kotlin/org/openrs2/game/net/NetworkService.kt new file mode 100644 index 00000000..6d48b9cb --- /dev/null +++ b/game/src/main/kotlin/org/openrs2/game/net/NetworkService.kt @@ -0,0 +1,62 @@ +package org.openrs2.game.net + +import com.google.common.util.concurrent.AbstractService +import io.netty.channel.EventLoopGroup +import org.openrs2.game.net.http.HttpChannelInitializer +import org.openrs2.net.BootstrapFactory +import org.openrs2.net.asCompletableFuture +import java.util.concurrent.CompletableFuture +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +public class NetworkService @Inject constructor( + private val bootstrapFactory: BootstrapFactory, + private val httpInitializer: HttpChannelInitializer, + private val rs2Initializer: Rs2ChannelInitializer +) : AbstractService() { + private lateinit var group: EventLoopGroup + + override fun doStart() { + group = bootstrapFactory.createEventLoopGroup() + + val httpFuture = bootstrapFactory.createServerBootstrap(group) + .childHandler(httpInitializer) + .bind(HTTP_PORT) + .asCompletableFuture() + + val rs2Initializer = bootstrapFactory.createServerBootstrap(group) + .childHandler(rs2Initializer) + + val rs2PrimaryFuture = rs2Initializer.bind(RS2_PRIMARY_PORT) + .asCompletableFuture() + + val rs2SecondaryFuture = rs2Initializer.bind(RS2_SECONDARY_PORT) + .asCompletableFuture() + + CompletableFuture.allOf(httpFuture, rs2PrimaryFuture, rs2SecondaryFuture).handle { _, ex -> + if (ex != null) { + notifyFailed(ex) + } else { + notifyStarted() + } + } + } + + override fun doStop() { + group.shutdownGracefully().addListener { future -> + if (future.isSuccess) { + notifyStopped() + } else { + notifyFailed(future.cause()) + } + } + } + + private companion object { + // TODO(gpe): make these configurable + private const val RS2_PRIMARY_PORT = 40001 + private const val RS2_SECONDARY_PORT = 50001 + private const val HTTP_PORT = 7001 + } +} diff --git a/game/src/main/kotlin/org/openrs2/game/net/Rs2ChannelInitializer.kt b/game/src/main/kotlin/org/openrs2/game/net/Rs2ChannelInitializer.kt new file mode 100644 index 00000000..759a83de --- /dev/null +++ b/game/src/main/kotlin/org/openrs2/game/net/Rs2ChannelInitializer.kt @@ -0,0 +1,24 @@ +package org.openrs2.game.net + +import io.netty.channel.Channel +import io.netty.channel.ChannelInitializer +import org.openrs2.game.net.login.LoginChannelHandler +import org.openrs2.protocol.Protocol +import org.openrs2.protocol.Rs2Decoder +import org.openrs2.protocol.Rs2Encoder +import javax.inject.Inject +import javax.inject.Provider +import javax.inject.Singleton + +@Singleton +public class Rs2ChannelInitializer @Inject constructor( + private val handlerProvider: Provider +) : ChannelInitializer() { + override fun initChannel(ch: Channel) { + ch.pipeline().addLast( + Rs2Decoder(Protocol.LOGIN_UPSTREAM), + Rs2Encoder(Protocol.LOGIN_DOWNSTREAM), + handlerProvider.get() + ) + } +} diff --git a/game/src/main/kotlin/org/openrs2/game/net/http/HttpChannelHandler.kt b/game/src/main/kotlin/org/openrs2/game/net/http/HttpChannelHandler.kt new file mode 100644 index 00000000..660962d4 --- /dev/null +++ b/game/src/main/kotlin/org/openrs2/game/net/http/HttpChannelHandler.kt @@ -0,0 +1,52 @@ +package org.openrs2.game.net.http + +import io.netty.channel.ChannelFutureListener +import io.netty.channel.ChannelHandler +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.codec.http.DefaultHttpResponse +import io.netty.handler.codec.http.HttpHeaderNames +import io.netty.handler.codec.http.HttpHeaderValues +import io.netty.handler.codec.http.HttpRequest +import io.netty.handler.codec.http.HttpResponse +import io.netty.handler.codec.http.HttpResponseStatus +import io.netty.handler.codec.http.HttpVersion +import org.openrs2.game.net.FileProvider +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +@ChannelHandler.Sharable +public class HttpChannelHandler @Inject constructor( + private val fileProvider: FileProvider +) : SimpleChannelInboundHandler() { + override fun channelActive(ctx: ChannelHandlerContext) { + ctx.read() + } + + override fun channelRead0(ctx: ChannelHandlerContext, msg: HttpRequest) { + val file = fileProvider.get(msg.uri()) + if (file == null) { + ctx.write(createResponse(HttpResponseStatus.NOT_FOUND)).addListener(ChannelFutureListener.CLOSE) + return + } + + val response = createResponse(HttpResponseStatus.OK) + response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM) + response.headers().add(HttpHeaderNames.CONTENT_LENGTH, file.count()) + + ctx.write(response, ctx.voidPromise()) + ctx.write(file).addListener(ChannelFutureListener.CLOSE) + } + + override fun channelReadComplete(ctx: ChannelHandlerContext) { + ctx.flush() + } + + private fun createResponse(status: HttpResponseStatus): HttpResponse { + val response = DefaultHttpResponse(HttpVersion.HTTP_1_1, status) + response.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE) + response.headers().add(HttpHeaderNames.SERVER, "OpenRS2") + return response + } +} diff --git a/game/src/main/kotlin/org/openrs2/game/net/http/HttpChannelInitializer.kt b/game/src/main/kotlin/org/openrs2/game/net/http/HttpChannelInitializer.kt new file mode 100644 index 00000000..2b77fd23 --- /dev/null +++ b/game/src/main/kotlin/org/openrs2/game/net/http/HttpChannelInitializer.kt @@ -0,0 +1,27 @@ +package org.openrs2.game.net.http + +import io.netty.channel.Channel +import io.netty.channel.ChannelInitializer +import io.netty.handler.codec.http.HttpObjectAggregator +import io.netty.handler.codec.http.HttpRequestDecoder +import io.netty.handler.codec.http.HttpResponseEncoder +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +public class HttpChannelInitializer @Inject constructor( + private val handler: HttpChannelHandler +) : ChannelInitializer() { + override fun initChannel(ch: Channel) { + ch.pipeline().addLast( + HttpRequestDecoder(), + HttpResponseEncoder(), + HttpObjectAggregator(MAX_CONTENT_LENGTH), + handler + ) + } + + private companion object { + private const val MAX_CONTENT_LENGTH = 65536 + } +} diff --git a/game/src/main/kotlin/org/openrs2/game/net/jaggrab/JaggrabChannelHandler.kt b/game/src/main/kotlin/org/openrs2/game/net/jaggrab/JaggrabChannelHandler.kt new file mode 100644 index 00000000..e80d8115 --- /dev/null +++ b/game/src/main/kotlin/org/openrs2/game/net/jaggrab/JaggrabChannelHandler.kt @@ -0,0 +1,34 @@ +package org.openrs2.game.net.jaggrab + +import io.netty.channel.ChannelFutureListener +import io.netty.channel.ChannelHandler +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import org.openrs2.game.net.FileProvider +import org.openrs2.protocol.jaggrab.JaggrabRequest +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +@ChannelHandler.Sharable +public class JaggrabChannelHandler @Inject constructor( + private val fileProvider: FileProvider +) : SimpleChannelInboundHandler() { + override fun handlerAdded(ctx: ChannelHandlerContext) { + ctx.read() + } + + override fun channelRead0(ctx: ChannelHandlerContext, msg: JaggrabRequest) { + val file = fileProvider.get(msg.path) + if (file == null) { + ctx.close() + return + } + + ctx.write(file).addListener(ChannelFutureListener.CLOSE) + } + + override fun channelReadComplete(ctx: ChannelHandlerContext) { + ctx.flush() + } +} diff --git a/game/src/main/kotlin/org/openrs2/game/net/js5/Js5ChannelHandler.kt b/game/src/main/kotlin/org/openrs2/game/net/js5/Js5ChannelHandler.kt new file mode 100644 index 00000000..7d154c41 --- /dev/null +++ b/game/src/main/kotlin/org/openrs2/game/net/js5/Js5ChannelHandler.kt @@ -0,0 +1,40 @@ +package org.openrs2.game.net.js5 + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import org.openrs2.protocol.js5.Js5Request +import org.openrs2.protocol.js5.XorEncoder +import javax.inject.Inject + +public class Js5ChannelHandler @Inject constructor( + private val service: Js5Service +) : SimpleChannelInboundHandler() { + private lateinit var client: Js5Client + + override fun handlerAdded(ctx: ChannelHandlerContext) { + client = Js5Client(ctx.read()) + } + + override fun channelRead0(ctx: ChannelHandlerContext, msg: Js5Request) { + when (msg) { + is Js5Request.Group -> service.push(client, msg) + is Js5Request.Rekey -> handleRekey(ctx, msg) + is Js5Request.Disconnect -> ctx.close() + } + } + + private fun handleRekey(ctx: ChannelHandlerContext, msg: Js5Request.Rekey) { + val encoder = ctx.pipeline().get(XorEncoder::class.java) + encoder.key = msg.key + } + + override fun channelReadComplete(ctx: ChannelHandlerContext) { + service.readIfNotFull(client) + } + + override fun channelWritabilityChanged(ctx: ChannelHandlerContext) { + if (ctx.channel().isWritable) { + service.notifyIfNotEmpty(client) + } + } +} diff --git a/game/src/main/kotlin/org/openrs2/game/net/js5/Js5Client.kt b/game/src/main/kotlin/org/openrs2/game/net/js5/Js5Client.kt new file mode 100644 index 00000000..2f8bdd99 --- /dev/null +++ b/game/src/main/kotlin/org/openrs2/game/net/js5/Js5Client.kt @@ -0,0 +1,43 @@ +package org.openrs2.game.net.js5 + +import io.netty.channel.ChannelHandlerContext +import org.openrs2.protocol.js5.Js5Request + +public class Js5Client( + public val ctx: ChannelHandlerContext +) { + private val urgent = ArrayDeque() + private val prefetch = ArrayDeque() + + public fun push(request: Js5Request.Group) { + if (request.prefetch) { + prefetch += request + } else { + urgent += request + } + } + + public fun pop(): Js5Request.Group? { + val request = urgent.removeFirstOrNull() + if (request != null) { + return request + } + return prefetch.removeFirstOrNull() + } + + public fun isNotFull(): Boolean { + return urgent.size < MAX_QUEUE_SIZE && prefetch.size < MAX_QUEUE_SIZE + } + + public fun isNotEmpty(): Boolean { + return urgent.isNotEmpty() || prefetch.isNotEmpty() + } + + public fun isReady(): Boolean { + return ctx.channel().isWritable && isNotEmpty() + } + + private companion object { + private const val MAX_QUEUE_SIZE = 20 + } +} diff --git a/game/src/main/kotlin/org/openrs2/game/net/js5/Js5Service.kt b/game/src/main/kotlin/org/openrs2/game/net/js5/Js5Service.kt new file mode 100644 index 00000000..6dabb0a2 --- /dev/null +++ b/game/src/main/kotlin/org/openrs2/game/net/js5/Js5Service.kt @@ -0,0 +1,128 @@ +package org.openrs2.game.net.js5 + +import com.google.common.util.concurrent.AbstractExecutionThreadService +import io.netty.buffer.ByteBufAllocator +import org.openrs2.buffer.use +import org.openrs2.cache.Js5Archive +import org.openrs2.cache.Js5Compression +import org.openrs2.cache.Js5CompressionType +import org.openrs2.cache.Js5MasterIndex +import org.openrs2.cache.Store +import org.openrs2.cache.VersionTrailer +import org.openrs2.protocol.js5.Js5Request +import org.openrs2.protocol.js5.Js5Response +import org.openrs2.util.collect.UniqueQueue +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +public class Js5Service @Inject constructor( + private val store: Store, + private val masterIndex: Js5MasterIndex, + private val alloc: ByteBufAllocator +) : AbstractExecutionThreadService() { + private val lock = Object() + private val clients = UniqueQueue() + + override fun run() { + while (true) { + var client: Js5Client + var request: Js5Request.Group + + synchronized(lock) { + while (true) { + if (!isRunning) { + return + } + + val next = clients.poll() + if (next == null) { + lock.wait() + continue + } + + client = next + request = client.pop() ?: continue + break + } + } + + serve(client, request) + } + } + + private fun serve(client: Js5Client, request: Js5Request.Group) { + val ctx = client.ctx + if (!ctx.channel().isActive) { + return + } + + val buf = if (request.archive == Js5Archive.ARCHIVESET && request.group == Js5Archive.ARCHIVESET) { + alloc.buffer().use { uncompressed -> + masterIndex.write(uncompressed) + + Js5Compression.compress(uncompressed, Js5CompressionType.UNCOMPRESSED).use { compressed -> + compressed.retain() + } + } + } else { + store.read(request.archive, request.group).use { buf -> + if (request.archive != Js5Archive.ARCHIVESET) { + VersionTrailer.strip(buf) + } + + buf.retain() + } + } + + val response = Js5Response(request.prefetch, request.archive, request.group, buf) + ctx.writeAndFlush(response, ctx.voidPromise()) + + synchronized(lock) { + if (client.isReady()) { + clients.add(client) + } + + if (client.isNotFull()) { + ctx.read() + } + } + } + + public fun push(client: Js5Client, request: Js5Request.Group) { + synchronized(lock) { + client.push(request) + + if (client.isReady()) { + clients.add(client) + lock.notifyAll() + } + + if (client.isNotFull()) { + client.ctx.read() + } + } + } + + public fun readIfNotFull(client: Js5Client) { + synchronized(lock) { + if (client.isNotFull()) { + client.ctx.read() + } + } + } + + public fun notifyIfNotEmpty(client: Js5Client) { + synchronized(lock) { + if (client.isNotEmpty()) { + lock.notifyAll() + } + } + } + + override fun triggerShutdown() { + synchronized(lock) { + lock.notifyAll() + } + } +} diff --git a/game/src/main/kotlin/org/openrs2/game/net/login/LoginChannelHandler.kt b/game/src/main/kotlin/org/openrs2/game/net/login/LoginChannelHandler.kt new file mode 100644 index 00000000..756a9985 --- /dev/null +++ b/game/src/main/kotlin/org/openrs2/game/net/login/LoginChannelHandler.kt @@ -0,0 +1,79 @@ +package org.openrs2.game.net.login + +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelFutureListener +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.codec.DelimiterBasedFrameDecoder +import io.netty.handler.codec.string.StringDecoder +import org.openrs2.buffer.copiedBuffer +import org.openrs2.game.net.jaggrab.JaggrabChannelHandler +import org.openrs2.game.net.js5.Js5ChannelHandler +import org.openrs2.protocol.Rs2Decoder +import org.openrs2.protocol.Rs2Encoder +import org.openrs2.protocol.jaggrab.JaggrabRequestDecoder +import org.openrs2.protocol.js5.Js5RequestDecoder +import org.openrs2.protocol.js5.Js5ResponseEncoder +import org.openrs2.protocol.js5.XorDecoder +import org.openrs2.protocol.login.LoginRequest +import org.openrs2.protocol.login.LoginResponse +import javax.inject.Inject +import javax.inject.Provider + +public class LoginChannelHandler @Inject constructor( + private val js5HandlerProvider: Provider, + private val jaggrabHandler: JaggrabChannelHandler +) : SimpleChannelInboundHandler() { + override fun channelActive(ctx: ChannelHandlerContext) { + ctx.read() + } + + override fun channelRead0(ctx: ChannelHandlerContext, msg: LoginRequest) { + when (msg) { + is LoginRequest.InitJs5RemoteConnection -> handleInitJs5RemoteConnection(ctx, msg) + is LoginRequest.InitJaggrabConnection -> handleInitJaggrabConnection(ctx) + } + } + + private fun handleInitJs5RemoteConnection(ctx: ChannelHandlerContext, msg: LoginRequest.InitJs5RemoteConnection) { + if (msg.build != BUILD) { + ctx.write(LoginResponse.ClientOutOfDate).addListener(ChannelFutureListener.CLOSE) + return + } + + ctx.pipeline().addLast( + XorDecoder(), + Js5RequestDecoder(), + Js5ResponseEncoder, + js5HandlerProvider.get() + ) + ctx.pipeline().remove(Rs2Decoder::class.java) + + ctx.write(LoginResponse.Js5Ok).addListener { + ctx.pipeline().remove(Rs2Encoder::class.java) + ctx.pipeline().remove(this) + } + } + + private fun handleInitJaggrabConnection(ctx: ChannelHandlerContext) { + ctx.pipeline().addLast( + DelimiterBasedFrameDecoder(JAGGRAB_MAX_FRAME_LENGTH, JAGGRAB_DELIMITER), + StringDecoder(Charsets.UTF_8), + JaggrabRequestDecoder, + jaggrabHandler + ) + ctx.pipeline().remove(Rs2Decoder::class.java) + ctx.pipeline().remove(Rs2Encoder::class.java) + ctx.pipeline().remove(this) + } + + override fun channelReadComplete(ctx: ChannelHandlerContext) { + ctx.flush() + } + + private companion object { + private const val BUILD = 550 + private const val JAGGRAB_MAX_FRAME_LENGTH = 4096 + private val JAGGRAB_DELIMITER = Unpooled.unreleasableBuffer(copiedBuffer("\n\n")) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 556b73cb..d9e71c8f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -51,7 +51,8 @@ ktor-thymeleaf = { module = "io.ktor:ktor-thymeleaf", version.ref = "ktor" } ktor-webjars = { module = "io.ktor:ktor-webjars", version.ref = "ktor" } logback = { module = "ch.qos.logback:logback-classic", version = "1.2.3" } netty-buffer = { module = "io.netty:netty-buffer", version.ref = "netty" } -netty-codec = { module = "io.netty:netty-codec", version.ref = "netty" } +netty-codec-core = { module = "io.netty:netty-codec", version.ref = "netty" } +netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty" } netty-transport = { module = "io.netty:netty-transport", version.ref = "netty" } openrs2-natives = { module = "org.openrs2:openrs2-natives-all", version = "3.2.0" } postgres = { module = "org.postgresql:postgresql", version = "42.2.20" } diff --git a/protocol/build.gradle.kts b/protocol/build.gradle.kts index 4730e30a..8da1693d 100644 --- a/protocol/build.gradle.kts +++ b/protocol/build.gradle.kts @@ -5,7 +5,7 @@ plugins { dependencies { api(projects.crypto) - api(libs.netty.codec) + api(libs.netty.codec.core) implementation(projects.buffer) }