Test: complete send chat message without signature
This commit is contained in:
6 files changed
+118
-7
No files matched your search
@@ -33,3 +33,32 @@ public fun BitSet.getBit(bitIndex: Int): Boolean {
|
|||||||
val bitOffset = bitIndex and 63
|
val bitOffset = bitIndex and 63
|
||||||
return (this[longIndex] and (1L shl bitOffset)) != 0L
|
return (this[longIndex] and (1L shl bitOffset)) != 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class FixedBitSet20(initialBits: Int = 0) {
|
||||||
|
private var bits: Int = initialBits and 0xFFFFF
|
||||||
|
|
||||||
|
public operator fun get(index: Int): Boolean {
|
||||||
|
require(index in 0..19) { "Index out of range [0, 19]" }
|
||||||
|
return (bits and (1 shl index)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public operator fun set(index: Int, value: Boolean) {
|
||||||
|
require(index in 0..19) { "Index out of range [0, 19]" }
|
||||||
|
bits = if (value) {
|
||||||
|
bits or (1 shl index)
|
||||||
|
} else {
|
||||||
|
bits and (1 shl index).inv()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun toByteArray(): ByteArray {
|
||||||
|
val result = ByteArray(3)
|
||||||
|
result[0] = (bits and 0xFF).toByte()
|
||||||
|
result[1] = ((bits shr 8) and 0xFF).toByte()
|
||||||
|
result[2] = ((bits shr 16) and 0x0F).toByte()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun createFixedBitSet20(initialBits: Int = 0): FixedBitSet20 =
|
||||||
|
FixedBitSet20(initialBits)
|
||||||
+1
-1
@@ -13,7 +13,7 @@ import cn.rtast.libmc.packet.MinecraftPacket
|
|||||||
import cn.rtast.libmc.primitives.readVarInt
|
import cn.rtast.libmc.primitives.readVarInt
|
||||||
import cn.rtast.libmc.protocol.protocol.game.effect.EntityEffectFlags
|
import cn.rtast.libmc.protocol.protocol.game.effect.EntityEffectFlags
|
||||||
import cn.rtast.libmc.protocol.protocol.game.effect.readEntityEffectFlags
|
import cn.rtast.libmc.protocol.protocol.game.effect.readEntityEffectFlags
|
||||||
import cn.rtast.libmc.protocol.tick.ticks
|
import cn.rtast.libmc.protocol.util.ticks
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
public data class ClientboundEntityEffectPacket(
|
public data class ClientboundEntityEffectPacket(
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ import cn.rtast.libmc.stream.BytesBuffer
|
|||||||
import cn.rtast.libmc.packet.PacketCodec
|
import cn.rtast.libmc.packet.PacketCodec
|
||||||
import cn.rtast.libmc.packet.MinecraftPacket
|
import cn.rtast.libmc.packet.MinecraftPacket
|
||||||
import cn.rtast.libmc.primitives.readVarInt
|
import cn.rtast.libmc.primitives.readVarInt
|
||||||
import cn.rtast.libmc.protocol.tick.ticks
|
import cn.rtast.libmc.protocol.util.ticks
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
public data class ClientboundPlayerEndCombatPacket(val duration: Duration) : MinecraftPacket {
|
public data class ClientboundPlayerEndCombatPacket(val duration: Duration) : MinecraftPacket {
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package cn.rtast.libmc.protocol.tick
|
package cn.rtast.libmc.protocol.util
|
||||||
|
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2026/9/7
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import cn.rtast.libmc.primitives.FixedBitSet20
|
||||||
|
import cn.rtast.libmc.primitives.createFixedBitSet20
|
||||||
|
import cn.rtast.libmc.stream.BytesBuffer
|
||||||
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
|
class ClientChatTracker {
|
||||||
|
private val lastSeenQueue = ArrayDeque<ByteArray>(20)
|
||||||
|
var pendingMessageCount: Int = 0
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun onReceivePlayerChat(signature: ByteArray?) {
|
||||||
|
if (signature == null || signature.size != 256) return
|
||||||
|
if (lastSeenQueue.size >= 20) lastSeenQueue.removeFirst()
|
||||||
|
lastSeenQueue.addLast(signature)
|
||||||
|
pendingMessageCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
fun prepareForOutgoingMessage(): ChatStateSnapshot {
|
||||||
|
val signatures = lastSeenQueue.toList()
|
||||||
|
val count = pendingMessageCount
|
||||||
|
pendingMessageCount = 0
|
||||||
|
return ChatStateSnapshot(signatures, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ChatStateSnapshot(val lastSeenSignatures: List<ByteArray>, val messageCount: Int)
|
||||||
|
|
||||||
|
fun createAcknowledgedBitSet(lastSeenSignatures: List<ByteArray>): FixedBitSet20 {
|
||||||
|
val bitSet = createFixedBitSet20()
|
||||||
|
for (i in lastSeenSignatures.indices) {
|
||||||
|
if (i >= 20) break
|
||||||
|
bitSet[i] = true
|
||||||
|
}
|
||||||
|
return bitSet
|
||||||
|
}
|
||||||
|
|
||||||
|
object ChatPacketUtils {
|
||||||
|
fun computeByteArrayHashCode(bytes: ByteArray): Int {
|
||||||
|
var result = 1
|
||||||
|
for (element in bytes) result = 31 * result + element.toInt()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun computePacketChecksum(lastSeenSignatures: List<ByteArray>): Byte {
|
||||||
|
var combinedHash = 1
|
||||||
|
for (sig in lastSeenSignatures) {
|
||||||
|
val sigHash = computeByteArrayHashCode(sig)
|
||||||
|
combinedHash = 31 * combinedHash + sigHash
|
||||||
|
}
|
||||||
|
val resultByte = combinedHash.toByte()
|
||||||
|
return if (resultByte == 0.toByte()) 1.toByte() else resultByte
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ package test
|
|||||||
import cn.rtast.libmc.packet.ClientboundUnknownPacket
|
import cn.rtast.libmc.packet.ClientboundUnknownPacket
|
||||||
import cn.rtast.libmc.protocol.client.createMinecraftClient
|
import cn.rtast.libmc.protocol.client.createMinecraftClient
|
||||||
import cn.rtast.libmc.protocol.crypto.DefaultProtocolContext
|
import cn.rtast.libmc.protocol.crypto.DefaultProtocolContext
|
||||||
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundPlayerInfoUpdatePacket
|
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundPlayerChatMessagePacket
|
||||||
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundWaypointPacket
|
import cn.rtast.libmc.protocol.packet.play.serverbound.ServerboundChatMessagePacket
|
||||||
import cn.rtast.libmc.protocol.util.generateOfflineUuid
|
import cn.rtast.libmc.protocol.util.generateOfflineUuid
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -21,11 +21,14 @@ import java.security.KeyFactory
|
|||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.spec.X509EncodedKeySpec
|
import java.security.spec.X509EncodedKeySpec
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.time.Clock
|
||||||
import kotlin.uuid.Uuid
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
|
|
||||||
class TestClientTestInJvm {
|
class TestClientTestInJvm {
|
||||||
val accessToken = File("src/jvmTest/resources/accessToken.txt").readText()
|
val accessToken = File("src/jvmTest/resources/accessToken.txt").readText()
|
||||||
|
private val chatTracker = ClientChatTracker()
|
||||||
|
|
||||||
fun encrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray {
|
fun encrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray {
|
||||||
val keySpec = X509EncodedKeySpec(publicKeyBytes)
|
val keySpec = X509EncodedKeySpec(publicKeyBytes)
|
||||||
@@ -84,8 +87,25 @@ class TestClientTestInJvm {
|
|||||||
// if (packet !is ClientboundWaypointPacket)
|
// if (packet !is ClientboundWaypointPacket)
|
||||||
// println("$direction -> $packet")
|
// println("$direction -> $packet")
|
||||||
// }
|
// }
|
||||||
// cli.onPacket<ClientboundUnknownPacket> { println(it) }
|
cli.onPacket<ClientboundUnknownPacket> {
|
||||||
cli.onPacket<ClientboundPlayerInfoUpdatePacket> { println(it) }
|
println(it)
|
||||||
|
val snapshot = chatTracker.prepareForOutgoingMessage()
|
||||||
|
cli.networkChannel.sendPacket(
|
||||||
|
ServerboundChatMessagePacket(
|
||||||
|
"114514", Clock.System.now().toEpochMilliseconds(),
|
||||||
|
Random.nextLong(),
|
||||||
|
null, snapshot.messageCount, createAcknowledgedBitSet(snapshot.lastSeenSignatures).toByteArray(),
|
||||||
|
ChatPacketUtils.computePacketChecksum(snapshot.lastSeenSignatures)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
cli.onPacket<ClientboundPlayerChatMessagePacket> {
|
||||||
|
chatTracker.onReceivePlayerChat(it.messageSignature)
|
||||||
|
}
|
||||||
|
// cli.onPacket<ClientboundServerDataPacket> { println(it) }
|
||||||
|
// cli.onPacket<ClientboundServerLinksPacket> { println(it) }
|
||||||
|
// cli.onPacket<ClientboundCodeOfConductPacket> { println(it) }
|
||||||
|
// cli.onPacket<ClientboundPlayerInfoUpdatePacket> { println(it) }
|
||||||
cli.launch { cli.connect() }
|
cli.launch { cli.connect() }
|
||||||
while (true) {
|
while (true) {
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user