forked from openrs2/openrs2
parent
65b3e1315a
commit
bc018a3b0f
@ -1,9 +1,24 @@ |
|||||||
package org.openrs2.game |
package org.openrs2.game |
||||||
|
|
||||||
|
import com.google.common.util.concurrent.Service |
||||||
import com.google.inject.AbstractModule |
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() { |
public object GameModule : AbstractModule() { |
||||||
override fun configure() { |
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) |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,10 +1,18 @@ |
|||||||
package org.openrs2.game |
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 |
import javax.inject.Singleton |
||||||
|
|
||||||
@Singleton |
@Singleton |
||||||
public class GameServer { |
public class GameServer @Inject constructor( |
||||||
|
services: Set<Service> |
||||||
|
) { |
||||||
|
private val serviceManager = ServiceManager(services) |
||||||
|
|
||||||
public fun run() { |
public fun run() { |
||||||
TODO() |
serviceManager.startAsync().awaitHealthy() |
||||||
|
serviceManager.awaitStopped() |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -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() |
||||||
|
} |
||||||
|
} |
@ -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 |
||||||
|
} |
||||||
|
} |
@ -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<LoginChannelHandler> |
||||||
|
) : ChannelInitializer<Channel>() { |
||||||
|
override fun initChannel(ch: Channel) { |
||||||
|
ch.pipeline().addLast( |
||||||
|
Rs2Decoder(Protocol.LOGIN_UPSTREAM), |
||||||
|
Rs2Encoder(Protocol.LOGIN_DOWNSTREAM), |
||||||
|
handlerProvider.get() |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -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<HttpRequest>() { |
||||||
|
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 |
||||||
|
} |
||||||
|
} |
@ -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<Channel>() { |
||||||
|
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 |
||||||
|
} |
||||||
|
} |
@ -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<JaggrabRequest>() { |
||||||
|
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() |
||||||
|
} |
||||||
|
} |
@ -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<Js5Request>() { |
||||||
|
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) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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<Js5Request.Group>() |
||||||
|
private val prefetch = ArrayDeque<Js5Request.Group>() |
||||||
|
|
||||||
|
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 |
||||||
|
} |
||||||
|
} |
@ -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<Js5Client>() |
||||||
|
|
||||||
|
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() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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<Js5ChannelHandler>, |
||||||
|
private val jaggrabHandler: JaggrabChannelHandler |
||||||
|
) : SimpleChannelInboundHandler<LoginRequest>() { |
||||||
|
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")) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue