149 lines
3.3 KiB
Go
149 lines
3.3 KiB
Go
package mcproto
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"io"
|
|
"unicode/utf16"
|
|
)
|
|
|
|
// WriteVarInt writes a VarInt (Minecraft format) to w
|
|
func WriteVarInt(w io.Writer, value int32) error {
|
|
var buf [5]byte
|
|
i := 0
|
|
v := uint32(value)
|
|
for {
|
|
temp := byte(v & 0x7F)
|
|
v >>= 7
|
|
if v != 0 {
|
|
temp |= 0x80
|
|
}
|
|
buf[i] = temp
|
|
i++
|
|
if v == 0 {
|
|
break
|
|
}
|
|
}
|
|
_, err := w.Write(buf[:i])
|
|
return err
|
|
}
|
|
|
|
// WriteString writes a Minecraft length-prefixed string
|
|
func WriteString(w io.Writer, s string) error {
|
|
if err := WriteVarInt(w, int32(len(s))); err != nil {
|
|
return err
|
|
}
|
|
_, err := io.WriteString(w, s)
|
|
return err
|
|
}
|
|
|
|
// buildPacket builds a framed packet: [length VarInt][packetId VarInt][payload]
|
|
func buildPacket(packetID int32, payload []byte) []byte {
|
|
var b bytes.Buffer
|
|
_ = WriteVarInt(&b, packetID)
|
|
b.Write(payload)
|
|
|
|
var framed bytes.Buffer
|
|
_ = WriteVarInt(&framed, int32(b.Len()))
|
|
framed.Write(b.Bytes())
|
|
return framed.Bytes()
|
|
}
|
|
|
|
// WriteStatusJSONPacket writes a Status Response (packet 0x00) with the provided JSON string
|
|
func WriteStatusJSONPacket(w io.Writer, jsonString string) error {
|
|
// payload is the JSON as a Minecraft string
|
|
var payload bytes.Buffer
|
|
if err := WriteString(&payload, jsonString); err != nil {
|
|
return err
|
|
}
|
|
pkt := buildPacket(PacketIdStatusResponse, payload.Bytes())
|
|
_, err := w.Write(pkt)
|
|
return err
|
|
}
|
|
|
|
// WriteStatusFromStruct writes a Status Response from a struct
|
|
func WriteStatusFromStruct(w io.Writer, status StatusResponse) error {
|
|
b, err := json.Marshal(status)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return WriteStatusJSONPacket(w, string(b))
|
|
}
|
|
|
|
// WritePongPacket writes Pong (packet 0x01) with the same payload
|
|
func WritePongPacket(w io.Writer, timestamp int64) error {
|
|
var pl bytes.Buffer
|
|
// payload is a signed long (64-bit)
|
|
var buf [8]byte
|
|
binary.BigEndian.PutUint64(buf[:], uint64(timestamp))
|
|
pl.Write(buf[:])
|
|
pkt := buildPacket(PackedIdPongResponse, pl.Bytes())
|
|
_, err := w.Write(pkt)
|
|
return err
|
|
}
|
|
|
|
// WriteLegacySLPResponse writes the 1.6-compatible legacy response packet (0xFF)
|
|
// Format: FF, [length short], UTF16BE string beginning with "\u00A7\u0031\u0000" then null-delimited fields
|
|
// fields: protocol, version, motd, online, max
|
|
func WriteLegacySLPResponse(w io.Writer, protocol int, version string, motd string, online int, max int) error {
|
|
// Build the string with null separators
|
|
s := "\u00A7\u0031\u0000" +
|
|
intToString(protocol) + "\u0000" +
|
|
version + "\u0000" +
|
|
motd + "\u0000" +
|
|
intToString(online) + "\u0000" +
|
|
intToString(max)
|
|
|
|
// Encode UTF-16BE
|
|
runes := []rune(s)
|
|
encoded := utf16.Encode(runes)
|
|
var be bytes.Buffer
|
|
for _, v := range encoded {
|
|
var tmp [2]byte
|
|
binary.BigEndian.PutUint16(tmp[:], v)
|
|
be.Write(tmp[:])
|
|
}
|
|
|
|
bw := bufio.NewWriter(w)
|
|
// 0xFF
|
|
if _, err := bw.Write([]byte{0xFF}); err != nil {
|
|
return err
|
|
}
|
|
// length short in code units
|
|
var lenBuf [2]byte
|
|
binary.BigEndian.PutUint16(lenBuf[:], uint16(len(encoded)))
|
|
if _, err := bw.Write(lenBuf[:]); err != nil {
|
|
return err
|
|
}
|
|
if _, err := bw.Write(be.Bytes()); err != nil {
|
|
return err
|
|
}
|
|
return bw.Flush()
|
|
}
|
|
|
|
// helpers
|
|
func intToString(i int) string {
|
|
if i == 0 {
|
|
return "0"
|
|
}
|
|
neg := false
|
|
if i < 0 {
|
|
neg = true
|
|
i = -i
|
|
}
|
|
var buf [20]byte
|
|
pos := len(buf)
|
|
for i > 0 {
|
|
pos--
|
|
buf[pos] = byte('0' + (i % 10))
|
|
i /= 10
|
|
}
|
|
if neg {
|
|
pos--
|
|
buf[pos] = '-'
|
|
}
|
|
return string(buf[pos:])
|
|
}
|