Bukkit向Forge发送自定义数据包失败,寻求技术解决方案
问题:Bukkit插件发送自定义数据包无法被Forge模组接收(1.19.3)
我用Bukkit插件发送自定义数据包,发送代码如下:
public static boolean sendPacket(Player player, String message) { try { String full_msg = "Text(" + message + ")"; byte[] data = full_msg.getBytes(StandardCharsets.UTF_8); MinecraftKey channel = new MinecraftKey("chilove", "main"); PacketContainer packet = new PacketContainer(PacketType.Play.Server.CUSTOM_PAYLOAD); packet.getMinecraftKeys().write(0, channel); ByteBuf byteBuf = Unpooled.wrappedBuffer(data); Object serializer = MinecraftReflection.getPacketDataSerializer(byteBuf); packet.getModifier().withType(ByteBuf.class).write(0, serializer); ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); return true; } catch (Exception exception) { Bukkit.getLogger().info(ConsoleColor.ANSI_RED + "Error on TextPacket: " + ConsoleColor.ANSI_CYAN + " (" + exception.getMessage() + ")" + ConsoleColor.ANSI_RESET); exception.printStackTrace(); } return false; }
数据包能正常发送,但Forge模组端的消息处理器完全没反应。我写的处理器代码如下:
private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel( new ResourceLocation("chilove", "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void register() { // INSTANCE.registerMessage(0, TextPacket.class, TextPacket::encode, TextPacket::decode, TextPacket::handle); int id = 0; INSTANCE.messageBuilder(TextPacket.class, id++, NetworkDirection.PLAY_TO_SERVER) .decoder(TextPacket::decode) .consumerMainThread(TextPacket::handle) .add(); INSTANCE.registerMessage(id++, TextPacket.class, TextPacket::encode, TextPacket::decode, TextPacket::handle); INSTANCE.messageBuilder(TextPacket.class, id++, NetworkDirection.PLAY_TO_CLIENT) .decoder(TextPacket::decode) .consumerMainThread(TextPacket::handle) .add(); }
TextPacket类代码:
public class TextPacket { private String text; public TextPacket() { } public TextPacket(String text) { this.text = text; } public String getText() { return text; } public static TextPacket decode(FriendlyByteBuf buffer) { byte[] bytes = buffer.readByteArray(); String text = new String(bytes, StandardCharsets.UTF_8); ChiloveMod.LOGGER.info("Decoded text: {}", text); return new TextPacket(text); } public void encode(FriendlyByteBuf buffer) { byte[] bytes = text.getBytes(StandardCharsets.UTF_8); buffer.writeByteArray(bytes); } public void handle(Supplier<NetworkEvent.Context> ctx) { ctx.get().enqueueWork(() -> { ChiloveMod.LOGGER.info("Received message: {}", text); }); ctx.get().setPacketHandled(true); } }
发送数据包时控制台会弹出以下两个错误:
- [15:15:32] [Netty Client IO #1/ERROR] [ne.mi.ne.si.IndexedMessageCodec/SIMPLENET]: Received invalid discriminator byte 84 on channel chilove:main - [15:15:32] [Render thread/WARN] [minecraft/ClientPacketListener]: Unknown custom packet identifier: chilove:main
我试过各种方法,包括用ClientboundCustomPayloadEvent,都解决不了,求帮助。Minecraft版本是1.19.3。
解决方案
问题根源
Forge的SimpleChannel处理自定义Payload时,要求数据包开头必须先写入消息判别符(varint类型的消息ID),你当前Bukkit端发送的数据包直接是Text(xxx)的字节内容,没有添加这个判别符,导致Forge端无法匹配对应的消息类型,触发invalid discriminator byte错误。同时Forge端的注册代码存在冗余,重复注册消息类可能导致判别符混乱。
修复步骤
1. 修改Bukkit端发送代码,添加消息判别符
Bukkit端需要先写入Forge端注册的消息ID(varint格式),再写入实际内容,修改后的代码如下:
public static boolean sendPacket(Player player, String message) { try { MinecraftKey channel = new MinecraftKey("chilove", "main"); PacketContainer packet = new PacketContainer(PacketType.Play.Server.CUSTOM_PAYLOAD); packet.getMinecraftKeys().write(0, channel); // 创建ByteBuf并写入消息ID和文本内容 ByteBuf byteBuf = Unpooled.buffer(); // 写入消息判别符:对应Forge端注册的PLAY_TO_CLIENT消息ID(这里用0) writeVarInt(byteBuf, 0); // 写入文本内容:先写长度(varint),再写字节数组 byte[] data = message.getBytes(StandardCharsets.UTF_8); writeVarInt(byteBuf, data.length); byteBuf.writeBytes(data); Object serializer = MinecraftReflection.getPacketDataSerializer(byteBuf); packet.getModifier().withType(ByteBuf.class).write(0, serializer); ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); return true; } catch (Exception exception) { Bukkit.getLogger().info(ConsoleColor.ANSI_RED + "Error on TextPacket: " + ConsoleColor.ANSI_CYAN + " (" + exception.getMessage() + ")" + ConsoleColor.ANSI_RESET); exception.printStackTrace(); } return false; } // 实现Minecraft的VarInt编码逻辑 private static void writeVarInt(ByteBuf buf, int value) { do { byte part = (byte)(value & 0x7F); value >>>= 7; if (value != 0) { part |= 0x80; } buf.writeByte(part); } while (value != 0); }
2. 清理Forge端的消息注册代码
删除冗余注册逻辑,只保留服务器到客户端的消息注册(Bukkit发往客户端对应PLAY_TO_CLIENT方向):
private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel( new ResourceLocation("chilove", "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void register() { // 仅注册服务器到客户端的消息,ID为0 INSTANCE.messageBuilder(TextPacket.class, 0, NetworkDirection.PLAY_TO_CLIENT) .encoder(TextPacket::encode) .decoder(TextPacket::decode) .consumerMainThread(TextPacket::handle) .add(); }
3. 修正TextPacket的编解码逻辑
确保解码时先读取字符串长度,再读取字节数组,和Bukkit端的发送逻辑对应:
public class TextPacket { private String text; public TextPacket() { } public TextPacket(String text) { this.text = text; } public String getText() { return text; } public static TextPacket decode(FriendlyByteBuf buffer) { // 先读字符串长度(varint),再读对应字节数组 int length = buffer.readVarInt(); byte[] bytes = new byte[length]; buffer.readBytes(bytes); String text = new String(bytes, StandardCharsets.UTF_8); ChiloveMod.LOGGER.info("Decoded text: {}", text); return new TextPacket(text); } public void encode(FriendlyByteBuf buffer) { byte[] bytes = text.getBytes(StandardCharsets.UTF_8); // 先写字符串长度(varint),再写字节数组 buffer.writeVarInt(bytes.length); buffer.writeBytes(bytes); } public void handle(Supplier<NetworkEvent.Context> ctx) { ctx.get().enqueueWork(() -> { ChiloveMod.LOGGER.info("Received message: {}", text); }); ctx.get().setPacketHandled(true); } }
额外注意事项
- 确保Bukkit端和Forge端的**频道ID(chilove:main)**完全一致,大小写敏感。
- 消息ID必须匹配:Bukkit端发送的判别符要和Forge端注册的消息ID对应(示例中用的是0)。
- 必须使用Minecraft标准的VarInt编码,不能直接写入普通int值,否则会导致解码失败。
内容的提问来源于stack exchange,提问作者owoPeef
相关产品推荐
相关产品推荐

