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

Minecraft客户端登录协议问题:公钥字节格式错误

问题描述

我已经实现了服务器列表ping功能,但对接登录协议时遇到问题。通过Wireshark分析发现握手包与登录包被合并发送。我尝试离线登录,根据MC协议文档说明,将hasSigData设为false时无需发送公钥等数据,仅发送了用户名、设为false的hasSigData、设为true的hasPlayerUUID(基于OfflinePlayer:[username]生成的UUID v3)。

现有两个问题:

  • 我的登录包编码是否正确?目标服务器处于离线模式。
  • 如何拆分合并的数据包?

附Wireshark抓包十六进制截图:Wireshark抓包截图,已标记数据起始位置及握手包、登录包大致分界。

服务器返回错误:

/[My IP] lost connection: Internal Exception: io.netty.handler.codec.DecoderException: Malformed public key bytes

我的代码:

LoginStart Packet (Offline)

public static byte[] getLoginStartPacket(String name) throws IOException {
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    DataOutputStream loginStart = new DataOutputStream(buffer);

    UUID uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8));

    loginStart.writeByte(PACKET_ID);
    loginStart.writeChars(name);
    loginStart.writeBoolean(false);
    loginStart.writeBoolean(true);
    MinecraftUUID.writeBytes(loginStart, uuid);

    return buffer.toByteArray();
}

Sending Packet

byte[] handshakeMessage = Handshake.getHandshakePacket(
            this.port,
            this.serverAddress,
            this.protocolVersion,
            2);

VarInt.write(dataOutputStream, handshakeMessage.length);
dataOutputStream.write(handshakeMessage);

byte[] login = LoginOfflineStart.getLoginStartPacket(name);
VarInt.write(dataOutputStream, login.length);
dataOutputStream.write(login);

解决方案

问题1:登录包编码错误修正

你的登录包有两处核心编码错误,这是导致服务器报错的直接原因:

  1. 用户名编码不符合协议:MC协议要求字符串用「VarInt前缀+UTF-8字节」存储,你用的writeChars会把每个字符转为双字节的UTF-16编码,服务器解析时会完全混乱。
  2. UUID生成规则错误:离线模式的UUID必须基于"OfflinePlayer:" + 用户名的字节数组生成,你直接用了用户名本身的字节数组,UUID不匹配会引发解析异常。

修正后的登录包代码:

public static byte[] getLoginStartPacket(String name) throws IOException {
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    DataOutputStream loginStart = new DataOutputStream(buffer);

    // 包ID必须用VarInt写入,不能直接writeByte(兼容高版本协议)
    VarInt.write(loginStart, PACKET_ID);

    // 正确写入用户名:先写长度VarInt,再写UTF-8字节
    byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
    VarInt.write(loginStart, nameBytes.length);
    loginStart.write(nameBytes);

    loginStart.writeBoolean(false); // hasSigData设为false,无需后续公钥数据

    boolean hasPlayerUUID = true;
    loginStart.writeBoolean(hasPlayerUUID);
    if (hasPlayerUUID) {
        // 按离线模式规则生成UUID
        String offlinePrefix = "OfflinePlayer:" + name;
        UUID uuid = UUID.nameUUIDFromBytes(offlinePrefix.getBytes(StandardCharsets.UTF_8));
        MinecraftUUID.writeBytes(loginStart, uuid);
    }

    return buffer.toByteArray();
}

问题2:数据包合并的处理

TCP是流式传输,多个数据包合并发送是正常现象,不需要手动拆分——MC协议本身就规定了「每个数据包前必须带VarInt类型的长度前缀」,服务器会自动根据这个前缀拆分解析每个数据包。

你当前代码中已经为握手包、登录包分别写入了长度前缀,这是正确的操作。之前的合并现象不是问题,错误根源完全是登录包的编码错误,而非数据包合并。

如果担心数据发送延迟,可以在发送完两个包后调用dataOutputStream.flush(),确保数据立即推送到服务器,但这不是必须的,TCP通常会自动处理。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 04:10:35