Introduce codec
This commit is contained in:
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user