You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

拦截到的Minecraft数据包ID与Protocol Wiki不符问题求助

问题根源分析

你遇到的问题核心是未正确解析Minecraft的数据包帧结构,同时可能存在Protocol Wiki版本与实际运行的Minecraft版本不匹配的情况:

1. Minecraft数据包的正确结构

Minecraft通过TCP流传输数据,但每个完整数据包有固定帧结构:

  • VarInt类型的数据包总长度:标识后续(数据包ID + 内容)的字节数
  • VarInt类型的数据包ID:对应你在Wiki中看到的类型标识
  • Payload字节数组:数据包的具体内容

你当前代码直接把读取到的原始字节流开头当作数据包ID,但实际上第一个字节(或多个字节)是长度前缀,而非ID。比如你提到的破坏方块数据包03 00 2F 00:

  • 第一个字节03是VarInt编码的总长度(十进制3),表示后续有3个字节的ID+内容
  • 真正的数据包ID需要从后续字节中解码VarInt得到

2. VarInt编码的影响

Minecraft用VarInt(可变长度整数)编码长度和ID,每个字节最高位是续位标记(1表示后续还有字节,0表示结束),剩余7位存数据。比如0-127的整数用1字节编码,128以上用多字节,你必须先解码VarInt才能拿到正确的长度和ID,不能直接取第一个字节。

3. 版本不匹配的可能性

如果解析后ID仍和Wiki不符,大概率是你参考的Protocol Wiki版本与客户端/服务器的Minecraft版本不一致——不同版本的数据包ID经常变动,比如1.18和1.19的ID就有不少差异。


解决方案

第一步:实现VarInt编解码工具

先编写VarInt的读写方法,用于解析长度和ID:

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;

public class VarIntUtil {
    // 解码VarInt
    public static int readVarInt(ByteArrayInputStream in) throws IOException {
        int value = 0;
        int position = 0;
        byte currentByte;

        while (true) {
            currentByte = (byte) in.read();
            value |= (currentByte & 0x7F) << position;

            if ((currentByte & 0x80) == 0) break;

            position += 7;
            if (position >= 32) {
                throw new IOException("VarInt超出最大长度(32位)");
            }
        }
        return value;
    }

    // 编码VarInt
    public static void writeVarInt(OutputStream out, int value) throws IOException {
        do {
            byte temp = (byte) (value & 0x7F);
            value >>>= 7;
            if (value != 0) {
                temp |= 0x80;
            }
            out.write(temp);
        } while (value != 0);
    }
}

第二步:修改代理代码,正确解析数据包

修改copyStreams方法,按照Minecraft的帧结构逐包解析,避免直接打印原始字节流:

private static void copyStreams(Socket inputSocket, Socket outputSocket, String direction) {
    try {
        InputStream inputStream = inputSocket.getInputStream();
        OutputStream outputStream = outputSocket.getOutputStream();

        byte[] buffer = new byte[4096];
        int bytesRead;
        ByteArrayInputStream byteStream = new ByteArrayInputStream(new byte[0]);

        while ((bytesRead = inputStream.read(buffer)) != -1) {
            // 将新读取的字节追加到缓存流中
            byte[] newBuffer = new byte[byteStream.available() + bytesRead];
            byteStream.read(newBuffer, 0, byteStream.available());
            System.arraycopy(buffer, 0, newBuffer, byteStream.available(), bytesRead);
            byteStream = new ByteArrayInputStream(newBuffer);

            // 尝试解析所有完整的数据包
            while (byteStream.available() > 0) {
                byteStream.mark(Integer.MAX_VALUE);
                int packetLength;
                try {
                    packetLength = VarIntUtil.readVarInt(byteStream);
                } catch (IOException e) {
                    // 不完整的VarInt,重置位置等待下一批数据
                    byteStream.reset();
                    break;
                }

                // 检查剩余字节是否足够组成完整数据包
                if (byteStream.available() < packetLength) {
                    byteStream.reset();
                    break;
                }

                // 读取完整的数据包内容(ID + payload)
                byte[] packetContent = new byte[packetLength];
                byteStream.read(packetContent);
                ByteArrayInputStream packetStream = new ByteArrayInputStream(packetContent);

                // 解析数据包ID和内容
                int packetId = VarIntUtil.readVarInt(packetStream);
                byte[] payload = new byte[packetStream.available()];
                packetStream.read(payload);

                // 打印解析后的信息
                if (direction.equals("Client to Server")) {
                    System.out.printf("客户端→服务器 | 总长度: %d | 数据包ID: 0x%X | 内容: %s%n",
                            packetLength, packetId, bytesToString(payload));
                } else {
                    System.out.printf("服务器→客户端 | 总长度: %d | 数据包ID: 0x%X | 内容: %s%n",
                            packetLength, packetId, bytesToString(payload));
                }

                // 将原始数据包写入输出流(先写编码后的长度,再写内容)
                VarIntUtil.writeVarInt(outputStream, packetLength);
                outputStream.write(packetContent);
                outputStream.flush();
            }
        }

        // 关闭资源
        outputStream.close();
        inputSocket.close();
        outputSocket.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

第三步:验证版本匹配

解析出正确ID后,对比Protocol Wiki中对应Minecraft版本的ID定义,确保参考的文档版本和你实际运行的客户端/服务器版本一致。


关键注意点
  • TCP是流协议,一次read可能读取到多个数据包或一个数据包的片段,必须通过长度前缀拆分完整数据包
  • VarInt编解码必须正确,否则会导致整个数据包解析错误
  • 始终确保Protocol Wiki的版本与你的Minecraft版本匹配

内容的提问来源于stack exchange,提问作者BoterBramKroket

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 16:17:02