Embed AES/RSA/SHA-1 crypto implementation

This commit is contained in:
2026-09-09 01:23:47 +08:00
parent 74f9e6f529
commit c038082897
49 files changed
+931 -1107

No files matched your search

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
-10
View File
@@ -1,7 +1,5 @@
# libmc
[中文](zh/README-zh.md)
A lightweight, modern Minecraft client protocol library designed for Kotlin Native & JVM. The core protocol library
module relies on the following dependencies:
@@ -14,9 +12,6 @@ module relies on the following dependencies:
| Module Name | Required | Notes |
|:-------------|:------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| TCP Socket | Yes | The `protocol` module does not have a built-in TCP Socket implementation. [Implement TCP Socket](Impl-TCP-Socket.md) |
| AES-128-CFB8 | Conditional | Required only when logging into an `online-mode` server to encrypt/decrypt traffic. [Implement AES-128-CFB8](Impl-Crypto.md#AES-128-CFB8) |
| RSA 1024 | Conditional | Required only when logging into an `online-mode` server to encrypt the shared secret with the server's public key. [Implement RSA 1024](Impl-Crypto.md#RSA1024) |
| SHA1 | Conditional | Required only when logging into an `online-mode` server to compute the Server ID hash. [Implement SHA1](Impl-Crypto.md#SHA1) |
| HTTP Client | Conditional | Required only when logging into an `online-mode` server to send join request to mojang's session server. [Implement HTTP Client](Impl-HTTP-Client.md) |
# Get started
@@ -26,11 +21,6 @@ module relies on the following dependencies:
> `libmc-protocol` is current under development. It only supports the latest Minecraft version
> (Current supported Minecraft version: `26.2`, Protocol Version: `776`)
[Start using libmc-protocol](en/Get-started.md)
# Assemble all context APIs
[Assemble context APIs](en/Assemble-context.md)
## NBT & SNBT
-31
View File
@@ -1,31 +0,0 @@
# Assemble context API
```kotlin
public val CustomProtocolContext: ProtocolContextBuilder.() -> Unit = {
rsaEncryptor = RSA1024Encryptor { key, data -> ... }
sha1Hasher = Sha1Hasher { serverId, secretKey, publicKey -> ... }
cipherFactory = { key -> ... }
authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash -> ... }
socketEngine = MyCustomTCPSocketImpl()
}
```
> If using `DefaultProtocolContext` from `libmc-protocol-context` as the default context implementation,
> please refer to [Implementing TCPSocket](Impl-TCP-Socket.md)
# Modify exists context API implementation
```kotlin
fun main() {
val myNewProtocolContext = DefaultProtocolContext.withCustom {
rsaEncryptor = RSA1024Encryptor { key, data -> ... }
sha1Hasher = Sha1Hasher { serverId, secretKey, publicKey -> ... }
cipherFactory = { key -> ... }
authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash -> ... }
socketEngine = MyCustomTCPSocketImpl()
}
}
```
> Call `.withCustom` on a `ProtocolContextBuilder` to modify exists context implementation
-67
View File
@@ -1,67 +0,0 @@
# Before start
`AES-128-CFB8`, `RSA1024` and `SHA1` has been implemented in `libmc-protocol-context` module, it uses
`cryptography-kotlin`(and its platform based provider)
> All impl example below are based on Java's built-in security & cryptography API
# AES-128-CFB8
```kotlin
class JavaAesCipher(sharedKey: ByteArray) : NetworkCipher {
private val encryptCipher = Cipher.getInstance("AES/CFB8/NoPadding").apply {
init(Cipher.ENCRYPT_MODE, SecretKeySpec(sharedKey, "AES"), IvParameterSpec(sharedKey))
}
private val decryptCipher = Cipher.getInstance("AES/CFB8/NoPadding").apply {
init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedKey, "AES"), IvParameterSpec(sharedKey))
}
override fun encrypt(buffer: ByteArray, offset: Int, length: Int) {
encryptCipher.update(buffer, offset, length, buffer, offset)
}
override fun decrypt(buffer: ByteArray, offset: Int, length: Int) {
decryptCipher.update(buffer, offset, length, buffer, offset)
}
}
```
# RSA1024
```kotlin
private fun encrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray {
val keySpec = X509EncodedKeySpec(publicKeyBytes)
val keyFactory = KeyFactory.getInstance("RSA")
val publicKey = keyFactory.generatePublic(keySpec)
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
return cipher.doFinal(data)
}
public val CustomProtocolContext: ProtocolContextBuilder.() -> Unit = {
rsaEncryptor = RSA1024Encryptor { key: ByteArray, data: ByteArray -> encrypt(key, data) }
}
```
# SHA1
```kotlin
private fun minecraftServerIdHash(serverId: String, secretKey: ByteArray, publicKey: ByteArray): String {
val serverIdBytes: ByteArray = serverId.encodeToByteArray()
for (b in serverIdBytes) require((b.toInt() and 0xFF) <= 0x7F) { "serverId contains non-US-ASCII character" }
val data = ByteArray(serverIdBytes.size + secretKey.size + publicKey.size)
System.arraycopy(serverIdBytes, 0, data, 0, serverIdBytes.size)
System.arraycopy(secretKey, 0, data, serverIdBytes.size, secretKey.size)
System.arraycopy(publicKey, 0, data, serverIdBytes.size + secretKey.size, publicKey.size)
val digest = MessageDigest.getInstance("SHA-1")
val hash = digest.digest(data)
return BigInteger(hash).toString(16)
}
public val CustomProtocolContext: ProtocolContextBuilder.() -> Unit = {
sha1Hasher = Sha1Hasher { serverId: String, secretKey: ByteArray, publicKey: ByteArray ->
minecraftServerIdHash(serverId, secretKey, publicKey)
}
}
```
-31
View File
@@ -1,31 +0,0 @@
# 组合上下文API
```kotlin
public val CustomProtocolContext: ProtocolContextBuilder.() -> Unit = {
rsaEncryptor = RSA1024Encryptor { key, data -> ... }
sha1Hasher = Sha1Hasher { serverId, secretKey, publicKey -> ... }
cipherFactory = { key -> ... }
authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash -> ... }
socketEngine = MyCustomTCPSocketImpl()
}
```
> 如果使用`libmc-protocol-context`中的`DefaultProtocolContext`作为默认上下文实现,
> 请参阅[实现 TCPSocket](Impl-TCP-Socket-zh.md)
# 修改默认实现
```kotlin
fun main() {
val myNewProtocolContext = DefaultProtocolContext.withCustom {
rsaEncryptor = RSA1024Encryptor { key, data -> ... }
sha1Hasher = Sha1Hasher { serverId, secretKey, publicKey -> ... }
cipherFactory = { key -> ... }
authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash -> ... }
socketEngine = MyCustomTCPSocketImpl()
}
}
```
> 使用`.withCustom`来修改已有的上下文API实现
-79
View File
@@ -1,79 +0,0 @@
# 创建客户端
```kotlin
public fun main() = runBlocking {
val client = createMinecraftClient(
"127.0.0.1", 25566, "MyBot",
generateOfflineUuid("MyBot"),
accessToken = null,
context = DefaultProtocolContext.withCustom {
socketEngine = KtorNetworkEngine()
}
)
client.launch { client.connect() }
while (true) {
delay(5.seconds)
}
}
```
> 在上面的示例代码中, 创建了一个`MinecraftClient`, 这个客户端将会使用`MyBot`作为玩家名称连接到`127.0.0.1:25565`的`离线`
> 服务器, 并且将底层TCP Socket实现替换为了基于`ktor-network`实现的TCP Socket.
> (有关如何创建`SocketEngine`请参阅[实现TCP Socket](Impl-TCP-Socket-zh.md),
> 有关如何创建Context请参阅[需要实现的API](README-zh.md#需要实现的api))
> `MinecraftClient`实现了`CoroutineContext`, 使用`client.connect()`后将会切换到后台线程执行, 添加阻塞线程代码以免程序直接退出.
## 连接到在线服务器
```kotlin
private val accessToken = "eyJraWQiOiIw..."
public fun main() = runBlocking {
val client = createMinecraftClient(
// 其他参数
username = "RTAkland",
uuid = Uuid.parse("bb033844-e68e-4909-a636-1a5d1821ddc4"),
accessToken = accessToken,
// 其他参数
)
}
```
> 上面的示例代码中, 将会使用`RTAkland`作为玩家名称连接到服务器
### 快速获取AccessToken
打开[minecraft.net](https://minecraft.net)并登录后按下F12, 在Console内输入
```javascript
console.log(`; ${document.cookie}`.split('; bearer_token=').pop().split(';').shift())
```
> AccessToken的有效期为24小时
# 监听数据包
```kotlin
// 监听接收到的指定类型的数据包, 必须以Clientbound开头
client.onPacket<ClientboundLoginSuccessPacket> {
println(it)
}
// 监听所有接收到的数据包
client.on { packet, direction ->
println("$direction -> $packet")
}
// 监听被发送出去的数据包, 必须以Serverbound开头
cli.onSent<ServerboundPongConfigurationPacket> {
println(it)
}
```
# 发送数据包
```kotlin
// 必须以Serverbound开头的数据包才可以被发送, 否则将会抛出UnsupportedOperationException异常
client.networkChannel.sendPacket(
ServerboundChatCommandPacket(command = "say Hello from libmc")
)
```
-67
View File
@@ -1,67 +0,0 @@
# Before start
`libmc-protocol-context` 模块中默认实现了 `AES-128-CFB8`, `RSA1024` 以及 `SHA1`,
三种加密/签名算法都基于`cryptography-kotlin`及其对应平台的provider
> 下面给出的示例代码均基于Java内置的密码学API
# AES-128-CFB8
```kotlin
class JavaAesCipher(sharedKey: ByteArray) : NetworkCipher {
private val encryptCipher = Cipher.getInstance("AES/CFB8/NoPadding").apply {
init(Cipher.ENCRYPT_MODE, SecretKeySpec(sharedKey, "AES"), IvParameterSpec(sharedKey))
}
private val decryptCipher = Cipher.getInstance("AES/CFB8/NoPadding").apply {
init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedKey, "AES"), IvParameterSpec(sharedKey))
}
override fun encrypt(buffer: ByteArray, offset: Int, length: Int) {
encryptCipher.update(buffer, offset, length, buffer, offset)
}
override fun decrypt(buffer: ByteArray, offset: Int, length: Int) {
decryptCipher.update(buffer, offset, length, buffer, offset)
}
}
```
# RSA1024
```kotlin
private fun encrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray {
val keySpec = X509EncodedKeySpec(publicKeyBytes)
val keyFactory = KeyFactory.getInstance("RSA")
val publicKey = keyFactory.generatePublic(keySpec)
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
return cipher.doFinal(data)
}
public val CustomProtocolContext: ProtocolContextBuilder.() -> Unit = {
rsaEncryptor = RSA1024Encryptor { key: ByteArray, data: ByteArray -> encrypt(key, data) }
}
```
# SHA1
```kotlin
private fun minecraftServerIdHash(serverId: String, secretKey: ByteArray, publicKey: ByteArray): String {
val serverIdBytes: ByteArray = serverId.encodeToByteArray()
for (b in serverIdBytes) require((b.toInt() and 0xFF) <= 0x7F) { "serverId contains non-US-ASCII character" }
val data = ByteArray(serverIdBytes.size + secretKey.size + publicKey.size)
System.arraycopy(serverIdBytes, 0, data, 0, serverIdBytes.size)
System.arraycopy(secretKey, 0, data, serverIdBytes.size, secretKey.size)
System.arraycopy(publicKey, 0, data, serverIdBytes.size + secretKey.size, publicKey.size)
val digest = MessageDigest.getInstance("SHA-1")
val hash = digest.digest(data)
return BigInteger(hash).toString(16)
}
public val CustomProtocolContext: ProtocolContextBuilder.() -> Unit = {
sha1Hasher = Sha1Hasher { serverId: String, secretKey: ByteArray, publicKey: ByteArray ->
minecraftServerIdHash(serverId, secretKey, publicKey)
}
}
```
-45
View File
@@ -1,45 +0,0 @@
# 前言
`libmc-protocol-context` 中提供了默认HTTP客户端实现, 如果使用了此模块则不需要手动配置HTTP客户端认证部分
# 基于Ktor的HTTP客户端
```kotlin
private val httpClient = HttpClient()
public val CustomProtocolContext: ProtocolContextBuilder.() -> Unit = {
authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash ->
val status = httpClient.post(url) {
headers { header("Content-Type", "application/json") }
setBody("{\"accessToken\":\"$accessToken\", \"selectedProfile\":\"$uuid\", \"serverId\":\"$serverIdHash\"}")
}.status
require(status == HttpStatusCode.NoContent)
}
}
```
# 基于Java8 HttpURLConnection的HTTP客户端
```kotlin
private fun sendJoinRequest(url: String, accessToken: String, uuid: String, serverIdHash: String): Boolean {
val connection = (URL(url).openConnection() as HttpURLConnection).apply {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json")
doOutput = true
connectTimeout = 5000
readTimeout = 5000
}
val jsonPayload = """{"accessToken":"$accessToken","selectedProfile":"$uuid","serverId":"$serverIdHash"}"""
connection.outputStream.use { os -> os.write(jsonPayload.toByteArray(Charsets.UTF_8)) }
val responseCode = connection.responseCode
connection.disconnect()
return responseCode == HttpURLConnection.HTTP_NO_CONTENT
}
public val CustomProtocolContext: ProtocolContextBuilder.() -> Unit = {
authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash ->
val success = sendJoinRequest(url, accessToken, uuid, serverIdHash)
require(success) { "Failed to authenticate with Mojang session server" }
}
}
```
-327
View File
@@ -1,327 +0,0 @@
# 前言
如果底层TCP Socket库是非挂起的, 那么**不要**将IO读写操作使用`withContext`包裹,
这会导致在网络IO中频繁的切换线程导致程序性能**下降**.
# 基于Netty的TCP Socket实现
```kotlin
dependencies {
implementation("io.netty:netty-transport:4.2.17.Final")
implementation("io.netty:netty-buffer:4.2.17.Final")
}
```
<details>
<summary>点击展开代码</summary>
```kotlin
import cn.rtast.libmc.network.RawSocket
import cn.rtast.libmc.network.ReadChannel
import cn.rtast.libmc.network.SocketEngine
import cn.rtast.libmc.network.WriteChannel
import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import io.netty.channel.*
import io.netty.channel.nio.NioIoHandler
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioSocketChannel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import io.netty.channel.Channel as NettyChannel
import kotlinx.coroutines.channels.Channel as KotlinChannel
public class NettyNetworkSocketEngine : SocketEngine {
override fun create(host: String, port: Int): RawSocket = NettyNetworkSocket(host, port)
private class NettyNetworkSocket(private val host: String, private val port: Int) : RawSocket {
private val workerGroup: EventLoopGroup = MultiThreadIoEventLoopGroup(0, NioIoHandler.newFactory())
private lateinit var channel: NettyChannel
private val inboundQueue = KotlinChannel<ByteArray>(KotlinChannel.UNLIMITED)
private lateinit var readChannel: NettyReadChannel
private lateinit var writeChannel: NettyWriteChannel
override suspend fun connect() {
val bootstrap = Bootstrap()
.group(workerGroup)
.channel(NioSocketChannel::class.java)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.handler(object : ChannelInitializer<SocketChannel>() {
override fun initChannel(ch: SocketChannel) {
ch.pipeline().addLast(object : ChannelInboundHandlerAdapter() {
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
if (msg is ByteBuf) {
try {
val bytes = ByteArray(msg.readableBytes())
msg.readBytes(bytes)
inboundQueue.trySend(bytes)
} finally {
msg.release()
}
}
}
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
inboundQueue.close(cause)
ctx.close()
}
override fun channelInactive(ctx: ChannelHandlerContext) {
inboundQueue.close()
super.channelInactive(ctx)
}
})
}
})
channel = withContext(Dispatchers.IO) {
bootstrap.connect(host, port).suspendAwait()
}
readChannel = NettyReadChannel(inboundQueue)
writeChannel = NettyWriteChannel(channel)
}
override fun openReadChannel(): ReadChannel = readChannel
override fun openWriteChannel(): WriteChannel = writeChannel
override fun close() {
if (::channel.isInitialized && channel.isOpen) {
channel.close()
}
inboundQueue.close()
workerGroup.shutdownGracefully()
}
}
private class NettyReadChannel(private val inboundQueue: KotlinChannel<ByteArray>) : ReadChannel {
private var currentChunk: ByteArray? = null
private var chunkOffset = 0
private suspend fun fetchNextChunk() {
val next = inboundQueue.receiveCatching().getOrNull()
?: throw IllegalStateException("Socket/Channel closed while reading")
currentChunk = next
chunkOffset = 0
}
override suspend fun readByte(): Byte {
while (currentChunk == null || chunkOffset >= currentChunk!!.size) {
fetchNextChunk()
}
val chunk = currentChunk!!
return chunk[chunkOffset++]
}
override suspend fun readBytes(length: Int): ByteArray {
val result = ByteArray(length)
readFully(result, 0, length)
return result
}
override suspend fun readFully(out: ByteArray, start: Int, end: Int) {
var written = start
while (written < end) {
while (currentChunk == null || chunkOffset >= currentChunk!!.size) {
fetchNextChunk()
}
val chunk = currentChunk!!
val available = chunk.size - chunkOffset
val toCopy = minOf(available, end - written)
chunk.copyInto(
out,
destinationOffset = written,
startIndex = chunkOffset,
endIndex = chunkOffset + toCopy
)
chunkOffset += toCopy
written += toCopy
}
}
}
private class NettyWriteChannel(private val channel: NettyChannel) : WriteChannel {
private val writeMutex = Mutex()
override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) {
val length = endIndex - startIndex
if (length <= 0) return
val nettyBuf = Unpooled.copiedBuffer(value, startIndex, length)
writeMutex.withLock {
withContext(Dispatchers.IO) {
channel.writeAndFlush(nettyBuf).suspendAwait()
}
}
}
override suspend fun flush() {
writeMutex.withLock {
withContext(Dispatchers.IO) {
channel.flush()
}
}
}
}
}
private suspend inline fun ChannelFuture.suspendAwait(): NettyChannel = suspendCancellableCoroutine { cont ->
if (isDone) {
if (isSuccess) cont.resume(channel())
else cont.resumeWithException(cause() ?: RuntimeException("Netty operation failed"))
return@suspendCancellableCoroutine
}
addListener { future ->
if (future.isSuccess) {
cont.resume(channel())
} else {
cont.resumeWithException(future.cause() ?: RuntimeException("Netty operation failed"))
}
}
cont.invokeOnCancellation { cancel(false) }
}
```
> 请一并复制import部分的代码
</details>
# 基于ktor-network的TCP Socket实现
```kotlin
dependencies {
implementation("io.ktor:ktor-network:3.5.2")
}
```
<details>
<summary>点击展开代码</summary>
```kotlin
public class KtorNetworkEngine : SocketEngine {
override fun create(host: String, port: Int): RawSocket = KtorNetworkSocket(host, port)
}
public class KtorNetworkSocket(private val host: String, private val port: Int) : RawSocket {
private val sm = SelectorManager(Dispatchers.IO)
private lateinit var socket: Socket
override suspend fun connect(): Unit = run { socket = aSocket(sm).tcp().connect(host, port) }
override fun openReadChannel(): ReadChannel = KtorReadChannel(socket.openReadChannel())
override fun openWriteChannel(): WriteChannel = KtorWriteChannel(socket.openWriteChannel())
override fun close() {
socket.close()
sm.close()
}
}
public class KtorReadChannel(private val readChannel: ByteReadChannel) : ReadChannel {
override suspend fun readByte(): Byte = readChannel.readByte()
override suspend fun readBytes(length: Int): ByteArray = readChannel.readByteArray(length)
override suspend fun readFully(out: ByteArray, start: Int, end: Int): Unit =
readChannel.readFully(out, start, end)
}
public class KtorWriteChannel(private val writeChannel: ByteWriteChannel) : WriteChannel {
override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) {
writeChannel.writeFully(value, startIndex, endIndex)
}
override suspend fun flush(): Unit = writeChannel.flush()
}
```
</details>
# 基于Java内置Socket的TCP Socket实现
<details>
<summary>点击展开代码</summary>
```kotlin
class JavaSocketEngine : SocketEngine {
override fun create(host: String, port: Int): RawSocket = JavaNetworkSocket(host, port)
}
class JavaNetworkSocket(
private val host: String,
private val port: Int,
private val connectTimeoutMs: Int = 10000,
) : RawSocket {
private lateinit var socket: Socket
private lateinit var readChannel: JavaReadChannel
private lateinit var writeChannel: JavaWriteChannel
override suspend fun connect() {
val s = Socket()
s.tcpNoDelay = true
s.connect(InetSocketAddress(host, port), connectTimeoutMs)
socket = s
readChannel = JavaReadChannel(s.getInputStream())
writeChannel = JavaWriteChannel(s.getOutputStream())
}
override fun openReadChannel(): ReadChannel = readChannel
override fun openWriteChannel(): WriteChannel = writeChannel
override fun close() {
if (::socket.isInitialized && !socket.isClosed) {
runCatching { socket.close() }
}
}
}
class JavaReadChannel(private val inputStream: InputStream) : ReadChannel {
override suspend fun readByte(): Byte {
val b = inputStream.read()
if (b == -1) throw IllegalStateException("Socket stream reached EOF while reading byte")
return b.toByte()
}
override suspend fun readBytes(length: Int): ByteArray {
val buffer = ByteArray(length)
readFullyInternal(buffer, 0, length)
return buffer
}
override suspend fun readFully(out: ByteArray, start: Int, end: Int) {
readFullyInternal(out, start, end - start)
}
private fun readFullyInternal(out: ByteArray, offset: Int, length: Int) {
var bytesRead = 0
while (bytesRead < length) {
val count = inputStream.read(out, offset + bytesRead, length - bytesRead)
if (count == -1) {
throw IllegalStateException("Socket stream closed unexpectedly (read $bytesRead of $length bytes)")
}
bytesRead += count
}
}
}
class JavaWriteChannel(private val outputStream: OutputStream) : WriteChannel {
override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) {
outputStream.write(value, startIndex, endIndex - startIndex)
}
override suspend fun flush() {
outputStream.flush()
}
}
```
</details>
-32
View File
@@ -1,32 +0,0 @@
# libmc
一个轻量级适用于Kotlin Native & Jvm的现代Minecraft客户端协议库, 核心协议库模块使用了以下依赖
- 标准库 (`kotlin-stdlib`)
- IO (`kotlinx-io`) - 高效的将每个数据包封装为一个Buffer
- 协程库 (`kotlinx-coroutines`) - 实现高性能异步操作
## 需要实现的API
| 名称 | 是否必须实现 | 额外说明 |
|:-------------|:-------------|:-----------------------------------------------------------------------------------------------------------------------------------------------|
| TCP Socket | 是 | `protocol` 模块没有内置TCP Socket实现. [实现 TCP Socket](Impl-TCP-Socket-zh.md) |
| AES-128-CFB8 | 视情况而定 | 仅在登录开启了正版验证(`online-mode`)的服务器时需要, 用于加密/解密流量. [实现AES-128-CFB8](Impl-Crypto-zh.md#AES-128-CFB8) |
| RSA 1024 | 视情况而定 | 仅在登录开启了正版验证(`online-mode`)的服务器时需要, 用于对服务器下发的公钥进行签名. [实现RSA 1024](Impl-Crypto-zh.md#RSA1024) |
| SHA1 | 视情况而定 | 仅在登录开启了正版验证(`online-mode`)的服务器时需要, 用于计算服务器的ServerId. [实现SHA1](Impl-Crypto-zh.md#SHA1) |
| HTTP 客户端 | 视情况而定 | 仅在登录开启了正版验证(`online-mode`)的服务器时需要, 用于向Mojang的Session服务器发送加入服务器的请求 [实现HTTP 客户端](Impl-HTTP-Client-zh.md) |
# 开始使用
> `libmc-protocol`目前正在开发中, 也许存在着某些未被发现的问题.
> 它仅支持最新的Minecraft版本 (目前支持的游戏版本: `26.2`, 协议版本: `776`)
[开始使用libmc-protocol](Get-started-zh.md)
# 将实现的API组合起来
[将所有上下文API组合](Assemble-context-zh.md)
# NBT & SNBT
[NBT & SNBT](../en/NBT-SNBT.md)