Files
mc-router/mcproto/decode.go
T
2025-06-29 07:37:26 -05:00

134 lines
4.0 KiB
Go

package mcproto
import (
"bytes"
"strings"
"github.com/pkg/errors"
)
const invalidPacketDataBytesMsg = "data should be byte slice from Packet.Data"
// DecodeHandshake takes the Packet.Data bytes and decodes a Handshake message from it
func DecodeHandshake(data interface{}) (*Handshake, error) {
dataBytes, ok := data.([]byte)
if !ok {
return nil, errors.New(invalidPacketDataBytesMsg)
}
handshake := &Handshake{}
buffer := bytes.NewBuffer(dataBytes)
var err error
protocolVersion, err := ReadVarInt(buffer)
if err != nil {
return nil, err
}
handshake.ProtocolVersion = ProtocolVersion(protocolVersion)
handshake.ServerAddress, err = ReadString(buffer)
if err != nil {
return nil, err
}
// Forge Mod Loader adds some data after the server address. Truncate it.
handshake.ServerAddress, _, _ = strings.Cut(handshake.ServerAddress, string(rune(0)))
handshake.ServerPort, err = ReadUnsignedShort(buffer)
if err != nil {
return nil, err
}
nextState, err := ReadVarInt(buffer)
if err != nil {
return nil, err
}
handshake.NextState = State(nextState)
return handshake, nil
}
// DecodeLoginStart takes the Packet.Data bytes and decodes a LoginStart message from it
func DecodeLoginStart(protocolVersion ProtocolVersion, data interface{}) (*LoginStart, error) {
dataBytes, ok := data.([]byte)
if !ok {
return nil, errors.New(invalidPacketDataBytesMsg)
}
loginStart := NewLoginStart()
buffer := bytes.NewBuffer(dataBytes)
var err error
loginStart.Name, err = ReadString(buffer)
if err != nil {
return loginStart, errors.Wrap(err, "failed to read username")
}
// These versions can send player keypair data. Ignore it.
// References:
// * https://github.com/MCCTeam/Minecraft-Console-Client/blob/f785f509f228bf787c237ac139e6f666a960819a/MinecraftClient/Protocol/Handlers/Protocol18.cs#L2808-L2828
// * https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol?oldid=2772902#Login_Start
if protocolVersion >= ProtocolVersion1_19 && protocolVersion <= ProtocolVersion1_19_2 {
hasSignatureData, err := ReadBoolean(buffer)
if err != nil {
return loginStart, errors.Wrap(err, "failed to read has signature data flag")
}
if hasSignatureData {
// Read and discard the data
_, err = ReadLong(buffer) // Expiration time
if err != nil {
return loginStart, errors.Wrap(err, "failed to read expiration time")
}
pubKeyLength, err := ReadVarInt(buffer) // Length of the public key
if err != nil {
return loginStart, errors.Wrap(err, "failed to read public key length")
}
_, err = ReadByteArray(buffer, pubKeyLength) // Public key data
if err != nil {
return loginStart, errors.Wrap(err, "failed to read public key")
}
signatureLength, err := ReadVarInt(buffer) // Length of the signature
if err != nil {
return loginStart, errors.Wrap(err, "failed to read signature length")
}
_, err = ReadByteArray(buffer, signatureLength) // Signature data
if err != nil {
return loginStart, errors.Wrap(err, "failed to read signature")
}
}
}
// References:
// * https://github.com/MCCTeam/Minecraft-Console-Client/blob/f785f509f228bf787c237ac139e6f666a960819a/MinecraftClient/Protocol/Handlers/Protocol18.cs#L2831-L2853
// * https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol?oldid=2772944#Login_Start
switch {
case protocolVersion >= ProtocolVersion1_19_2 && protocolVersion < ProtocolVersion1_20_2:
// Check to see if a UUID was provided at all
hasUUID, err := ReadBoolean(buffer)
if err != nil {
return loginStart, errors.Wrap(err, "failed to read has uuid flag")
}
if !hasUUID {
break
}
fallthrough
case protocolVersion >= ProtocolVersion1_20_2:
// For 1.20.2 and later, the UUID is always present
playerUuid, err := ReadUuid(buffer)
if err != nil {
return loginStart, errors.Wrap(err, "failed to read player uuid")
}
loginStart.PlayerUuid = playerUuid
default:
// For versions before 1.19.2, the UUID is not present
}
return loginStart, nil
}