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.http.HttpObjectAggregator import io.netty.handler.codec.http.HttpRequestDecoder import io.netty.handler.codec.http.HttpResponseEncoder import io.netty.handler.codec.string.StringDecoder import io.netty.handler.timeout.IdleStateEvent import org.openrs2.buffer.copiedBuffer import org.openrs2.game.cluster.Cluster import org.openrs2.game.net.crossdomain.CrossDomainChannelHandler import org.openrs2.game.net.http.Http import org.openrs2.game.net.jaggrab.JaggrabChannelHandler import org.openrs2.game.net.js5.Js5ChannelHandler import org.openrs2.protocol.Protocol 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 org.openrs2.protocol.world.WorldListResponse import javax.inject.Inject import javax.inject.Provider public class LoginChannelHandler @Inject constructor( private val cluster: Cluster, private val js5HandlerProvider: Provider, private val jaggrabHandler: JaggrabChannelHandler ) : SimpleChannelInboundHandler(LoginRequest::class.java) { 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) is LoginRequest.RequestWorldList -> handleRequestWorldList(ctx, msg) is LoginRequest.InitCrossDomainConnection -> handleInitCrossDomainConnection(ctx) else -> Unit } } private fun handleInitJs5RemoteConnection(ctx: ChannelHandlerContext, msg: LoginRequest.InitJs5RemoteConnection) { val encoder = ctx.pipeline().get(Rs2Encoder::class.java) encoder.protocol = Protocol.JS5REMOTE_DOWNSTREAM 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 { future -> if (future.isSuccess) { ctx.pipeline().remove(encoder) 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) } private fun handleRequestWorldList(ctx: ChannelHandlerContext, msg: LoginRequest.RequestWorldList) { val (worlds, players) = cluster.getWorldList() var checksum = worlds.hashCode() /* * Fix a 1 in 2^32 chance that we'll fail to fetch the original world * list on startup. */ if (checksum == 0) { checksum = 1 } val worldList = if (checksum != msg.checksum) { WorldListResponse.WorldList(worlds, checksum) } else { null } val encoder = ctx.pipeline().get(Rs2Encoder::class.java) encoder.protocol = Protocol.WORLD_LIST_DOWNSTREAM ctx.write(WorldListResponse(worldList, players)).addListener(ChannelFutureListener.CLOSE) } private fun handleInitCrossDomainConnection(ctx: ChannelHandlerContext) { ctx.pipeline().addLast( HttpRequestDecoder(), HttpResponseEncoder(), HttpObjectAggregator(Http.MAX_CONTENT_LENGTH), CrossDomainChannelHandler ) ctx.fireChannelRead(Unpooled.wrappedBuffer(G)) ctx.pipeline().remove(Rs2Decoder::class.java) ctx.pipeline().remove(Rs2Encoder::class.java) ctx.pipeline().remove(this) } override fun channelReadComplete(ctx: ChannelHandlerContext) { ctx.flush() } override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { if (evt is IdleStateEvent) { ctx.close() } } 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")) private val G = byteArrayOf('G'.code.toByte()) } }