From f80e85e02be105e63694a58a3af56c41ed98e960 Mon Sep 17 00:00:00 2001 From: twisti-dev <76837088+twisti-dev@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:23:10 +0200 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20feat(chat):=20add=20LastSeenMes?= =?UTF-8?q?sagesValidatorMirror=20and=20PlayerChatSessionSnapshot=20data?= =?UTF-8?q?=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implement LastSeenMessagesValidatorMirror to track last seen messages and their count - create PlayerChatSessionSnapshot to encapsulate chat session data including last seen messages - add methods to create and apply chat session snapshots in SurfPaperNmsPlayerBridge --- .../surf/api/core/util/var-handle-util.kt | 27 ++++++ .../V1_21_11SurfPaperNmsPlayerBridgeImpl.kt | 12 +++ .../V26_1SurfPaperNmsPlayerBridgeImpl.kt | 96 +++++++++++++++++++ .../v26_1/reflection/V26_1NmsReflections.kt | 30 +++++- .../nms/bridges/SurfPaperNmsPlayerBridge.kt | 4 + .../chat/LastSeenMessagesValidatorMirror.kt | 51 ++++++++++ .../data/chat/PlayerChatSessionSnapshot.kt | 33 +++++++ 7 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/util/var-handle-util.kt create mode 100644 surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt create mode 100644 surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/util/var-handle-util.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/util/var-handle-util.kt new file mode 100644 index 00000000..6431028e --- /dev/null +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/util/var-handle-util.kt @@ -0,0 +1,27 @@ +package dev.slne.surf.api.core.util + +import java.lang.invoke.VarHandle + +/** + * Sets the value at the specified variable handle to the new value if the new value is greater than the current value. + * + * This method ensures that the value is updated only when the provided `newValue` is strictly greater + * than the current value stored at the variable handle. The operation is performed atomically. + * + * @param receiver the object on which the variable handle operates + * @param newValue the new value to set if greater than the current value + * @return `true` if the value was updated to `newValue`, `false` if `newValue` was not greater and no update occurred + */ +fun VarHandle.setIfGreater(receiver: Any, newValue: Int): Boolean { + while (true) { + val current = getVolatile(receiver) as Int + + if (newValue <= current) { + return false + } + + if (compareAndSet(receiver, current, newValue)) { + return true + } + } +} \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt index 8df0e0d1..4715d697 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt @@ -8,6 +8,7 @@ import dev.slne.surf.api.paper.nms.NmsUseWithCaution import dev.slne.surf.api.paper.nms.bridges.SurfPaperNmsPlayerBridge import dev.slne.surf.api.paper.nms.bridges.SurfPaperNmsPlayerBridge.PlayerInventoryEdit import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatMessageMirror +import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatSessionSnapshot import dev.slne.surf.api.paper.nms.bridges.data.chat.RemoteChatSessionData import dev.slne.surf.api.paper.nms.common.dummy.DummyEntityEquipment import dev.slne.surf.api.paper.server.nms.v1_21_11.extensions.toNms @@ -63,6 +64,17 @@ class V1_21_11SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { ) } + override fun createChatSessionSnapshot(player: Player): PlayerChatSessionSnapshot { + TODO("Not yet implemented") + } + + override fun applyChatSessionSnapshot( + player: Player, + snapshot: PlayerChatSessionSnapshot + ) { + TODO("Not yet implemented") + } + override fun runOnChatMessageChain(player: Player, scope: CoroutineScope, block: suspend () -> Unit) { val nmsPlayer = player.toNms() val connection = nmsPlayer.connection diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt index befa29e7..e9de504c 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt @@ -7,7 +7,9 @@ import dev.slne.surf.api.paper.extensions.server import dev.slne.surf.api.paper.nms.NmsUseWithCaution import dev.slne.surf.api.paper.nms.bridges.SurfPaperNmsPlayerBridge import dev.slne.surf.api.paper.nms.bridges.SurfPaperNmsPlayerBridge.PlayerInventoryEdit +import dev.slne.surf.api.paper.nms.bridges.data.chat.LastSeenMessagesValidatorMirror import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatMessageMirror +import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatSessionSnapshot import dev.slne.surf.api.paper.nms.bridges.data.chat.RemoteChatSessionData import dev.slne.surf.api.paper.nms.common.dummy.DummyEntityEquipment import dev.slne.surf.api.paper.server.nms.v26_1.extensions.toNms @@ -26,6 +28,7 @@ import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.NbtIo import net.minecraft.network.chat.* import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket +import net.minecraft.network.protocol.game.ServerboundChatSessionUpdatePacket import net.minecraft.server.MinecraftServer import net.minecraft.server.players.NameAndId import net.minecraft.util.ProblemReporter @@ -36,6 +39,7 @@ import net.minecraft.world.entity.EntityEquipment import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.npc.InventoryCarrier import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.ProfilePublicKey import net.minecraft.world.level.storage.* import org.bukkit.craftbukkit.CraftEquipmentSlot import org.bukkit.craftbukkit.inventory.CraftItemStack @@ -44,6 +48,7 @@ import org.bukkit.inventory.EquipmentSlot import org.bukkit.inventory.ItemStack import java.io.File import java.nio.file.Path +import java.util.* import java.util.concurrent.CompletableFuture import kotlin.io.path.createTempFile import kotlin.jvm.optionals.getOrNull @@ -62,6 +67,97 @@ class V26_1SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { ) } + override fun createChatSessionSnapshot(player: Player): PlayerChatSessionSnapshot { + val nmsPlayer = player.toNms() + val connection = nmsPlayer.connection + + @Suppress("RedundantRequireNotNullCall") + requireNotNull(connection) { "Player connection is not available" } + + val nextChatIndex = V26_1NmsReflections.getNextChatIndex(nmsPlayer.connection) + val chatSession = nmsPlayer.chatSession + val chatSessionData = chatSession?.asData() + val lastSeenMessages = V26_1NmsReflections.getLastSeenMessages(connection) + val messageSignatureCache = V26_1NmsReflections.getMessageSignatureCache(connection) + + val messageSignatureCacheEntries = + V26_1NmsReflections.getEntriesFromMessageSignatureCache(messageSignatureCache) + val messageSignatureCacheEntriesApi = Array(messageSignatureCacheEntries.size) { index -> + messageSignatureCacheEntries[index]?.bytes() + } + + return PlayerChatSessionSnapshot( + nextChatIndex = nextChatIndex, + chatSession = if (chatSessionData != null) RemoteChatSessionData( + sessionId = chatSessionData.sessionId(), + expiresAt = chatSessionData.profilePublicKey().expiresAt(), + key = chatSessionData.profilePublicKey().key(), + keySignature = chatSessionData.profilePublicKey().keySignature() + ) else null, + lastSeenMessages = LastSeenMessagesValidatorMirror( + V26_1NmsReflections.getLastSeenCountFromMessageValidator(lastSeenMessages), + V26_1NmsReflections.getTrackedMessagesFromMessageValidator(lastSeenMessages) + .map { LastSeenMessagesValidatorMirror.LastSeenTrackedEntry(it.signature().bytes(), it.pending()) }, + V26_1NmsReflections.getLastPendingMessageFromMessageValidator(lastSeenMessages)?.bytes + ), + messageSignatureCache = messageSignatureCacheEntriesApi + ) + } + + override fun applyChatSessionSnapshot(player: Player, snapshot: PlayerChatSessionSnapshot) { + val nmsPlayer = player.toNms() + val connection = nmsPlayer.connection ?: return + + val lastSeenMessages = V26_1NmsReflections.getLastSeenMessages(connection) + val messageSignatureCache = V26_1NmsReflections.getMessageSignatureCache(connection) + val sessionData = snapshot.chatSession + + synchronized(messageSignatureCache) { + V26_1NmsReflections.setNextChatIndex(connection, snapshot.nextChatIndex) + + synchronized(lastSeenMessages) { + val messages = V26_1NmsReflections.getTrackedMessagesFromMessageValidator(lastSeenMessages) + + for (entry in snapshot.lastSeenMessages.trackedMessages) { + val signature = entry.signature + val pending = entry.pending + messages.add(LastSeenTrackedEntry(MessageSignature(signature), pending)) + } + + V26_1NmsReflections.setLastPendingMessageFromMessageValidator( + lastSeenMessages, + snapshot.lastSeenMessages.lastPendingMessage?.let(::MessageSignature) + ) + } + + if (sessionData != null) { + MinecraftServer.getServer().packetProcessor() + .scheduleIfPossible( + connection, + ServerboundChatSessionUpdatePacket( + RemoteChatSession.Data( + sessionData.sessionId, + ProfilePublicKey.Data( + sessionData.expiresAt, + sessionData.key, + sessionData.keySignature + ) + ) + ) + ) + } + + val messageSignatureCacheQueue = ArrayDeque() + for (signatureBytes in snapshot.messageSignatureCache) { + if (signatureBytes != null) { + messageSignatureCacheQueue.add(MessageSignature(signatureBytes)) + } + } + + V26_1NmsReflections.pushMessageSignatureCache(messageSignatureCache, messageSignatureCacheQueue) + } + } + override fun runOnChatMessageChain(player: Player, scope: CoroutineScope, block: suspend () -> Unit) { val nmsPlayer = player.toNms() val connection = nmsPlayer.connection diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt index 2a2539d6..aac53b6d 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt @@ -5,10 +5,8 @@ import com.google.gson.JsonElement import dev.slne.surf.api.shared.api.reflection.* import io.netty.channel.ChannelFuture import io.papermc.paper.adventure.ChatProcessor -import net.minecraft.network.chat.ChatType -import net.minecraft.network.chat.FilterMask -import net.minecraft.network.chat.LastSeenMessagesValidator -import net.minecraft.network.chat.MessageSignatureCache +import it.unimi.dsi.fastutil.objects.ObjectList +import net.minecraft.network.chat.* import net.minecraft.network.syncher.EntityDataAccessor import net.minecraft.resources.ResourceKey import net.minecraft.server.network.ServerConnectionListener @@ -32,12 +30,36 @@ interface V26_1NmsReflections { @ConstantIntArgument(1) fun getAndIncreaseNextChatIndex(instance: ServerGamePacketListenerImpl): Int + @ReflectedField("nextChatIndex") + fun getNextChatIndex(instance: ServerGamePacketListenerImpl): Int + + @ReflectedField("nextChatIndex", access = ReflectedFieldAccess.SET) + fun setNextChatIndex(instance: ServerGamePacketListenerImpl, index: Int) + @ReflectedField("messageSignatureCache") fun getMessageSignatureCache(instance: ServerGamePacketListenerImpl): MessageSignatureCache + @ReflectedMethod("push") + fun pushMessageSignatureCache(instance: MessageSignatureCache, deque: ArrayDeque) + @ReflectedField(name = "lastSeenMessages") fun getLastSeenMessages(instance: ServerGamePacketListenerImpl): LastSeenMessagesValidator + @ReflectedField("lastSeenCount") + fun getLastSeenCountFromMessageValidator(validator: LastSeenMessagesValidator): Int + + @ReflectedField("trackedMessages") + fun getTrackedMessagesFromMessageValidator(validator: LastSeenMessagesValidator): ObjectList + + @ReflectedField("lastPendingMessage") + fun getLastPendingMessageFromMessageValidator(validator: LastSeenMessagesValidator): MessageSignature? + + @ReflectedField("lastPendingMessage", access = ReflectedFieldAccess.SET) + fun setLastPendingMessageFromMessageValidator(validator: LastSeenMessagesValidator, message: MessageSignature?) + + @ReflectedField("entries") + fun getEntriesFromMessageSignatureCache(cache: MessageSignatureCache): Array + @ReflectedConstructor fun createFilterMask(bitSet: BitSet): FilterMask diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt index 5e886970..465503c9 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt @@ -5,6 +5,7 @@ import dev.slne.surf.api.core.util.requiredService import dev.slne.surf.api.paper.nms.NmsUseWithCaution import dev.slne.surf.api.paper.nms.bridges.SurfPaperNmsPlayerBridge.Companion.editOfflineInventory import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatMessageMirror +import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatSessionSnapshot import dev.slne.surf.api.paper.nms.bridges.data.chat.RemoteChatSessionData import kotlinx.coroutines.CoroutineScope import net.kyori.adventure.chat.ChatType @@ -23,6 +24,9 @@ interface SurfPaperNmsPlayerBridge { fun getRemoteChatSessionData(player: Player): RemoteChatSessionData? + fun createChatSessionSnapshot(player: Player): PlayerChatSessionSnapshot + fun applyChatSessionSnapshot(player: Player, snapshot: PlayerChatSessionSnapshot) + fun runOnChatMessageChain(player: Player, scope: CoroutineScope, block: suspend () -> Unit) fun sendSignedMessageWithChangedContent( diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt new file mode 100644 index 00000000..bc7b6dec --- /dev/null +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt @@ -0,0 +1,51 @@ +package dev.slne.surf.api.paper.nms.bridges.data.chat + +import kotlinx.serialization.Serializable + +@Serializable +data class LastSeenMessagesValidatorMirror( + val lastSeenCount: Int, + val trackedMessages: List, + val lastPendingMessage: ByteArray? = null, +) { + + @Serializable + data class LastSeenTrackedEntry( + val signature: ByteArray, + val pending: Boolean + ) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is LastSeenTrackedEntry) return false + + if (pending != other.pending) return false + if (!signature.contentEquals(other.signature)) return false + + return true + } + + override fun hashCode(): Int { + var result = pending.hashCode() + result = 31 * result + signature.contentHashCode() + return result + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is LastSeenMessagesValidatorMirror) return false + + if (lastSeenCount != other.lastSeenCount) return false + if (trackedMessages != other.trackedMessages) return false + if (!lastPendingMessage.contentEquals(other.lastPendingMessage)) return false + + return true + } + + override fun hashCode(): Int { + var result = lastSeenCount + result = 31 * result + trackedMessages.hashCode() + result = 31 * result + (lastPendingMessage?.contentHashCode() ?: 0) + return result + } +} \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt new file mode 100644 index 00000000..44cbb2ef --- /dev/null +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt @@ -0,0 +1,33 @@ +package dev.slne.surf.api.paper.nms.bridges.data.chat + +import dev.slne.surf.api.paper.nms.NmsUseWithCaution +import kotlinx.serialization.Serializable + +@NmsUseWithCaution +@Serializable +data class PlayerChatSessionSnapshot( + val nextChatIndex: Int, + val chatSession: RemoteChatSessionData?, + val lastSeenMessages: LastSeenMessagesValidatorMirror, + val messageSignatureCache: Array, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PlayerChatSessionSnapshot) return false + + if (nextChatIndex != other.nextChatIndex) return false + if (chatSession != other.chatSession) return false + if (lastSeenMessages != other.lastSeenMessages) return false + if (!messageSignatureCache.contentDeepEquals(other.messageSignatureCache)) return false + + return true + } + + override fun hashCode(): Int { + var result = nextChatIndex + result = 31 * result + chatSession.hashCode() + result = 31 * result + lastSeenMessages.hashCode() + result = 31 * result + messageSignatureCache.contentDeepHashCode() + return result + } +} \ No newline at end of file From 6771af59001b710e2b7f78c918c4a18bc6c992d9 Mon Sep 17 00:00:00 2001 From: twisti-dev <76837088+twisti-dev@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:35:34 +0200 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=A8=20feat(reflection):=20add=20suppo?= =?UTF-8?q?rt=20for=20Kotlin=20array=20types=20in=20ReflectionSymbolProces?= =?UTF-8?q?sor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implement handling for Kotlin array types including BooleanArray, ByteArray, ShortArray, IntArray, LongArray, FloatArray, DoubleArray, and CharArray - enhance JavaType creation for arrays with appropriate nullability and class names --- .../surf-api-processor/build.gradle.kts | 2 +- .../reflection/ReflectionSymbolProcessor.kt | 78 ++++++++++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts b/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts index c631e60f..48638430 100644 --- a/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts +++ b/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts @@ -12,7 +12,7 @@ plugins { group = groupId version = buildString { - append("1.0.0") + append("1.0.1") if (snapshot) append("-SNAPSHOT") } diff --git a/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/api/processor/reflection/ReflectionSymbolProcessor.kt b/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/api/processor/reflection/ReflectionSymbolProcessor.kt index 18680563..a17733a3 100644 --- a/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/api/processor/reflection/ReflectionSymbolProcessor.kt +++ b/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/api/processor/reflection/ReflectionSymbolProcessor.kt @@ -7,10 +7,7 @@ import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.symbol.* import com.google.devtools.ksp.validate -import com.palantir.javapoet.ClassName -import com.palantir.javapoet.ParameterizedTypeName -import com.palantir.javapoet.TypeName -import com.palantir.javapoet.WildcardTypeName +import com.palantir.javapoet.* import dev.slne.surf.api.processor.ClassNames import dev.slne.surf.api.processor.ShortClassNames import dev.slne.surf.api.processor.ShortClassNames.CONSTANT_BOOLEAN_ARGUMENT @@ -412,6 +409,79 @@ class ReflectionSymbolProcessor(environment: SymbolProcessorEnvironment) : Symbo "kotlin.Double" -> if (isNullable) boxed("java.lang.Double", javaNullability) else JavaType.Double "kotlin.Char" -> if (isNullable) boxed("java.lang.Character", javaNullability) else JavaType.Char + "kotlin.Array" -> { + val componentType = arguments.firstOrNull() + ?.type + ?.resolve() + ?.toJavaType() + ?: JavaType.Object + + val arrayType = ArrayTypeName.of(componentType.className.box()) + + JavaType( + sourceName = "${componentType.sourceName}[]", + className = arrayType, + erasedClassName = ArrayTypeName.of(componentType.erasedClassName.box()), + nullability = javaNullability, + ) + } + + "kotlin.BooleanArray" -> JavaType( + sourceName = "boolean[]", + className = ArrayTypeName.of(TypeName.BOOLEAN), + erasedClassName = ArrayTypeName.of(TypeName.BOOLEAN), + nullability = javaNullability, + ) + + "kotlin.ByteArray" -> JavaType( + sourceName = "byte[]", + className = ArrayTypeName.of(TypeName.BYTE), + erasedClassName = ArrayTypeName.of(TypeName.BYTE), + nullability = javaNullability, + ) + + "kotlin.ShortArray" -> JavaType( + sourceName = "short[]", + className = ArrayTypeName.of(TypeName.SHORT), + erasedClassName = ArrayTypeName.of(TypeName.SHORT), + nullability = javaNullability, + ) + + "kotlin.IntArray" -> JavaType( + sourceName = "int[]", + className = ArrayTypeName.of(TypeName.INT), + erasedClassName = ArrayTypeName.of(TypeName.INT), + nullability = javaNullability, + ) + + "kotlin.LongArray" -> JavaType( + sourceName = "long[]", + className = ArrayTypeName.of(TypeName.LONG), + erasedClassName = ArrayTypeName.of(TypeName.LONG), + nullability = javaNullability, + ) + + "kotlin.FloatArray" -> JavaType( + sourceName = "float[]", + className = ArrayTypeName.of(TypeName.FLOAT), + erasedClassName = ArrayTypeName.of(TypeName.FLOAT), + nullability = javaNullability, + ) + + "kotlin.DoubleArray" -> JavaType( + sourceName = "double[]", + className = ArrayTypeName.of(TypeName.DOUBLE), + erasedClassName = ArrayTypeName.of(TypeName.DOUBLE), + nullability = javaNullability, + ) + + "kotlin.CharArray" -> JavaType( + sourceName = "char[]", + className = ArrayTypeName.of(TypeName.CHAR), + erasedClassName = ArrayTypeName.of(TypeName.CHAR), + nullability = javaNullability, + ) + else -> { val javaClassId = JavaToKotlinClassMap.mapKotlinToJava(FqNameUnsafe(qualifiedName)) val javaFqName = javaClassId?.asSingleFqName()?.asString() ?: qualifiedName From 4b14bf43c9573378a4d27776087a8fbcf4a07796 Mon Sep 17 00:00:00 2001 From: twisti-dev <76837088+twisti-dev@users.noreply.github.com> Date: Wed, 10 Jun 2026 20:59:35 +0200 Subject: [PATCH 3/5] =?UTF-8?q?=E2=9C=A8=20feat(chat):=20add=20IncomingCha?= =?UTF-8?q?tChainMirror=20and=20update=20chat=20session=20structures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - introduce IncomingChatChainMirror data class for managing chat chain state - update LastSeenMessagesValidatorMirror to support nullable tracked messages - enhance PlayerChatSessionSnapshot to include incomingChatChain - add debugChatSessionState method for improved chat session diagnostics - update version to 3.21.0 in gradle.properties --- gradle.properties | 2 +- .../V1_21_11SurfPaperNmsPlayerBridgeImpl.kt | 4 + .../V26_1SurfPaperNmsPlayerBridgeImpl.kt | 544 ++++++++++++++++-- .../v26_1/reflection/V26_1NmsReflections.kt | 51 +- .../nms/bridges/SurfPaperNmsPlayerBridge.kt | 2 + .../data/chat/IncomingChatChainMirror.kt | 13 + .../chat/LastSeenMessagesValidatorMirror.kt | 5 +- .../data/chat/PlayerChatSessionSnapshot.kt | 1 + 8 files changed, 559 insertions(+), 63 deletions(-) create mode 100644 surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt diff --git a/gradle.properties b/gradle.properties index c8fc390e..e53dd202 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled javaVersion=25 mcVersion=26.1.2 group=dev.slne.surf.api -version=3.20.0 +version=3.21.0 relocationPrefix=dev.slne.surf.api.libs snapshot=false diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt index 4715d697..103b8fb9 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt @@ -75,6 +75,10 @@ class V1_21_11SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { TODO("Not yet implemented") } + override fun debugChatSessionState(player: Player, label: String) { + TODO("Not yet implemented") + } + override fun runOnChatMessageChain(player: Player, scope: CoroutineScope, block: suspend () -> Unit) { val nmsPlayer = player.toNms() val connection = nmsPlayer.connection diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt index e9de504c..2598377a 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt @@ -7,10 +7,7 @@ import dev.slne.surf.api.paper.extensions.server import dev.slne.surf.api.paper.nms.NmsUseWithCaution import dev.slne.surf.api.paper.nms.bridges.SurfPaperNmsPlayerBridge import dev.slne.surf.api.paper.nms.bridges.SurfPaperNmsPlayerBridge.PlayerInventoryEdit -import dev.slne.surf.api.paper.nms.bridges.data.chat.LastSeenMessagesValidatorMirror -import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatMessageMirror -import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatSessionSnapshot -import dev.slne.surf.api.paper.nms.bridges.data.chat.RemoteChatSessionData +import dev.slne.surf.api.paper.nms.bridges.data.chat.* import dev.slne.surf.api.paper.nms.common.dummy.DummyEntityEquipment import dev.slne.surf.api.paper.server.nms.v26_1.extensions.toNms import dev.slne.surf.api.paper.server.nms.v26_1.reflection.V26_1NmsReflections @@ -28,8 +25,10 @@ import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.NbtIo import net.minecraft.network.chat.* import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket -import net.minecraft.network.protocol.game.ServerboundChatSessionUpdatePacket +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket import net.minecraft.server.MinecraftServer +import net.minecraft.server.level.ServerPlayer +import net.minecraft.server.network.ServerGamePacketListenerImpl import net.minecraft.server.players.NameAndId import net.minecraft.util.ProblemReporter import net.minecraft.util.ProblemReporter.ScopedCollector @@ -55,109 +54,533 @@ import kotlin.jvm.optionals.getOrNull @NmsUseWithCaution class V26_1SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { + + + private fun chatDebug(message: () -> String) { + CHAT_TRANSFER_LOGGER.info("[chat-xfer] {}", message()) + } + + private fun ByteArray?.shortSig(): String { + if (this == null) return "null" + + return take(6).joinToString("") { + "%02x".format(it.toInt() and 0xff) + } + } + + private fun LastSeenMessagesValidator.debugSummary(): String { + val tracked = + V26_1NmsReflections.getTrackedMessagesFromMessageValidator(this) + + val nonNull = + tracked.count { it != null } + + val pending = + tracked.count { it?.pending() == true } + + val lastPending = + V26_1NmsReflections.getLastPendingMessageFromMessageValidator(this) + + return "lastSeenCount=${V26_1NmsReflections.getLastSeenCountFromMessageValidator(this)} " + + "trackedSize=${tracked.size} trackedNonNull=$nonNull trackedPending=$pending " + + "lastPending=${lastPending?.bytes?.shortSig()}" + } + + private fun MessageSignatureCache.debugSummary(): String { + val entries = + V26_1NmsReflections.getEntriesFromMessageSignatureCache(this) + + val nonNull = + entries.count { it != null } + + return "cacheSize=${entries.size} cacheNonNull=$nonNull first=${entries.firstOrNull()?.bytes()?.shortSig()}" + } + + private fun ServerGamePacketListenerImpl.debugRemoteSessionId(): UUID? { + return V26_1NmsReflections.getRemoteChatSession(this)?.sessionId() + } + + private fun ServerGamePacketListenerImpl.debugDecoderLink(): SignedMessageLink? { + val decoder = + V26_1NmsReflections.getSignedMessageDecoder(this) + + val chain = + decoder.signedMessageChainOrNull() + + return chain?.let { + V26_1NmsReflections.getNextLinkFromSignedMessageChain(it) + } + } + + override fun getRemoteChatSessionData(player: Player): RemoteChatSessionData? { - val session = player.toNms().chatSession?.asData() ?: return null + val connection = player.toNms().connection ?: return null + val session = V26_1NmsReflections.getRemoteChatSession(connection) ?: return null val profilePublicKey = session.profilePublicKey() return RemoteChatSessionData( sessionId = session.sessionId(), - expiresAt = profilePublicKey.expiresAt(), - key = profilePublicKey.key(), - keySignature = profilePublicKey.keySignature() + expiresAt = profilePublicKey.data().expiresAt(), + key = profilePublicKey.data().key(), + keySignature = profilePublicKey.data().keySignature() ) } override fun createChatSessionSnapshot(player: Player): PlayerChatSessionSnapshot { val nmsPlayer = player.toNms() - val connection = nmsPlayer.connection - - @Suppress("RedundantRequireNotNullCall") - requireNotNull(connection) { "Player connection is not available" } + val connection = requireNotNull(nmsPlayer.connection) { + "Player connection is not available" + } - val nextChatIndex = V26_1NmsReflections.getNextChatIndex(nmsPlayer.connection) - val chatSession = nmsPlayer.chatSession - val chatSessionData = chatSession?.asData() val lastSeenMessages = V26_1NmsReflections.getLastSeenMessages(connection) val messageSignatureCache = V26_1NmsReflections.getMessageSignatureCache(connection) - val messageSignatureCacheEntries = - V26_1NmsReflections.getEntriesFromMessageSignatureCache(messageSignatureCache) - val messageSignatureCacheEntriesApi = Array(messageSignatureCacheEntries.size) { index -> - messageSignatureCacheEntries[index]?.bytes() - } + synchronized(messageSignatureCache) { + synchronized(lastSeenMessages) { + val nextChatIndex = V26_1NmsReflections.getNextChatIndex(connection) + val chatSessionData = V26_1NmsReflections.getRemoteChatSession(connection)?.asData() - return PlayerChatSessionSnapshot( - nextChatIndex = nextChatIndex, - chatSession = if (chatSessionData != null) RemoteChatSessionData( - sessionId = chatSessionData.sessionId(), - expiresAt = chatSessionData.profilePublicKey().expiresAt(), - key = chatSessionData.profilePublicKey().key(), - keySignature = chatSessionData.profilePublicKey().keySignature() - ) else null, - lastSeenMessages = LastSeenMessagesValidatorMirror( - V26_1NmsReflections.getLastSeenCountFromMessageValidator(lastSeenMessages), - V26_1NmsReflections.getTrackedMessagesFromMessageValidator(lastSeenMessages) - .map { LastSeenMessagesValidatorMirror.LastSeenTrackedEntry(it.signature().bytes(), it.pending()) }, - V26_1NmsReflections.getLastPendingMessageFromMessageValidator(lastSeenMessages)?.bytes - ), - messageSignatureCache = messageSignatureCacheEntriesApi - ) + val cacheEntries = + V26_1NmsReflections.getEntriesFromMessageSignatureCache(messageSignatureCache) + + val decoder = V26_1NmsReflections.getSignedMessageDecoder(connection) + val signedMessageChain = decoder.signedMessageChainOrNull() + val nextLink = signedMessageChain?.let { + V26_1NmsReflections.getNextLinkFromSignedMessageChain(it) + } + + val incomingChatChain = if (signedMessageChain != null && nextLink != null) { + IncomingChatChainMirror( + nextLinkIndex = nextLink.index(), + lastTimeStamp = V26_1NmsReflections.getLastTimeStampFromSignedMessageChain(signedMessageChain) + ) + } else { + null + } + + chatDebug { + "CREATE_SNAPSHOT uuid=${player.uniqueId} name=${player.name} " + + "connectionSession=${V26_1NmsReflections.getRemoteChatSession(connection)?.sessionId()} " + + "playerSession=${nmsPlayer.chatSession?.asData()?.sessionId()} " + + "nextChatIndex=$nextChatIndex " + + "decoderNextIndex=${nextLink?.index()} decoderNextSession=${nextLink?.sessionId()} " + + "incomingNextLink=${incomingChatChain?.nextLinkIndex} " + + "lastSeen=${lastSeenMessages.debugSummary()} " + + "cache=${messageSignatureCache.debugSummary()}" + } + + return PlayerChatSessionSnapshot( + nextChatIndex = nextChatIndex, + chatSession = chatSessionData?.let { + RemoteChatSessionData( + sessionId = it.sessionId(), + expiresAt = it.profilePublicKey().expiresAt(), + key = it.profilePublicKey().key(), + keySignature = it.profilePublicKey().keySignature() + ) + }, + lastSeenMessages = LastSeenMessagesValidatorMirror( + V26_1NmsReflections.getLastSeenCountFromMessageValidator(lastSeenMessages), + V26_1NmsReflections.getTrackedMessagesFromMessageValidator(lastSeenMessages) + .map { entry -> + entry?.let { + LastSeenMessagesValidatorMirror.LastSeenTrackedEntry( + it.signature().bytes(), + it.pending() + ) + } + }, + V26_1NmsReflections + .getLastPendingMessageFromMessageValidator(lastSeenMessages) + ?.bytes + ), + messageSignatureCache = Array(cacheEntries.size) { index -> + cacheEntries[index]?.bytes() + }, + incomingChatChain = incomingChatChain + ) + } + } } override fun applyChatSessionSnapshot(player: Player, snapshot: PlayerChatSessionSnapshot) { val nmsPlayer = player.toNms() val connection = nmsPlayer.connection ?: return - val lastSeenMessages = V26_1NmsReflections.getLastSeenMessages(connection) - val messageSignatureCache = V26_1NmsReflections.getMessageSignatureCache(connection) val sessionData = snapshot.chatSession + if (sessionData == null) { + chatDebug { + "APPLY_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_snapshot_session" + } + return + } - synchronized(messageSignatureCache) { - V26_1NmsReflections.setNextChatIndex(connection, snapshot.nextChatIndex) + val currentRemoteChatSession = V26_1NmsReflections.getRemoteChatSession(connection) + + chatDebug { + "APPLY_START uuid=${player.uniqueId} name=${player.name} " + + "snapshotSession=${sessionData.sessionId} " + + "currentConnectionSession=${currentRemoteChatSession?.sessionId()} " + + "currentPlayerSession=${nmsPlayer.chatSession?.asData()?.sessionId()} " + + "snapshotNextChatIndex=${snapshot.nextChatIndex} " + + "snapshotIncomingNextLink=${snapshot.incomingChatChain?.nextLinkIndex}" + } + + val remoteChatSession = when { + currentRemoteChatSession != null && + currentRemoteChatSession.sessionId() == sessionData.sessionId -> { + chatDebug { + "APPLY_SESSION_USE_CURRENT_MATCH uuid=${player.uniqueId} name=${player.name} " + + "session=${currentRemoteChatSession.sessionId()}" + } + + nmsPlayer.setChatSession(currentRemoteChatSession) + + MinecraftServer.getServer().playerList.broadcastAll( + ClientboundPlayerInfoUpdatePacket( + EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), + listOf(nmsPlayer) + ), + nmsPlayer + ) + + currentRemoteChatSession + } + + currentRemoteChatSession != null -> { + chatDebug { + "APPLY_SKIP uuid=${player.uniqueId} name=${player.name} reason=session_mismatch " + + "currentSession=${currentRemoteChatSession.sessionId()} " + + "snapshotSession=${sessionData.sessionId}" + } + + // Extrem wichtig: + // Bei neuer Vanilla-Session den alten Snapshot komplett verwerfen. + // Kein lastSeen, kein cache, kein nextChatIndex, kein decoder patch. + return + } + + else -> { + val profileKeyValidator = + MinecraftServer.getServer().services().profileKeySignatureValidator() + ?: run { + chatDebug { + "APPLY_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_profile_key_validator" + } + return + } + + val remoteChatSessionData = RemoteChatSession.Data( + sessionData.sessionId, + ProfilePublicKey.Data( + sessionData.expiresAt, + sessionData.key, + sessionData.keySignature + ) + ) + + val createdRemoteChatSession = remoteChatSessionData.validate( + nmsPlayer.gameProfile, + profileKeyValidator + ) + + V26_1NmsReflections.setRemoteChatSession( + connection, + createdRemoteChatSession + ) + + V26_1NmsReflections.setHasLoggedExpiry( + connection, + false + ) + + V26_1NmsReflections.setSignedMessageDecoder( + connection, + createdRemoteChatSession.createMessageDecoder(player.uniqueId) + ) + + nmsPlayer.setChatSession(createdRemoteChatSession) + + MinecraftServer.getServer().playerList.broadcastAll( + ClientboundPlayerInfoUpdatePacket( + EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), + listOf(nmsPlayer) + ), + nmsPlayer + ) + + chatDebug { + "APPLY_SESSION_CREATED uuid=${player.uniqueId} name=${player.name} " + + "session=${createdRemoteChatSession.sessionId()} " + + "decoderIndex=${connection.debugDecoderLink()?.index()} " + + "decoderSession=${connection.debugDecoderLink()?.sessionId()}" + } + + createdRemoteChatSession + } + } + + val lastSeenMessages = + V26_1NmsReflections.getLastSeenMessages(connection) + val messageSignatureCache = + V26_1NmsReflections.getMessageSignatureCache(connection) + + synchronized(messageSignatureCache) { synchronized(lastSeenMessages) { - val messages = V26_1NmsReflections.getTrackedMessagesFromMessageValidator(lastSeenMessages) + V26_1NmsReflections.setNextChatIndex( + connection, + snapshot.nextChatIndex + ) + + val messages = + V26_1NmsReflections.getTrackedMessagesFromMessageValidator(lastSeenMessages) + + messages.clear() for (entry in snapshot.lastSeenMessages.trackedMessages) { - val signature = entry.signature - val pending = entry.pending - messages.add(LastSeenTrackedEntry(MessageSignature(signature), pending)) + if (entry == null) { + messages.add(null) + } else { + messages.add( + LastSeenTrackedEntry( + MessageSignature(entry.signature), + + // Erstmal so lassen, weil das den + // "previously acknowledged" Fehler verhindert. + true + ) + ) + } } V26_1NmsReflections.setLastPendingMessageFromMessageValidator( lastSeenMessages, snapshot.lastSeenMessages.lastPendingMessage?.let(::MessageSignature) ) + + chatDebug { + "APPLY_LAST_SEEN_DONE uuid=${player.uniqueId} name=${player.name} " + + "afterLastSeen=${lastSeenMessages.debugSummary()}" + } } - if (sessionData != null) { - MinecraftServer.getServer().packetProcessor() - .scheduleIfPossible( - connection, - ServerboundChatSessionUpdatePacket( - RemoteChatSession.Data( - sessionData.sessionId, - ProfilePublicKey.Data( - sessionData.expiresAt, - sessionData.key, - sessionData.keySignature - ) - ) + val incomingChatChain = snapshot.incomingChatChain + + if (incomingChatChain != null) { + val decoder = + V26_1NmsReflections.getSignedMessageDecoder(connection) + + val signedMessageChain = + decoder.signedMessageChainOrNull() + + if (signedMessageChain != null) { + val beforeLink = + V26_1NmsReflections.getNextLinkFromSignedMessageChain(signedMessageChain) + + chatDebug { + "APPLY_CHAIN_PATCH_START uuid=${player.uniqueId} name=${player.name} " + + "beforeIndex=${beforeLink?.index()} beforeSession=${beforeLink?.sessionId()} " + + "targetIndex=${incomingChatChain.nextLinkIndex} " + + "targetSession=${remoteChatSession.sessionId()}" + } + + V26_1NmsReflections.setNextLinkFromSignedMessageChain( + signedMessageChain, + SignedMessageLink( + incomingChatChain.nextLinkIndex, + player.uniqueId, + remoteChatSession.sessionId() ) ) + + V26_1NmsReflections.setLastTimeStampFromSignedMessageChain( + signedMessageChain, + incomingChatChain.lastTimeStamp + ) + + val afterLink = + V26_1NmsReflections.getNextLinkFromSignedMessageChain(signedMessageChain) + + chatDebug { + "APPLY_CHAIN_PATCH_DONE uuid=${player.uniqueId} name=${player.name} " + + "afterIndex=${afterLink?.index()} afterSession=${afterLink?.sessionId()} " + + "lastTimeStamp=${incomingChatChain.lastTimeStamp}" + } + } else { + chatDebug { + "APPLY_CHAIN_PATCH_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_signed_message_chain" + } + } + } else { + chatDebug { + "APPLY_CHAIN_PATCH_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_incoming_chain" + } } val messageSignatureCacheQueue = ArrayDeque() - for (signatureBytes in snapshot.messageSignatureCache) { + + for (signatureBytes in snapshot.messageSignatureCache.reversed()) { if (signatureBytes != null) { messageSignatureCacheQueue.add(MessageSignature(signatureBytes)) } } - V26_1NmsReflections.pushMessageSignatureCache(messageSignatureCache, messageSignatureCacheQueue) + chatDebug { + "APPLY_CACHE_PUSH_START uuid=${player.uniqueId} name=${player.name} " + + "queueSize=${messageSignatureCacheQueue.size} " + + "beforeCache=${messageSignatureCache.debugSummary()}" + } + + V26_1NmsReflections.pushMessageSignatureCache( + messageSignatureCache, + messageSignatureCacheQueue + ) + + chatDebug { + "APPLY_CACHE_PUSH_DONE uuid=${player.uniqueId} name=${player.name} " + + "afterCache=${messageSignatureCache.debugSummary()} " + + "finalDecoderIndex=${connection.debugDecoderLink()?.index()} " + + "finalDecoderSession=${connection.debugDecoderLink()?.sessionId()} " + + "finalConnectionSession=${connection.debugRemoteSessionId()}" + } } } + override fun debugChatSessionState(player: Player, label: String) { + val nmsPlayer = player.toNms() + val connection = nmsPlayer.connection ?: return + + val lastSeenMessages = + V26_1NmsReflections.getLastSeenMessages(connection) + + val messageSignatureCache = + V26_1NmsReflections.getMessageSignatureCache(connection) + + val decoderLink = + connection.debugDecoderLink() + + chatDebug { + "STATE label=$label uuid=${player.uniqueId} name=${player.name} " + + "connectionSession=${connection.debugRemoteSessionId()} " + + "playerSession=${nmsPlayer.chatSession?.asData()?.sessionId()} " + + "decoderIndex=${decoderLink?.index()} " + + "decoderSession=${decoderLink?.sessionId()} " + + "nextChatIndex=${V26_1NmsReflections.getNextChatIndex(connection)} " + + "lastSeen=${lastSeenMessages.debugSummary()} " + + "cache=${messageSignatureCache.debugSummary()}" + } + } + + private fun restoreOrGetRemoteChatSession( + nmsPlayer: ServerPlayer, + connection: ServerGamePacketListenerImpl, + player: Player, + sessionData: RemoteChatSessionData? + ): RemoteChatSession? { + val currentRemoteChatSession = + V26_1NmsReflections.getRemoteChatSession(connection) + + chatDebug { + "RESTORE_REMOTE_SESSION_START uuid=${player.uniqueId} name=${player.name} " + + "currentConnectionSession=${currentRemoteChatSession?.sessionId()} " + + "currentPlayerSession=${nmsPlayer.chatSession?.asData()?.sessionId()} " + + "snapshotSession=${sessionData?.sessionId}" + } + + if (currentRemoteChatSession != null) { + nmsPlayer.setChatSession(currentRemoteChatSession) + + MinecraftServer.getServer().playerList.broadcastAll( + ClientboundPlayerInfoUpdatePacket( + EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), + listOf(nmsPlayer) + ), + nmsPlayer + ) + + chatDebug { + "RESTORE_REMOTE_SESSION_USE_CURRENT uuid=${player.uniqueId} name=${player.name} " + + "session=${currentRemoteChatSession.sessionId()}" + } + + return currentRemoteChatSession + } + + if (sessionData == null) { + chatDebug { + "RESTORE_REMOTE_SESSION_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_snapshot_session" + } + + return null + } + + val profileKeyValidator = + MinecraftServer.getServer().services().profileKeySignatureValidator() + ?: run { + chatDebug { + "RESTORE_REMOTE_SESSION_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_profile_key_validator" + } + + return null + } + + val remoteChatSessionData = RemoteChatSession.Data( + sessionData.sessionId, + ProfilePublicKey.Data( + sessionData.expiresAt, + sessionData.key, + sessionData.keySignature + ) + ) + + val remoteChatSession = remoteChatSessionData.validate( + nmsPlayer.gameProfile, + profileKeyValidator + ) + + V26_1NmsReflections.setRemoteChatSession( + connection, + remoteChatSession + ) + + V26_1NmsReflections.setHasLoggedExpiry( + connection, + false + ) + + V26_1NmsReflections.setSignedMessageDecoder( + connection, + remoteChatSession.createMessageDecoder(player.uniqueId) + ) + + nmsPlayer.setChatSession(remoteChatSession) + + MinecraftServer.getServer().playerList.broadcastAll( + ClientboundPlayerInfoUpdatePacket( + EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), + listOf(nmsPlayer) + ), + nmsPlayer + ) + + chatDebug { + "RESTORE_REMOTE_SESSION_CREATED uuid=${player.uniqueId} name=${player.name} " + + "session=${remoteChatSession.sessionId()} decoderIndex=${connection.debugDecoderLink()?.index()} " + + "decoderSession=${connection.debugDecoderLink()?.sessionId()}" + } + + return remoteChatSession + } + + private fun SignedMessageChain.Decoder.signedMessageChainOrNull(): SignedMessageChain? { + return runCatching { + val field = javaClass.getDeclaredField("this$0") + field.isAccessible = true + field.get(this) as? SignedMessageChain + }.getOrNull() + } + override fun runOnChatMessageChain(player: Player, scope: CoroutineScope, block: suspend () -> Unit) { val nmsPlayer = player.toNms() val connection = nmsPlayer.connection @@ -442,5 +865,6 @@ class V26_1SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { companion object { private val OFFLINE_INVENTORY_EDIT_LOGGER = ComponentLogger.logger("OfflinePlayer Inventory Edit") + private val CHAT_TRANSFER_LOGGER = ComponentLogger.logger("ChatSessionTransfer") } } \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt index aac53b6d..20b2b31c 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt @@ -15,6 +15,7 @@ import net.minecraft.stats.ServerStatsCounter import net.minecraft.util.FutureChain import net.minecraft.world.entity.Entity import java.lang.invoke.VarHandle +import java.time.Instant import java.util.* @Suppress("ClassName") @@ -49,7 +50,7 @@ interface V26_1NmsReflections { fun getLastSeenCountFromMessageValidator(validator: LastSeenMessagesValidator): Int @ReflectedField("trackedMessages") - fun getTrackedMessagesFromMessageValidator(validator: LastSeenMessagesValidator): ObjectList + fun getTrackedMessagesFromMessageValidator(validator: LastSeenMessagesValidator): ObjectList @ReflectedField("lastPendingMessage") fun getLastPendingMessageFromMessageValidator(validator: LastSeenMessagesValidator): MessageSignature? @@ -107,5 +108,53 @@ interface V26_1NmsReflections { ) fun getServerStatsCounterGson(): Gson + @ReflectedField("signedMessageDecoder") + fun getSignedMessageDecoder(instance: ServerGamePacketListenerImpl): SignedMessageChain.Decoder + + @ReflectedField("signedMessageDecoder", access = ReflectedFieldAccess.SET) + fun setSignedMessageDecoder( + instance: ServerGamePacketListenerImpl, + decoder: SignedMessageChain.Decoder + ) + + @ReflectedField("nextLink") + fun getNextLinkFromSignedMessageChain(chain: SignedMessageChain): SignedMessageLink? + + @ReflectedField("nextLink", access = ReflectedFieldAccess.SET) + fun setNextLinkFromSignedMessageChain( + chain: SignedMessageChain, + nextLink: SignedMessageLink? + ) + + @ReflectedField("lastTimeStamp") + fun getLastTimeStampFromSignedMessageChain(chain: SignedMessageChain): Instant + + @ReflectedField("lastTimeStamp", access = ReflectedFieldAccess.SET) + fun setLastTimeStampFromSignedMessageChain( + chain: SignedMessageChain, + lastTimeStamp: Instant + ) + + @ReflectedMethod("resetPlayerChatState") + fun resetPlayerChatState( + instance: ServerGamePacketListenerImpl, + chatSession: RemoteChatSession + ) + + @ReflectedField("chatSession") + fun getRemoteChatSession(instance: ServerGamePacketListenerImpl): RemoteChatSession? + + @ReflectedField("chatSession", access = ReflectedFieldAccess.SET) + fun setRemoteChatSession( + instance: ServerGamePacketListenerImpl, + chatSession: RemoteChatSession? + ) + + @ReflectedField("hasLoggedExpiry", access = ReflectedFieldAccess.SET) + fun setHasLoggedExpiry( + instance: ServerGamePacketListenerImpl, + value: Boolean + ) + companion object : V26_1NmsReflections by generatedReflectionAccessor() } \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt index 465503c9..232392ef 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt @@ -27,6 +27,8 @@ interface SurfPaperNmsPlayerBridge { fun createChatSessionSnapshot(player: Player): PlayerChatSessionSnapshot fun applyChatSessionSnapshot(player: Player, snapshot: PlayerChatSessionSnapshot) + fun debugChatSessionState(player: Player, label: String) + fun runOnChatMessageChain(player: Player, scope: CoroutineScope, block: suspend () -> Unit) fun sendSignedMessageWithChangedContent( diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt new file mode 100644 index 00000000..5ea13d07 --- /dev/null +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt @@ -0,0 +1,13 @@ +package dev.slne.surf.api.paper.nms.bridges.data.chat + +import dev.slne.surf.api.paper.nms.NmsUseWithCaution +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable +import java.time.Instant + +@NmsUseWithCaution +@Serializable +data class IncomingChatChainMirror( + val nextLinkIndex: Int, + val lastTimeStamp: @Contextual Instant, +) \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt index bc7b6dec..0917f658 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt @@ -1,15 +1,18 @@ package dev.slne.surf.api.paper.nms.bridges.data.chat +import dev.slne.surf.api.paper.nms.NmsUseWithCaution import kotlinx.serialization.Serializable @Serializable +@NmsUseWithCaution data class LastSeenMessagesValidatorMirror( val lastSeenCount: Int, - val trackedMessages: List, + val trackedMessages: List, val lastPendingMessage: ByteArray? = null, ) { @Serializable + @NmsUseWithCaution data class LastSeenTrackedEntry( val signature: ByteArray, val pending: Boolean diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt index 44cbb2ef..e111d8a7 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt @@ -10,6 +10,7 @@ data class PlayerChatSessionSnapshot( val chatSession: RemoteChatSessionData?, val lastSeenMessages: LastSeenMessagesValidatorMirror, val messageSignatureCache: Array, + val incomingChatChain: IncomingChatChainMirror? ) { override fun equals(other: Any?): Boolean { if (this === other) return true From 31cbb9798b5e0bfae7c7c5015d867e269c805d08 Mon Sep 17 00:00:00 2001 From: twisti-dev <76837088+twisti-dev@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:34:27 +0200 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9C=A8=20feat(chat):=20update=20Incoming?= =?UTF-8?q?ChatChainMirror=20and=20PlayerChatSessionSnapshot=20structures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - make nextLinkIndex nullable in IncomingChatChainMirror - remove nullable type for incomingChatChain in PlayerChatSessionSnapshot - implement resetPlayerChatState method in SurfPaperNmsPlayerBridge --- .../V1_21_11SurfPaperNmsPlayerBridgeImpl.kt | 39 +- .../reflection/V1_21_11NmsReflections.kt | 11 +- .../V26_1SurfPaperNmsPlayerBridgeImpl.kt | 524 +----------------- .../v26_1/reflection/V26_1NmsReflections.kt | 65 --- .../nms/bridges/SurfPaperNmsPlayerBridge.kt | 6 +- .../data/chat/IncomingChatChainMirror.kt | 2 +- .../data/chat/PlayerChatSessionSnapshot.kt | 2 +- 7 files changed, 56 insertions(+), 593 deletions(-) diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt index 103b8fb9..f9f38103 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt @@ -8,7 +8,6 @@ import dev.slne.surf.api.paper.nms.NmsUseWithCaution import dev.slne.surf.api.paper.nms.bridges.SurfPaperNmsPlayerBridge import dev.slne.surf.api.paper.nms.bridges.SurfPaperNmsPlayerBridge.PlayerInventoryEdit import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatMessageMirror -import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatSessionSnapshot import dev.slne.surf.api.paper.nms.bridges.data.chat.RemoteChatSessionData import dev.slne.surf.api.paper.nms.common.dummy.DummyEntityEquipment import dev.slne.surf.api.paper.server.nms.v1_21_11.extensions.toNms @@ -37,6 +36,7 @@ import net.minecraft.world.entity.EntityEquipment import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.npc.InventoryCarrier import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.ProfilePublicKey import net.minecraft.world.level.storage.* import org.bukkit.craftbukkit.CraftEquipmentSlot import org.bukkit.craftbukkit.inventory.CraftItemStack @@ -64,19 +64,33 @@ class V1_21_11SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { ) } - override fun createChatSessionSnapshot(player: Player): PlayerChatSessionSnapshot { - TODO("Not yet implemented") - } + @Suppress("USELESS_ELVIS") + override fun resetPlayerChatState(player: Player, chatSession: RemoteChatSessionData) { + val nmsPlayer = player.toNms() + val connection = nmsPlayer.connection ?: return - override fun applyChatSessionSnapshot( - player: Player, - snapshot: PlayerChatSessionSnapshot - ) { - TODO("Not yet implemented") - } + val newChatSessionData = RemoteChatSession.Data( + chatSession.sessionId, + ProfilePublicKey.Data( + chatSession.expiresAt, + chatSession.key, + chatSession.keySignature + ) + ) + + + val signatureValidator = MinecraftServer.getServer().services().profileKeySignatureValidator() + if (signatureValidator == null) { + CHAT_LOGGER.warn("Ignoring chat session from {} due to missing Services public key", player.name) + return + } + + val newChatSession = newChatSessionData.validate( + nmsPlayer.gameProfile, + signatureValidator + ) - override fun debugChatSessionState(player: Player, label: String) { - TODO("Not yet implemented") + V1_21_11NmsReflections.resetPlayerChatState(connection, newChatSession) } override fun runOnChatMessageChain(player: Player, scope: CoroutineScope, block: suspend () -> Unit) { @@ -363,5 +377,6 @@ class V1_21_11SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { companion object { private val OFFLINE_INVENTORY_EDIT_LOGGER = ComponentLogger.logger("OfflinePlayer Inventory Edit") + private val CHAT_LOGGER = ComponentLogger.logger("SurfPaperNmsPlayerBridge Chat") } } \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/reflection/V1_21_11NmsReflections.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/reflection/V1_21_11NmsReflections.kt index 3a7c5e3a..25aaf114 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/reflection/V1_21_11NmsReflections.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/reflection/V1_21_11NmsReflections.kt @@ -5,10 +5,7 @@ import com.google.gson.JsonElement import dev.slne.surf.api.shared.api.reflection.* import io.netty.channel.ChannelFuture import io.papermc.paper.adventure.ChatProcessor -import net.minecraft.network.chat.ChatType -import net.minecraft.network.chat.FilterMask -import net.minecraft.network.chat.LastSeenMessagesValidator -import net.minecraft.network.chat.MessageSignatureCache +import net.minecraft.network.chat.* import net.minecraft.network.syncher.EntityDataAccessor import net.minecraft.resources.ResourceKey import net.minecraft.server.network.ServerConnectionListener @@ -86,5 +83,11 @@ interface V1_21_11NmsReflections { ) fun getServerStatsCounterGson(): Gson + @ReflectedMethod("resetPlayerChatState") + fun resetPlayerChatState( + instance: ServerGamePacketListenerImpl, + chatSession: RemoteChatSession + ) + companion object : V1_21_11NmsReflections by generatedReflectionAccessor() } \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt index 2598377a..4fc2721a 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt @@ -25,10 +25,7 @@ import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.NbtIo import net.minecraft.network.chat.* import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket -import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket import net.minecraft.server.MinecraftServer -import net.minecraft.server.level.ServerPlayer -import net.minecraft.server.network.ServerGamePacketListenerImpl import net.minecraft.server.players.NameAndId import net.minecraft.util.ProblemReporter import net.minecraft.util.ProblemReporter.ScopedCollector @@ -53,66 +50,10 @@ import kotlin.io.path.createTempFile import kotlin.jvm.optionals.getOrNull @NmsUseWithCaution +@Suppress("ClassName") class V26_1SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { - - private fun chatDebug(message: () -> String) { - CHAT_TRANSFER_LOGGER.info("[chat-xfer] {}", message()) - } - - private fun ByteArray?.shortSig(): String { - if (this == null) return "null" - - return take(6).joinToString("") { - "%02x".format(it.toInt() and 0xff) - } - } - - private fun LastSeenMessagesValidator.debugSummary(): String { - val tracked = - V26_1NmsReflections.getTrackedMessagesFromMessageValidator(this) - - val nonNull = - tracked.count { it != null } - - val pending = - tracked.count { it?.pending() == true } - - val lastPending = - V26_1NmsReflections.getLastPendingMessageFromMessageValidator(this) - - return "lastSeenCount=${V26_1NmsReflections.getLastSeenCountFromMessageValidator(this)} " + - "trackedSize=${tracked.size} trackedNonNull=$nonNull trackedPending=$pending " + - "lastPending=${lastPending?.bytes?.shortSig()}" - } - - private fun MessageSignatureCache.debugSummary(): String { - val entries = - V26_1NmsReflections.getEntriesFromMessageSignatureCache(this) - - val nonNull = - entries.count { it != null } - - return "cacheSize=${entries.size} cacheNonNull=$nonNull first=${entries.firstOrNull()?.bytes()?.shortSig()}" - } - - private fun ServerGamePacketListenerImpl.debugRemoteSessionId(): UUID? { - return V26_1NmsReflections.getRemoteChatSession(this)?.sessionId() - } - - private fun ServerGamePacketListenerImpl.debugDecoderLink(): SignedMessageLink? { - val decoder = - V26_1NmsReflections.getSignedMessageDecoder(this) - - val chain = - decoder.signedMessageChainOrNull() - - return chain?.let { - V26_1NmsReflections.getNextLinkFromSignedMessageChain(it) - } - } - - + @Suppress("USELESS_ELVIS") override fun getRemoteChatSessionData(player: Player): RemoteChatSessionData? { val connection = player.toNms().connection ?: return null val session = V26_1NmsReflections.getRemoteChatSession(connection) ?: return null @@ -126,459 +67,32 @@ class V26_1SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { ) } - override fun createChatSessionSnapshot(player: Player): PlayerChatSessionSnapshot { - val nmsPlayer = player.toNms() - val connection = requireNotNull(nmsPlayer.connection) { - "Player connection is not available" - } - - val lastSeenMessages = V26_1NmsReflections.getLastSeenMessages(connection) - val messageSignatureCache = V26_1NmsReflections.getMessageSignatureCache(connection) - - synchronized(messageSignatureCache) { - synchronized(lastSeenMessages) { - val nextChatIndex = V26_1NmsReflections.getNextChatIndex(connection) - val chatSessionData = V26_1NmsReflections.getRemoteChatSession(connection)?.asData() - - val cacheEntries = - V26_1NmsReflections.getEntriesFromMessageSignatureCache(messageSignatureCache) - - val decoder = V26_1NmsReflections.getSignedMessageDecoder(connection) - val signedMessageChain = decoder.signedMessageChainOrNull() - val nextLink = signedMessageChain?.let { - V26_1NmsReflections.getNextLinkFromSignedMessageChain(it) - } - - val incomingChatChain = if (signedMessageChain != null && nextLink != null) { - IncomingChatChainMirror( - nextLinkIndex = nextLink.index(), - lastTimeStamp = V26_1NmsReflections.getLastTimeStampFromSignedMessageChain(signedMessageChain) - ) - } else { - null - } - - chatDebug { - "CREATE_SNAPSHOT uuid=${player.uniqueId} name=${player.name} " + - "connectionSession=${V26_1NmsReflections.getRemoteChatSession(connection)?.sessionId()} " + - "playerSession=${nmsPlayer.chatSession?.asData()?.sessionId()} " + - "nextChatIndex=$nextChatIndex " + - "decoderNextIndex=${nextLink?.index()} decoderNextSession=${nextLink?.sessionId()} " + - "incomingNextLink=${incomingChatChain?.nextLinkIndex} " + - "lastSeen=${lastSeenMessages.debugSummary()} " + - "cache=${messageSignatureCache.debugSummary()}" - } - - return PlayerChatSessionSnapshot( - nextChatIndex = nextChatIndex, - chatSession = chatSessionData?.let { - RemoteChatSessionData( - sessionId = it.sessionId(), - expiresAt = it.profilePublicKey().expiresAt(), - key = it.profilePublicKey().key(), - keySignature = it.profilePublicKey().keySignature() - ) - }, - lastSeenMessages = LastSeenMessagesValidatorMirror( - V26_1NmsReflections.getLastSeenCountFromMessageValidator(lastSeenMessages), - V26_1NmsReflections.getTrackedMessagesFromMessageValidator(lastSeenMessages) - .map { entry -> - entry?.let { - LastSeenMessagesValidatorMirror.LastSeenTrackedEntry( - it.signature().bytes(), - it.pending() - ) - } - }, - V26_1NmsReflections - .getLastPendingMessageFromMessageValidator(lastSeenMessages) - ?.bytes - ), - messageSignatureCache = Array(cacheEntries.size) { index -> - cacheEntries[index]?.bytes() - }, - incomingChatChain = incomingChatChain - ) - } - } - } - - override fun applyChatSessionSnapshot(player: Player, snapshot: PlayerChatSessionSnapshot) { - val nmsPlayer = player.toNms() - val connection = nmsPlayer.connection ?: return - - val sessionData = snapshot.chatSession - if (sessionData == null) { - chatDebug { - "APPLY_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_snapshot_session" - } - return - } - - val currentRemoteChatSession = V26_1NmsReflections.getRemoteChatSession(connection) - - chatDebug { - "APPLY_START uuid=${player.uniqueId} name=${player.name} " + - "snapshotSession=${sessionData.sessionId} " + - "currentConnectionSession=${currentRemoteChatSession?.sessionId()} " + - "currentPlayerSession=${nmsPlayer.chatSession?.asData()?.sessionId()} " + - "snapshotNextChatIndex=${snapshot.nextChatIndex} " + - "snapshotIncomingNextLink=${snapshot.incomingChatChain?.nextLinkIndex}" - } - - val remoteChatSession = when { - currentRemoteChatSession != null && - currentRemoteChatSession.sessionId() == sessionData.sessionId -> { - chatDebug { - "APPLY_SESSION_USE_CURRENT_MATCH uuid=${player.uniqueId} name=${player.name} " + - "session=${currentRemoteChatSession.sessionId()}" - } - - nmsPlayer.setChatSession(currentRemoteChatSession) - - MinecraftServer.getServer().playerList.broadcastAll( - ClientboundPlayerInfoUpdatePacket( - EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), - listOf(nmsPlayer) - ), - nmsPlayer - ) - - currentRemoteChatSession - } - - currentRemoteChatSession != null -> { - chatDebug { - "APPLY_SKIP uuid=${player.uniqueId} name=${player.name} reason=session_mismatch " + - "currentSession=${currentRemoteChatSession.sessionId()} " + - "snapshotSession=${sessionData.sessionId}" - } - - // Extrem wichtig: - // Bei neuer Vanilla-Session den alten Snapshot komplett verwerfen. - // Kein lastSeen, kein cache, kein nextChatIndex, kein decoder patch. - return - } - - else -> { - val profileKeyValidator = - MinecraftServer.getServer().services().profileKeySignatureValidator() - ?: run { - chatDebug { - "APPLY_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_profile_key_validator" - } - return - } - - val remoteChatSessionData = RemoteChatSession.Data( - sessionData.sessionId, - ProfilePublicKey.Data( - sessionData.expiresAt, - sessionData.key, - sessionData.keySignature - ) - ) - - val createdRemoteChatSession = remoteChatSessionData.validate( - nmsPlayer.gameProfile, - profileKeyValidator - ) - - V26_1NmsReflections.setRemoteChatSession( - connection, - createdRemoteChatSession - ) - - V26_1NmsReflections.setHasLoggedExpiry( - connection, - false - ) - - V26_1NmsReflections.setSignedMessageDecoder( - connection, - createdRemoteChatSession.createMessageDecoder(player.uniqueId) - ) - - nmsPlayer.setChatSession(createdRemoteChatSession) - - MinecraftServer.getServer().playerList.broadcastAll( - ClientboundPlayerInfoUpdatePacket( - EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), - listOf(nmsPlayer) - ), - nmsPlayer - ) - - chatDebug { - "APPLY_SESSION_CREATED uuid=${player.uniqueId} name=${player.name} " + - "session=${createdRemoteChatSession.sessionId()} " + - "decoderIndex=${connection.debugDecoderLink()?.index()} " + - "decoderSession=${connection.debugDecoderLink()?.sessionId()}" - } - - createdRemoteChatSession - } - } - - val lastSeenMessages = - V26_1NmsReflections.getLastSeenMessages(connection) - - val messageSignatureCache = - V26_1NmsReflections.getMessageSignatureCache(connection) - - synchronized(messageSignatureCache) { - synchronized(lastSeenMessages) { - V26_1NmsReflections.setNextChatIndex( - connection, - snapshot.nextChatIndex - ) - - val messages = - V26_1NmsReflections.getTrackedMessagesFromMessageValidator(lastSeenMessages) - - messages.clear() - - for (entry in snapshot.lastSeenMessages.trackedMessages) { - if (entry == null) { - messages.add(null) - } else { - messages.add( - LastSeenTrackedEntry( - MessageSignature(entry.signature), - - // Erstmal so lassen, weil das den - // "previously acknowledged" Fehler verhindert. - true - ) - ) - } - } - - V26_1NmsReflections.setLastPendingMessageFromMessageValidator( - lastSeenMessages, - snapshot.lastSeenMessages.lastPendingMessage?.let(::MessageSignature) - ) - - chatDebug { - "APPLY_LAST_SEEN_DONE uuid=${player.uniqueId} name=${player.name} " + - "afterLastSeen=${lastSeenMessages.debugSummary()}" - } - } - - val incomingChatChain = snapshot.incomingChatChain - - if (incomingChatChain != null) { - val decoder = - V26_1NmsReflections.getSignedMessageDecoder(connection) - - val signedMessageChain = - decoder.signedMessageChainOrNull() - - if (signedMessageChain != null) { - val beforeLink = - V26_1NmsReflections.getNextLinkFromSignedMessageChain(signedMessageChain) - - chatDebug { - "APPLY_CHAIN_PATCH_START uuid=${player.uniqueId} name=${player.name} " + - "beforeIndex=${beforeLink?.index()} beforeSession=${beforeLink?.sessionId()} " + - "targetIndex=${incomingChatChain.nextLinkIndex} " + - "targetSession=${remoteChatSession.sessionId()}" - } - - V26_1NmsReflections.setNextLinkFromSignedMessageChain( - signedMessageChain, - SignedMessageLink( - incomingChatChain.nextLinkIndex, - player.uniqueId, - remoteChatSession.sessionId() - ) - ) - - V26_1NmsReflections.setLastTimeStampFromSignedMessageChain( - signedMessageChain, - incomingChatChain.lastTimeStamp - ) - - val afterLink = - V26_1NmsReflections.getNextLinkFromSignedMessageChain(signedMessageChain) - - chatDebug { - "APPLY_CHAIN_PATCH_DONE uuid=${player.uniqueId} name=${player.name} " + - "afterIndex=${afterLink?.index()} afterSession=${afterLink?.sessionId()} " + - "lastTimeStamp=${incomingChatChain.lastTimeStamp}" - } - } else { - chatDebug { - "APPLY_CHAIN_PATCH_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_signed_message_chain" - } - } - } else { - chatDebug { - "APPLY_CHAIN_PATCH_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_incoming_chain" - } - } - - val messageSignatureCacheQueue = ArrayDeque() - - for (signatureBytes in snapshot.messageSignatureCache.reversed()) { - if (signatureBytes != null) { - messageSignatureCacheQueue.add(MessageSignature(signatureBytes)) - } - } - - chatDebug { - "APPLY_CACHE_PUSH_START uuid=${player.uniqueId} name=${player.name} " + - "queueSize=${messageSignatureCacheQueue.size} " + - "beforeCache=${messageSignatureCache.debugSummary()}" - } - - V26_1NmsReflections.pushMessageSignatureCache( - messageSignatureCache, - messageSignatureCacheQueue - ) - - chatDebug { - "APPLY_CACHE_PUSH_DONE uuid=${player.uniqueId} name=${player.name} " + - "afterCache=${messageSignatureCache.debugSummary()} " + - "finalDecoderIndex=${connection.debugDecoderLink()?.index()} " + - "finalDecoderSession=${connection.debugDecoderLink()?.sessionId()} " + - "finalConnectionSession=${connection.debugRemoteSessionId()}" - } - } - } - - override fun debugChatSessionState(player: Player, label: String) { + @Suppress("USELESS_ELVIS") + override fun resetPlayerChatState(player: Player, chatSession: RemoteChatSessionData) { val nmsPlayer = player.toNms() val connection = nmsPlayer.connection ?: return - val lastSeenMessages = - V26_1NmsReflections.getLastSeenMessages(connection) - - val messageSignatureCache = - V26_1NmsReflections.getMessageSignatureCache(connection) - - val decoderLink = - connection.debugDecoderLink() - - chatDebug { - "STATE label=$label uuid=${player.uniqueId} name=${player.name} " + - "connectionSession=${connection.debugRemoteSessionId()} " + - "playerSession=${nmsPlayer.chatSession?.asData()?.sessionId()} " + - "decoderIndex=${decoderLink?.index()} " + - "decoderSession=${decoderLink?.sessionId()} " + - "nextChatIndex=${V26_1NmsReflections.getNextChatIndex(connection)} " + - "lastSeen=${lastSeenMessages.debugSummary()} " + - "cache=${messageSignatureCache.debugSummary()}" - } - } - - private fun restoreOrGetRemoteChatSession( - nmsPlayer: ServerPlayer, - connection: ServerGamePacketListenerImpl, - player: Player, - sessionData: RemoteChatSessionData? - ): RemoteChatSession? { - val currentRemoteChatSession = - V26_1NmsReflections.getRemoteChatSession(connection) - - chatDebug { - "RESTORE_REMOTE_SESSION_START uuid=${player.uniqueId} name=${player.name} " + - "currentConnectionSession=${currentRemoteChatSession?.sessionId()} " + - "currentPlayerSession=${nmsPlayer.chatSession?.asData()?.sessionId()} " + - "snapshotSession=${sessionData?.sessionId}" - } - - if (currentRemoteChatSession != null) { - nmsPlayer.setChatSession(currentRemoteChatSession) - - MinecraftServer.getServer().playerList.broadcastAll( - ClientboundPlayerInfoUpdatePacket( - EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), - listOf(nmsPlayer) - ), - nmsPlayer - ) - - chatDebug { - "RESTORE_REMOTE_SESSION_USE_CURRENT uuid=${player.uniqueId} name=${player.name} " + - "session=${currentRemoteChatSession.sessionId()}" - } - - return currentRemoteChatSession - } - - if (sessionData == null) { - chatDebug { - "RESTORE_REMOTE_SESSION_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_snapshot_session" - } - - return null - } - - val profileKeyValidator = - MinecraftServer.getServer().services().profileKeySignatureValidator() - ?: run { - chatDebug { - "RESTORE_REMOTE_SESSION_SKIP uuid=${player.uniqueId} name=${player.name} reason=no_profile_key_validator" - } - - return null - } - - val remoteChatSessionData = RemoteChatSession.Data( - sessionData.sessionId, + val newChatSessionData = RemoteChatSession.Data( + chatSession.sessionId, ProfilePublicKey.Data( - sessionData.expiresAt, - sessionData.key, - sessionData.keySignature + chatSession.expiresAt, + chatSession.key, + chatSession.keySignature ) ) - val remoteChatSession = remoteChatSessionData.validate( - nmsPlayer.gameProfile, - profileKeyValidator - ) - - V26_1NmsReflections.setRemoteChatSession( - connection, - remoteChatSession - ) - - V26_1NmsReflections.setHasLoggedExpiry( - connection, - false - ) - - V26_1NmsReflections.setSignedMessageDecoder( - connection, - remoteChatSession.createMessageDecoder(player.uniqueId) - ) - - nmsPlayer.setChatSession(remoteChatSession) - - MinecraftServer.getServer().playerList.broadcastAll( - ClientboundPlayerInfoUpdatePacket( - EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), - listOf(nmsPlayer) - ), - nmsPlayer - ) - - chatDebug { - "RESTORE_REMOTE_SESSION_CREATED uuid=${player.uniqueId} name=${player.name} " + - "session=${remoteChatSession.sessionId()} decoderIndex=${connection.debugDecoderLink()?.index()} " + - "decoderSession=${connection.debugDecoderLink()?.sessionId()}" + val signatureValidator = MinecraftServer.getServer().services().profileKeySignatureValidator() + if (signatureValidator == null) { + CHAT_LOGGER.warn("Ignoring chat session from {} due to missing Services public key", player.name) + return } - return remoteChatSession - } + val newChatSession = newChatSessionData.validate( + nmsPlayer.gameProfile, + signatureValidator + ) - private fun SignedMessageChain.Decoder.signedMessageChainOrNull(): SignedMessageChain? { - return runCatching { - val field = javaClass.getDeclaredField("this$0") - field.isAccessible = true - field.get(this) as? SignedMessageChain - }.getOrNull() + V26_1NmsReflections.resetPlayerChatState(connection, newChatSession,) } override fun runOnChatMessageChain(player: Player, scope: CoroutineScope, block: suspend () -> Unit) { @@ -865,6 +379,6 @@ class V26_1SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { companion object { private val OFFLINE_INVENTORY_EDIT_LOGGER = ComponentLogger.logger("OfflinePlayer Inventory Edit") - private val CHAT_TRANSFER_LOGGER = ComponentLogger.logger("ChatSessionTransfer") + private val CHAT_LOGGER = ComponentLogger.logger("SurfPaperNmsPlayerBridge Chat") } } \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt index 20b2b31c..b0844ca9 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/reflection/V26_1NmsReflections.kt @@ -5,7 +5,6 @@ import com.google.gson.JsonElement import dev.slne.surf.api.shared.api.reflection.* import io.netty.channel.ChannelFuture import io.papermc.paper.adventure.ChatProcessor -import it.unimi.dsi.fastutil.objects.ObjectList import net.minecraft.network.chat.* import net.minecraft.network.syncher.EntityDataAccessor import net.minecraft.resources.ResourceKey @@ -15,7 +14,6 @@ import net.minecraft.stats.ServerStatsCounter import net.minecraft.util.FutureChain import net.minecraft.world.entity.Entity import java.lang.invoke.VarHandle -import java.time.Instant import java.util.* @Suppress("ClassName") @@ -31,36 +29,12 @@ interface V26_1NmsReflections { @ConstantIntArgument(1) fun getAndIncreaseNextChatIndex(instance: ServerGamePacketListenerImpl): Int - @ReflectedField("nextChatIndex") - fun getNextChatIndex(instance: ServerGamePacketListenerImpl): Int - - @ReflectedField("nextChatIndex", access = ReflectedFieldAccess.SET) - fun setNextChatIndex(instance: ServerGamePacketListenerImpl, index: Int) - @ReflectedField("messageSignatureCache") fun getMessageSignatureCache(instance: ServerGamePacketListenerImpl): MessageSignatureCache - @ReflectedMethod("push") - fun pushMessageSignatureCache(instance: MessageSignatureCache, deque: ArrayDeque) - @ReflectedField(name = "lastSeenMessages") fun getLastSeenMessages(instance: ServerGamePacketListenerImpl): LastSeenMessagesValidator - @ReflectedField("lastSeenCount") - fun getLastSeenCountFromMessageValidator(validator: LastSeenMessagesValidator): Int - - @ReflectedField("trackedMessages") - fun getTrackedMessagesFromMessageValidator(validator: LastSeenMessagesValidator): ObjectList - - @ReflectedField("lastPendingMessage") - fun getLastPendingMessageFromMessageValidator(validator: LastSeenMessagesValidator): MessageSignature? - - @ReflectedField("lastPendingMessage", access = ReflectedFieldAccess.SET) - fun setLastPendingMessageFromMessageValidator(validator: LastSeenMessagesValidator, message: MessageSignature?) - - @ReflectedField("entries") - fun getEntriesFromMessageSignatureCache(cache: MessageSignatureCache): Array - @ReflectedConstructor fun createFilterMask(bitSet: BitSet): FilterMask @@ -108,33 +82,6 @@ interface V26_1NmsReflections { ) fun getServerStatsCounterGson(): Gson - @ReflectedField("signedMessageDecoder") - fun getSignedMessageDecoder(instance: ServerGamePacketListenerImpl): SignedMessageChain.Decoder - - @ReflectedField("signedMessageDecoder", access = ReflectedFieldAccess.SET) - fun setSignedMessageDecoder( - instance: ServerGamePacketListenerImpl, - decoder: SignedMessageChain.Decoder - ) - - @ReflectedField("nextLink") - fun getNextLinkFromSignedMessageChain(chain: SignedMessageChain): SignedMessageLink? - - @ReflectedField("nextLink", access = ReflectedFieldAccess.SET) - fun setNextLinkFromSignedMessageChain( - chain: SignedMessageChain, - nextLink: SignedMessageLink? - ) - - @ReflectedField("lastTimeStamp") - fun getLastTimeStampFromSignedMessageChain(chain: SignedMessageChain): Instant - - @ReflectedField("lastTimeStamp", access = ReflectedFieldAccess.SET) - fun setLastTimeStampFromSignedMessageChain( - chain: SignedMessageChain, - lastTimeStamp: Instant - ) - @ReflectedMethod("resetPlayerChatState") fun resetPlayerChatState( instance: ServerGamePacketListenerImpl, @@ -144,17 +91,5 @@ interface V26_1NmsReflections { @ReflectedField("chatSession") fun getRemoteChatSession(instance: ServerGamePacketListenerImpl): RemoteChatSession? - @ReflectedField("chatSession", access = ReflectedFieldAccess.SET) - fun setRemoteChatSession( - instance: ServerGamePacketListenerImpl, - chatSession: RemoteChatSession? - ) - - @ReflectedField("hasLoggedExpiry", access = ReflectedFieldAccess.SET) - fun setHasLoggedExpiry( - instance: ServerGamePacketListenerImpl, - value: Boolean - ) - companion object : V26_1NmsReflections by generatedReflectionAccessor() } \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt index 232392ef..43a84730 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt @@ -5,7 +5,6 @@ import dev.slne.surf.api.core.util.requiredService import dev.slne.surf.api.paper.nms.NmsUseWithCaution import dev.slne.surf.api.paper.nms.bridges.SurfPaperNmsPlayerBridge.Companion.editOfflineInventory import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatMessageMirror -import dev.slne.surf.api.paper.nms.bridges.data.chat.PlayerChatSessionSnapshot import dev.slne.surf.api.paper.nms.bridges.data.chat.RemoteChatSessionData import kotlinx.coroutines.CoroutineScope import net.kyori.adventure.chat.ChatType @@ -24,10 +23,7 @@ interface SurfPaperNmsPlayerBridge { fun getRemoteChatSessionData(player: Player): RemoteChatSessionData? - fun createChatSessionSnapshot(player: Player): PlayerChatSessionSnapshot - fun applyChatSessionSnapshot(player: Player, snapshot: PlayerChatSessionSnapshot) - - fun debugChatSessionState(player: Player, label: String) + fun resetPlayerChatState(player: Player, chatSession: RemoteChatSessionData) fun runOnChatMessageChain(player: Player, scope: CoroutineScope, block: suspend () -> Unit) diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt index 5ea13d07..94718e3e 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt @@ -8,6 +8,6 @@ import java.time.Instant @NmsUseWithCaution @Serializable data class IncomingChatChainMirror( - val nextLinkIndex: Int, + val nextLinkIndex: Int?, val lastTimeStamp: @Contextual Instant, ) \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt index e111d8a7..9cd8c1fd 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt @@ -10,7 +10,7 @@ data class PlayerChatSessionSnapshot( val chatSession: RemoteChatSessionData?, val lastSeenMessages: LastSeenMessagesValidatorMirror, val messageSignatureCache: Array, - val incomingChatChain: IncomingChatChainMirror? + val incomingChatChain: IncomingChatChainMirror ) { override fun equals(other: Any?): Boolean { if (this === other) return true From e03f67feba1c31a5caf5017fba587b79b49fed87 Mon Sep 17 00:00:00 2001 From: twisti-dev <76837088+twisti-dev@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:48:07 +0200 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=94=A7=20chore(abi):=20update=20api?= =?UTF-8?q?=20dump?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../surf-api-core/api/surf-api-core.api | 4 ++ .../surf-api-paper/api/surf-api-paper.api | 2 + .../data/chat/IncomingChatChainMirror.kt | 13 ----- .../chat/LastSeenMessagesValidatorMirror.kt | 54 ------------------- .../data/chat/PlayerChatSessionSnapshot.kt | 34 ------------ 5 files changed, 6 insertions(+), 101 deletions(-) delete mode 100644 surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt delete mode 100644 surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt delete mode 100644 surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt diff --git a/surf-api-core/surf-api-core/api/surf-api-core.api b/surf-api-core/surf-api-core/api/surf-api-core.api index 9446f803..d6c294a3 100644 --- a/surf-api-core/surf-api-core/api/surf-api-core.api +++ b/surf-api-core/surf-api-core/api/surf-api-core.api @@ -11035,6 +11035,10 @@ public abstract interface class dev/slne/surf/api/core/util/ToByteFunction { public abstract fun applyAsByte (Ljava/lang/Object;)B } +public final class dev/slne/surf/api/core/util/Var_handle_utilKt { + public static final fun setIfGreater (Ljava/lang/invoke/VarHandle;Ljava/lang/Object;I)Z +} + public abstract interface class dev/slne/surf/api/core/util/blockstate/BlockStateFactory { public static final field Companion Ldev/slne/surf/api/core/util/blockstate/BlockStateFactory$Companion; public static fun builder (Lcom/github/retrooper/packetevents/protocol/world/states/WrappedBlockState;)Ldev/slne/surf/api/core/util/blockstate/BlockStateFactory$Builder; diff --git a/surf-api-paper/surf-api-paper/api/surf-api-paper.api b/surf-api-paper/surf-api-paper/api/surf-api-paper.api index 0d2a6892..b60c8551 100644 --- a/surf-api-paper/surf-api-paper/api/surf-api-paper.api +++ b/surf-api-paper/surf-api-paper/api/surf-api-paper.api @@ -1735,6 +1735,7 @@ public abstract interface class dev/slne/surf/api/paper/nms/bridges/SurfPaperNms public abstract fun getRemoteChatSessionData (Lorg/bukkit/entity/Player;)Ldev/slne/surf/api/paper/nms/bridges/data/chat/RemoteChatSessionData; public abstract fun getStatsDataPath ()Ljava/nio/file/Path; public abstract fun increaseNextChatIndex (Lorg/bukkit/entity/Player;)Ljava/lang/Integer; + public abstract fun resetPlayerChatState (Lorg/bukkit/entity/Player;Ldev/slne/surf/api/paper/nms/bridges/data/chat/RemoteChatSessionData;)V public abstract fun runOnChatMessageChain (Lorg/bukkit/entity/Player;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)V public abstract fun sendPlayerChatMessage (Lorg/bukkit/entity/Player;Lnet/kyori/adventure/chat/SignedMessage;Lnet/kyori/adventure/chat/ChatType$Bound;)V public abstract fun sendSignedMessageWithChangedContent (Lorg/bukkit/entity/Player;Lnet/kyori/adventure/chat/SignedMessage;Lnet/kyori/adventure/chat/ChatType$Bound;Lnet/kyori/adventure/text/Component;)V @@ -1750,6 +1751,7 @@ public final class dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge$ public fun getRemoteChatSessionData (Lorg/bukkit/entity/Player;)Ldev/slne/surf/api/paper/nms/bridges/data/chat/RemoteChatSessionData; public fun getStatsDataPath ()Ljava/nio/file/Path; public fun increaseNextChatIndex (Lorg/bukkit/entity/Player;)Ljava/lang/Integer; + public fun resetPlayerChatState (Lorg/bukkit/entity/Player;Ldev/slne/surf/api/paper/nms/bridges/data/chat/RemoteChatSessionData;)V public fun runOnChatMessageChain (Lorg/bukkit/entity/Player;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)V public fun sendPlayerChatMessage (Lorg/bukkit/entity/Player;Lnet/kyori/adventure/chat/SignedMessage;Lnet/kyori/adventure/chat/ChatType$Bound;)V public fun sendSignedMessageWithChangedContent (Lorg/bukkit/entity/Player;Lnet/kyori/adventure/chat/SignedMessage;Lnet/kyori/adventure/chat/ChatType$Bound;Lnet/kyori/adventure/text/Component;)V diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt deleted file mode 100644 index 94718e3e..00000000 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/IncomingChatChainMirror.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.slne.surf.api.paper.nms.bridges.data.chat - -import dev.slne.surf.api.paper.nms.NmsUseWithCaution -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable -import java.time.Instant - -@NmsUseWithCaution -@Serializable -data class IncomingChatChainMirror( - val nextLinkIndex: Int?, - val lastTimeStamp: @Contextual Instant, -) \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt deleted file mode 100644 index 0917f658..00000000 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/LastSeenMessagesValidatorMirror.kt +++ /dev/null @@ -1,54 +0,0 @@ -package dev.slne.surf.api.paper.nms.bridges.data.chat - -import dev.slne.surf.api.paper.nms.NmsUseWithCaution -import kotlinx.serialization.Serializable - -@Serializable -@NmsUseWithCaution -data class LastSeenMessagesValidatorMirror( - val lastSeenCount: Int, - val trackedMessages: List, - val lastPendingMessage: ByteArray? = null, -) { - - @Serializable - @NmsUseWithCaution - data class LastSeenTrackedEntry( - val signature: ByteArray, - val pending: Boolean - ) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is LastSeenTrackedEntry) return false - - if (pending != other.pending) return false - if (!signature.contentEquals(other.signature)) return false - - return true - } - - override fun hashCode(): Int { - var result = pending.hashCode() - result = 31 * result + signature.contentHashCode() - return result - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is LastSeenMessagesValidatorMirror) return false - - if (lastSeenCount != other.lastSeenCount) return false - if (trackedMessages != other.trackedMessages) return false - if (!lastPendingMessage.contentEquals(other.lastPendingMessage)) return false - - return true - } - - override fun hashCode(): Int { - var result = lastSeenCount - result = 31 * result + trackedMessages.hashCode() - result = 31 * result + (lastPendingMessage?.contentHashCode() ?: 0) - return result - } -} \ No newline at end of file diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt deleted file mode 100644 index 9cd8c1fd..00000000 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatSessionSnapshot.kt +++ /dev/null @@ -1,34 +0,0 @@ -package dev.slne.surf.api.paper.nms.bridges.data.chat - -import dev.slne.surf.api.paper.nms.NmsUseWithCaution -import kotlinx.serialization.Serializable - -@NmsUseWithCaution -@Serializable -data class PlayerChatSessionSnapshot( - val nextChatIndex: Int, - val chatSession: RemoteChatSessionData?, - val lastSeenMessages: LastSeenMessagesValidatorMirror, - val messageSignatureCache: Array, - val incomingChatChain: IncomingChatChainMirror -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is PlayerChatSessionSnapshot) return false - - if (nextChatIndex != other.nextChatIndex) return false - if (chatSession != other.chatSession) return false - if (lastSeenMessages != other.lastSeenMessages) return false - if (!messageSignatureCache.contentDeepEquals(other.messageSignatureCache)) return false - - return true - } - - override fun hashCode(): Int { - var result = nextChatIndex - result = 31 * result + chatSession.hashCode() - result = 31 * result + lastSeenMessages.hashCode() - result = 31 * result + messageSignatureCache.contentDeepHashCode() - return result - } -} \ No newline at end of file