From 8a6574f2d3c26f5c0afb8d9fb56da8ae60ebcf47 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Mon, 10 Nov 2025 06:43:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=8E=A9=E5=AE=B6=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E6=A0=87=E7=AD=BE=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/BukkitNetworkManager.java | 195 ++++++++++++++++++ .../bukkit/plugin/network/PacketIds.java | 2 + .../plugin/network/id/PacketIds1_20.java | 5 + .../plugin/network/id/PacketIds1_20_5.java | 5 + .../minecraft/NetworkReflections.java | 6 + common-files/src/main/resources/config.yml | 1 + .../core/plugin/config/Config.java | 6 + .../core/util/FriendlyByteBuf.java | 10 + gradle.properties | 2 +- 9 files changed, 231 insertions(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 1ff589e57..baad47fea 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -121,6 +121,7 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; @@ -433,6 +434,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes new SetObjectiveListener1_20(), this.packetIds.clientboundSetObjectivePacket(), "ClientboundSetObjectivePacket" ); + registerS2CGamePacketListener( + VersionHelper.isOrAbove1_20_3() ? + new PlayerChatListener_1_20_3() : + new PlayerChatListener_1_20(), + this.packetIds.clientboundPlayerChatPacket(), "ClientboundPlayerChatPacket"); } @EventHandler(priority = EventPriority.LOWEST) @@ -4206,4 +4212,193 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } } } + + public static class PlayerChatListener_1_20 implements ByteBufferPacketListener { + + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptPlayerChat()) return; + FriendlyByteBuf buf = event.getBuffer(); + boolean changed = false; + UUID sender = buf.readUUID(); + int index = buf.readVarInt(); + byte @Nullable [] messageSignature = buf.readNullable(b -> { + byte[] bs = new byte[256]; + buf.readBytes(bs); + return bs; + }); + // SignedMessageBody.Packed start + String content = buf.readUtf(256); + Instant timeStamp = buf.readInstant(); + long salt = buf.readLong(); + // LastSeenMessages.Packed start + ArrayList> lastSeen = buf.readCollection(FriendlyByteBuf.limitValue(ArrayList::new, 20), b -> { + int i = b.readVarInt() - 1; + if (i == -1) { + byte[] bs = new byte[256]; + buf.readBytes(bs); + return Pair.of(-1, bs); + } else { + return Pair.of(i, null); + } + }); + // LastSeenMessages.Packed end + // SignedMessageBody.Packed end + @Nullable String unsignedContent = buf.readNullable(FriendlyByteBuf::readUtf); + if (unsignedContent != null) { + Map unsignedContentTokens = CraftEngine.instance().fontManager().matchTags(unsignedContent); + if (!unsignedContentTokens.isEmpty()) { + Tag tag = MRegistryOps.JSON.convertTo(MRegistryOps.SPARROW_NBT, GsonHelper.get().fromJson(unsignedContent, JsonElement.class)); + Component component = AdventureHelper.nbtToComponent(tag); + component = AdventureHelper.replaceText(component, unsignedContentTokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)); + unsignedContent = MRegistryOps.SPARROW_NBT.convertTo(MRegistryOps.JSON, AdventureHelper.componentToNbt(component)).toString(); + changed = true; + } + } + // FilterMask start + int type = buf.readVarInt(); + BitSet mask = type == 2 /* PARTIALLY_FILTERED */ ? buf.readBitSet() : null; + // FilterMask end + // ChatType.BoundNetwork start + int chatType = buf.readVarInt(); + String name = buf.readUtf(); + Map nameTokens = CraftEngine.instance().fontManager().matchTags(name); + if (!nameTokens.isEmpty()) { + Tag tag = MRegistryOps.JSON.convertTo(MRegistryOps.SPARROW_NBT, GsonHelper.get().fromJson(name, JsonElement.class)); + Component component = AdventureHelper.nbtToComponent(tag); + component = AdventureHelper.replaceText(component, nameTokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)); + name = MRegistryOps.SPARROW_NBT.convertTo(MRegistryOps.JSON, AdventureHelper.componentToNbt(component)).toString(); + changed = true; + } + @Nullable String targetName = buf.readNullable(FriendlyByteBuf::readUtf); + if (targetName != null) { + Map targetNameTokens = CraftEngine.instance().fontManager().matchTags(targetName); + if (!targetNameTokens.isEmpty()) { + Tag tag = MRegistryOps.JSON.convertTo(MRegistryOps.SPARROW_NBT, GsonHelper.get().fromJson(targetName, JsonElement.class)); + Component component = AdventureHelper.nbtToComponent(tag); + component = AdventureHelper.replaceText(component, targetNameTokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)); + targetName = MRegistryOps.SPARROW_NBT.convertTo(MRegistryOps.JSON, AdventureHelper.componentToNbt(component)).toString(); + changed = true; + } + } + // ChatType.BoundNetwork end + if (changed) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUUID(sender); + buf.writeVarInt(index); + buf.writeNullable(messageSignature, (b, bs) -> buf.writeBytes(bs)); + buf.writeUtf(content); + buf.writeInstant(timeStamp); + buf.writeLong(salt); + buf.writeCollection(lastSeen, (b, pair) -> { + b.writeVarInt(pair.left() + 1); + if (pair.right() != null) { + b.writeBytes(pair.right()); + } + }); + buf.writeNullable(unsignedContent, FriendlyByteBuf::writeUtf); + buf.writeVarInt(type); + if (type == 2) buf.writeBitSet(mask); + buf.writeVarInt(chatType); + buf.writeUtf(name); + buf.writeNullable(targetName, FriendlyByteBuf::writeUtf); + } + } + } + + public static class PlayerChatListener_1_20_3 implements ByteBufferPacketListener { + + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptPlayerChat()) return; + FriendlyByteBuf buf = event.getBuffer(); + boolean changed = false; + int globalIndex = VersionHelper.isOrAbove1_21_5() ? buf.readVarInt() : -1; + UUID sender = buf.readUUID(); + int index = buf.readVarInt(); + byte @Nullable [] messageSignature = buf.readNullable(b -> { + byte[] bs = new byte[256]; + buf.readBytes(bs); + return bs; + }); + // SignedMessageBody.Packed start + String content = buf.readUtf(256); + Instant timeStamp = buf.readInstant(); + long salt = buf.readLong(); + // LastSeenMessages.Packed start + ArrayList> lastSeen = buf.readCollection(FriendlyByteBuf.limitValue(ArrayList::new, 20), b -> { + int i = b.readVarInt() - 1; + if (i == -1) { + byte[] bs = new byte[256]; + buf.readBytes(bs); + return Pair.of(-1, bs); + } else { + return Pair.of(i, null); + } + }); + // LastSeenMessages.Packed end + // SignedMessageBody.Packed end + @Nullable Tag unsignedContent = buf.readNullable(b -> b.readNbt(false)); + if (unsignedContent != null) { + Map tokens = CraftEngine.instance().fontManager().matchTags(unsignedContent); + if (!tokens.isEmpty()) { + Component component = AdventureHelper.tagToComponent(unsignedContent); + component = AdventureHelper.replaceText(component, tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)); + unsignedContent = AdventureHelper.componentToTag(component); + changed = true; + } + } + // FilterMask start + int type = buf.readVarInt(); + BitSet mask = type == 2 /* PARTIALLY_FILTERED */ ? buf.readBitSet() : null; + // FilterMask end + // ChatType.Bound start + int chatType = buf.readVarInt(); + Tag name = buf.readNbt(false); + if (name != null) { + Map tokens = CraftEngine.instance().fontManager().matchTags(name); + if (!tokens.isEmpty()) { + Component component = AdventureHelper.tagToComponent(name); + component = AdventureHelper.replaceText(component, tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)); + name = AdventureHelper.componentToTag(component); + changed = true; + } + } + @Nullable Tag targetName = buf.readNullable(b -> b.readNbt(false)); + if (targetName != null) { + Map tokens = CraftEngine.instance().fontManager().matchTags(targetName); + if (!tokens.isEmpty()) { + Component component = AdventureHelper.tagToComponent(targetName); + component = AdventureHelper.replaceText(component, tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)); + targetName = AdventureHelper.componentToTag(component); + changed = true; + } + } + // ChatType.Bound end + if (changed) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + if (VersionHelper.isOrAbove1_21_5()) buf.writeVarInt(globalIndex); + buf.writeUUID(sender); + buf.writeVarInt(index); + buf.writeNullable(messageSignature, (b, bs) -> buf.writeBytes(bs)); + buf.writeUtf(content); + buf.writeInstant(timeStamp); + buf.writeLong(salt); + buf.writeCollection(lastSeen, (b, pair) -> { + b.writeVarInt(pair.left() + 1); + if (pair.right() != null) { + b.writeBytes(pair.right()); + } + }); + buf.writeNullable(unsignedContent, (b, tag) -> b.writeNbt(tag, false)); + buf.writeVarInt(type); + if (type == 2) buf.writeBitSet(mask); + buf.writeVarInt(chatType); + buf.writeNbt(name, false); + buf.writeNullable(targetName, (b, tag) -> b.writeNbt(tag, false)); + } + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java index 15a5f2b0f..67936ef29 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java @@ -75,4 +75,6 @@ public interface PacketIds { int clientboundForgetLevelChunkPacket(); int serverboundCustomPayloadPacket(); + + int clientboundPlayerChatPacket(); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java index ecc7b7a1e..2245921ae 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java @@ -190,4 +190,9 @@ public class PacketIds1_20 implements PacketIds { public int serverboundCustomPayloadPacket() { return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ServerboundCustomPayloadPacket, PacketFlow.SERVERBOUND); } + + @Override + public int clientboundPlayerChatPacket() { + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundPlayerChatPacket, PacketFlow.CLIENTBOUND); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java index 2b6d35656..9ea4b0483 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java @@ -189,4 +189,9 @@ public class PacketIds1_20_5 implements PacketIds { public int serverboundCustomPayloadPacket() { return PlayPacketIdHelper.byName("minecraft:custom_payload", PacketFlow.SERVERBOUND); } + + @Override + public int clientboundPlayerChatPacket() { + return PlayPacketIdHelper.byName("minecraft:player_chat", PacketFlow.CLIENTBOUND); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java index f5b1e4ee9..a8f62e3ac 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java @@ -1726,4 +1726,10 @@ public final class NetworkReflections { clazz$ClientboundUpdateTagsPacket, Map.class, 0 ) ); + + public static final Class clazz$ClientboundPlayerChatPacket = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundPlayerChatPacket") + ) + ); } diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index c818e6167..9aebfa866 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -403,6 +403,7 @@ network: text-display: true # Modern Holograms item: true advancement: true + player-chat: true recipe: # Master switch for custom recipes diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index ce0e42ee9..d97a9831e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -175,6 +175,7 @@ public class Config { protected boolean network$intercept_packets$set_score; protected boolean network$intercept_packets$item; protected boolean network$intercept_packets$advancement; + protected boolean network$intercept_packets$player_chat; protected boolean network$disable_item_operations; protected boolean item$client_bound_model; @@ -546,6 +547,7 @@ public class Config { network$intercept_packets$set_score = config.getBoolean("network.intercept-packets.set-score", true); network$intercept_packets$item = config.getBoolean("network.intercept-packets.item", true); network$intercept_packets$advancement = config.getBoolean("network.intercept-packets.advancement", true); + network$intercept_packets$player_chat = config.getBoolean("network.intercept-packets.player-chat", true); // emoji emoji$contexts$chat = config.getBoolean("emoji.contexts.chat", true); @@ -980,6 +982,10 @@ public class Config { return instance.network$intercept_packets$advancement; } + public static boolean interceptPlayerChat() { + return instance.network$intercept_packets$player_chat; + } + public static boolean predictBreaking() { return instance.block$predict_breaking; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java b/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java index b4b339108..eb5890256 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java @@ -74,6 +74,16 @@ public class FriendlyByteBuf extends ByteBuf { this.writeLong(instant.toEpochMilli()); } + public static IntFunction limitValue(IntFunction applier, int max) { + return (j) -> { + if (j > max) { + throw new DecoderException("Value " + j + " is larger than limit " + max); + } else { + return applier.apply(j); + } + }; + } + public > C readCollection(IntFunction collectionFactory, Reader reader) { int i = this.readVarInt(); C collection = collectionFactory.apply(i); diff --git a/gradle.properties b/gradle.properties index fc1dc340b..66c12e6c9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] project_version=0.0.65.6 -config_version=53 +config_version=54 lang_version=38 project_group=net.momirealms latest_supported_version=1.21.10