Introduce codec

This commit is contained in:
2026-09-04 16:55:56 +08:00
parent 1d6012ea23
commit 5e4f877a4a
22 files changed
+375 -101

No files matched your search

@@ -27,6 +27,7 @@ public expect class _Buffer {
public fun hasRemaining(): Boolean
public fun close()
public val size: Int
public val remaining: Long
}
public fun ByteArray.wrap(): _Buffer = _Buffer(this)
@@ -0,0 +1,38 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package cn.rtast.libmc.common
public interface Encoder<in T> {
public fun encode(buffer: _Buffer, value: T)
public fun encodeToByteArray(value: T): ByteArray {
val buf = _Buffer()
encode(buf, value)
return buf.toByteArray()
}
}
public interface Decoder<out T> {
public fun decode(buffer: _Buffer): T
public fun decodeFromByteArray(bytes: ByteArray): T = decode(bytes.wrap())
}
public interface PacketCodec<T> : Encoder<T>, Decoder<T>
@Suppress("NOTHING_TO_INLINE")
public inline fun <T> _Buffer.write(value: T, encoder: Encoder<T>): Unit = encoder.encode(this, value)
@Suppress("NOTHING_TO_INLINE")
public inline fun <T> _Buffer.read(decoder: Decoder<T>): T = decoder.decode(this)
public fun _Buffer.writeBuffer(source: _Buffer, length: Long = source.remaining) {
if (length <= 0) return
val bytes = source.readBytes(length.toInt())
this.writeBytes(bytes)
}
@@ -0,0 +1,10 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package cn.rtast.libmc.common
internal expect fun ByteArray.zlibDecompress(): ByteArray
@@ -94,4 +94,5 @@ public actual class _Buffer {
public actual fun close(): Unit = outStream.close()
public actual val size: Int get() = outStream.size()
public actual val remaining: Long get() = (outStream.size() - readOffset).coerceAtLeast(0).toLong()
}
@@ -0,0 +1,44 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package cn.rtast.libmc.common
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.util.zip.GZIPInputStream
import java.util.zip.Inflater
public actual fun ByteArray.zlibDecompress(): ByteArray {
if (isEmpty()) return byteArrayOf()
val isGzip = size >= 2 && this[0] == 0x1F.toByte() && this[1] == 0x8B.toByte()
return if (isGzip) this.gzipDecompress() else {
val inflater = Inflater(false)
val outputStream = ByteArrayOutputStream(this.size)
val buffer = ByteArray(1024)
inflater.setInput(this)
while (!inflater.finished()) {
val length = inflater.inflate(buffer)
if (length > 0) outputStream.write(buffer, 0, length)
}
inflater.end()
outputStream.toByteArray()
}
}
private fun ByteArray.gzipDecompress(): ByteArray {
if (isEmpty()) return byteArrayOf()
ByteArrayInputStream(this).use { bais ->
GZIPInputStream(bais).use { gzis ->
val outputStream = ByteArrayOutputStream(this.size * 2)
val buffer = ByteArray(1024)
var len: Int
while (gzis.read(buffer).also { len = it } != -1) {
outputStream.write(buffer, 0, len)
}
return outputStream.toByteArray()
}
}
}
@@ -7,6 +7,7 @@
package cn.rtast.libmc.common
import io.ktor.utils.io.bits.*
import io.ktor.utils.io.core.*
import kotlinx.io.Buffer
import kotlinx.io.readByteArray
@@ -68,6 +69,6 @@ public actual class _Buffer {
public actual fun hasRemaining(): Boolean = !_delegateBuf.exhausted()
public actual fun close(): Unit = _delegateBuf.close()
public actual val size: Int
get() = _delegateBuf.size.toInt()
public actual val size: Int get() = _delegateBuf.size.toInt()
public actual val remaining: Long get() = _delegateBuf.remaining
}
@@ -0,0 +1,68 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
@file:OptIn(ExperimentalForeignApi::class)
package cn.rtast.libmc.common
import kotlinx.cinterop.*
import platform.zlib.*
private const val ENABLE_ZLIB_GZIP_HEADER = 15 + 32
public actual fun ByteArray.zlibDecompress(): ByteArray {
if (isEmpty()) return ByteArray(0)
return memScoped {
val stream = alloc<z_stream>()
stream.zalloc = null
stream.zfree = null
stream.opaque = null
val initResult = inflateInit2_(
stream.ptr,
ENABLE_ZLIB_GZIP_HEADER,
ZLIB_VERSION,
sizeOf<z_stream>().toInt()
)
check(initResult == Z_OK) { "inflateInit2_ failed with code: $initResult" }
val inputPinned = this@zlibDecompress.pin()
try {
stream.next_in = inputPinned.addressOf(0).reinterpret()
stream.avail_in = this@zlibDecompress.size.toUInt()
val bufferSize = 4096
val tempBuffer = ByteArray(bufferSize)
val tempPinned = tempBuffer.pin()
val output = ArrayList<Byte>(this@zlibDecompress.size * 3)
try {
var result: Int
do {
stream.next_out = tempPinned.addressOf(0).reinterpret()
stream.avail_out = bufferSize.toUInt()
result = inflate(stream.ptr, Z_NO_FLUSH)
check(result == Z_OK || result == Z_STREAM_END) { "inflate error: $result" }
val bytesDecompressed = bufferSize - stream.avail_out.toInt()
for (i in 0 until bytesDecompressed) {
output.add(tempBuffer[i])
}
if (result == Z_STREAM_END) break
} while (stream.avail_in > 0u || stream.avail_out == 0u)
} finally {
tempPinned.unpin()
}
inflateEnd(stream.ptr)
return@memScoped output.toByteArray()
} finally {
inputPinned.unpin()
}
}
}