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.
 
 
 
 
openrs2/cache/src/main/java/dev/openrs2/cache/Js5Compression.kt

123 lines
3.9 KiB

package dev.openrs2.cache
import dev.openrs2.buffer.use
import dev.openrs2.crypto.XteaKey
import dev.openrs2.crypto.xteaDecrypt
import dev.openrs2.crypto.xteaEncrypt
import io.netty.buffer.ByteBuf
import io.netty.buffer.ByteBufInputStream
import io.netty.buffer.ByteBufOutputStream
public object Js5Compression {
public fun compress(input: ByteBuf, type: Js5CompressionType, key: XteaKey = XteaKey.ZERO): ByteBuf {
input.alloc().buffer().use { output ->
output.writeByte(type.ordinal)
if (type == Js5CompressionType.NONE) {
val len = input.readableBytes()
output.writeInt(len)
output.writeBytes(input)
if (!key.isZero) {
output.xteaEncrypt(5, len, key)
}
return output.retain()
}
val lenIndex = output.writerIndex()
output.writeZero(4)
val uncompressedLen = input.readableBytes()
output.writeInt(uncompressedLen)
val start = output.writerIndex()
type.createOutputStream(ByteBufOutputStream(output)).use { outputStream ->
input.readBytes(outputStream, uncompressedLen)
}
val len = output.writerIndex() - start
output.setInt(lenIndex, len)
if (!key.isZero) {
output.xteaEncrypt(5, len + 4, key)
}
return output.retain()
}
}
public fun compressBest(input: ByteBuf, enableLzma: Boolean = false, key: XteaKey = XteaKey.ZERO): ByteBuf {
var best = compress(input.slice(), Js5CompressionType.NONE, key)
try {
for (type in Js5CompressionType.values()) {
if (type == Js5CompressionType.NONE || (type == Js5CompressionType.LZMA && !enableLzma)) {
continue
}
compress(input.slice(), type, key).use { output ->
if (output.readableBytes() < best.readableBytes()) {
best.release()
best = output.retain()
}
}
}
// consume all of input so this method is a drop-in replacement for compress()
input.skipBytes(input.readableBytes())
return best.retain()
} finally {
best.release()
}
}
public fun uncompress(input: ByteBuf, key: XteaKey = XteaKey.ZERO): ByteBuf {
val typeId = input.readUnsignedByte().toInt()
val type = Js5CompressionType.fromOrdinal(typeId)
require(type != null) {
"Invalid compression type: $typeId"
}
val len = input.readInt()
require(len >= 0) {
"Length is negative: $len"
}
if (type == Js5CompressionType.NONE) {
input.readBytes(len).use { output ->
if (!key.isZero) {
output.xteaDecrypt(0, len, key)
}
return output.retain()
}
}
decrypt(input, len + 4, key).use { plaintext ->
val uncompressedLen = plaintext.readInt()
require(uncompressedLen >= 0) {
"Uncompressed length is negative: $uncompressedLen"
}
plaintext.alloc().buffer(uncompressedLen, uncompressedLen).use { output ->
type.createInputStream(ByteBufInputStream(plaintext, len), uncompressedLen).use { inputStream ->
output.writeBytes(inputStream, uncompressedLen)
}
return output.retain()
}
}
}
private fun decrypt(buf: ByteBuf, len: Int, key: XteaKey): ByteBuf {
if (key.isZero) {
return buf.readRetainedSlice(len)
}
buf.readBytes(len).use { output ->
output.xteaDecrypt(0, len, key)
return output.retain()
}
}
}