Open-source multiplayer game server compatible with the RuneScape client
https://www.openrs2.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
111 lines
3.8 KiB
111 lines
3.8 KiB
package org.openrs2.protocol.js5.downstream
|
|
|
|
import io.netty.buffer.ByteBuf
|
|
import io.netty.channel.ChannelHandler
|
|
import io.netty.channel.ChannelHandlerContext
|
|
import io.netty.handler.codec.EncoderException
|
|
import io.netty.handler.codec.MessageToByteEncoder
|
|
import kotlin.math.min
|
|
|
|
@ChannelHandler.Sharable
|
|
public object Js5ResponseEncoder : MessageToByteEncoder<Js5Response>(Js5Response::class.java) {
|
|
override fun encode(ctx: ChannelHandlerContext, msg: Js5Response, out: ByteBuf) {
|
|
out.writeByte(msg.archive)
|
|
out.writeShort(msg.group)
|
|
|
|
if (!msg.data.isReadable) {
|
|
// TOOD(gpe): check if the entire container is well-formed?
|
|
throw EncoderException("Missing compression byte")
|
|
}
|
|
|
|
var compression = msg.data.readUnsignedByte().toInt()
|
|
if (msg.prefetch) {
|
|
compression = compression or 0x80
|
|
}
|
|
out.writeByte(compression)
|
|
|
|
out.writeBytes(msg.data, min(msg.data.readableBytes(), 508))
|
|
|
|
while (msg.data.isReadable) {
|
|
out.writeByte(0xFF)
|
|
out.writeBytes(msg.data, min(msg.data.readableBytes(), 511))
|
|
}
|
|
}
|
|
|
|
override fun allocateBuffer(ctx: ChannelHandlerContext, msg: Js5Response, preferDirect: Boolean): ByteBuf {
|
|
val dataLen = msg.data.readableBytes()
|
|
|
|
/*
|
|
* The naive code to estimate the length is:
|
|
*
|
|
* var len = 3 // for the archive/group header
|
|
* len += dataLen // for the data itself
|
|
*
|
|
* // first 0xFF marker
|
|
* if (dataLen > 509) { // compression byte plus the first 508 bytes of data
|
|
* len++
|
|
* dataLen -= 509
|
|
*
|
|
* // remaining 0xFF markers
|
|
* while (dataLen > 511) {
|
|
* len++
|
|
* dataLen -= 511
|
|
* }
|
|
* }
|
|
*
|
|
* We can simplify this by combining the if and while loop by adding an
|
|
* extra 2 bytes to dataLen, which ensures we add the first marker
|
|
* after 509 bytes (511-2) and then all subsequent markers after 511
|
|
* bytes:
|
|
*
|
|
* var len = 3
|
|
* len += dataLen
|
|
*
|
|
* dataLen += 2
|
|
*
|
|
* while (dataLen > 511) {
|
|
* len++
|
|
* dataLen -= 511
|
|
* }
|
|
*
|
|
* The while loop can be replaced with division:
|
|
*
|
|
* var len = 3
|
|
* len += dataLen
|
|
* len += divide(dataLen + 2, 511)
|
|
*
|
|
* divide() is almost but not quite standard division. We want
|
|
* divide(x, 511) to produce 0 if x=511, and only produce 1 if x=512.
|
|
* Similarly, it should produce 1 if x=1022, and only produce 1 if
|
|
* x=1023, and so on.
|
|
*
|
|
* This can be achieved by implementing divide(x, y) as
|
|
* ((x + (y - 1)) / y) - 1. So in our case, where y=511,
|
|
* ((x + 510) / y) - 1.
|
|
*
|
|
* Combined with the above, our length calculation becomes:
|
|
*
|
|
* var len = 3
|
|
* len += dataLen
|
|
* len += (dataLen + 2 + 510) / 511 - 1
|
|
*
|
|
* which we can simplify to:
|
|
*
|
|
* val len = 2 + dataLen + (512 + dataLen) / 511
|
|
*
|
|
* As this is confusing, testAllocateBuffer() has a test to ensure the
|
|
* length calculation is correct for all dataLens between 1 and 1534
|
|
* inclusive, which covers all lengths for 1, 2 and 3 block responses
|
|
* completely, and one length for a 4 block responses (as a boundary
|
|
* check).
|
|
*/
|
|
|
|
val len = 2 + dataLen + (512 + dataLen) / 511
|
|
|
|
return if (preferDirect) {
|
|
ctx.alloc().ioBuffer(len, len)
|
|
} else {
|
|
ctx.alloc().heapBuffer(len, len)
|
|
}
|
|
}
|
|
}
|
|
|