460 lines
11 KiB
Go
460 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"compress/zlib"
|
|
"crypto/md5"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Decompress function using zlib
|
|
func decompress(data []byte) ([]byte, error) {
|
|
reader, err := zlib.NewReader(bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func(reader io.ReadCloser) {
|
|
_ = reader.Close()
|
|
}(reader)
|
|
|
|
var decompressedData bytes.Buffer
|
|
_, err = io.Copy(&decompressedData, reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return decompressedData.Bytes(), nil
|
|
}
|
|
|
|
// Compress function using zlib
|
|
func compress(data []byte) ([]byte, error) {
|
|
var compressedData bytes.Buffer
|
|
writer := zlib.NewWriter(&compressedData)
|
|
|
|
_, err := writer.Write(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = writer.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return compressedData.Bytes(), nil
|
|
}
|
|
|
|
func readPacket(reader *bufio.Reader, threshold int32) (packetID int32, packetData []byte, packetLength int, err error) {
|
|
packetLengthTemp, _, err := readVarint(reader)
|
|
if err != nil {
|
|
return
|
|
}
|
|
packetLength = packetLengthTemp
|
|
|
|
packetData = make([]byte, packetLength)
|
|
|
|
n, err := io.ReadFull(reader, packetData)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if n != packetLength {
|
|
err = errors.New("packet read length mismatch")
|
|
return
|
|
}
|
|
|
|
var dataLength int
|
|
if threshold > 0 {
|
|
n, dataLengthTemp := receiveVarint(packetData)
|
|
packetData = packetData[n:]
|
|
dataLength = int(dataLengthTemp)
|
|
}
|
|
|
|
// If dataLength > 0, it means the packet is compressed
|
|
if dataLength > 0 {
|
|
packetData, err = decompress(packetData)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
n, packetID = receiveVarint(packetData)
|
|
packetLength -= n
|
|
packetData = packetData[n:]
|
|
|
|
return
|
|
}
|
|
|
|
func createPacket(packetID int32, packetData []byte, threshold int32, startedCompression bool) ([]byte, error) {
|
|
var dataBuffer []byte
|
|
addVarint(&dataBuffer, packetID)
|
|
|
|
dataBuffer = append(dataBuffer, packetData...)
|
|
|
|
var outBuffer []byte
|
|
length := int32(len(dataBuffer))
|
|
|
|
if startedCompression {
|
|
if threshold > 0 && length >= threshold {
|
|
// Compress the packet
|
|
compressedData, err := compress(dataBuffer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataBuffer = compressedData
|
|
|
|
// Add the uncompressed length
|
|
addVarint(&outBuffer, int32(len(packetData)))
|
|
} else {
|
|
// Set Data Length to 0 if not compressed
|
|
addVarint(&outBuffer, 0)
|
|
}
|
|
}
|
|
|
|
addVarint(&outBuffer, int32(len(dataBuffer)))
|
|
|
|
// Append compressed or uncompressed data
|
|
outBuffer = append(outBuffer, dataBuffer...)
|
|
|
|
// Add the Packet Length
|
|
|
|
return outBuffer, nil
|
|
}
|
|
|
|
func addString(buffer *[]byte, str string) {
|
|
length := len(str)
|
|
addVarint(buffer, int32(length))
|
|
|
|
*buffer = append(*buffer, str...)
|
|
}
|
|
|
|
// Reads a VarInt from a bufio.Reader and returns the decoded value, the length read, and an error if any.
|
|
// Only negative values are transformed similar to Java's implementation.
|
|
func readVarint(r *bufio.Reader) (value int, length int, err error) {
|
|
var position uint
|
|
value = 0
|
|
for {
|
|
currentByte, err := r.ReadByte()
|
|
if err != nil {
|
|
return 0, length, err
|
|
}
|
|
value |= int(currentByte&0x7F) << position
|
|
length++
|
|
if (currentByte & 0x80) == 0 {
|
|
break
|
|
}
|
|
position += 7
|
|
if position >= 32 {
|
|
return 0, length, fmt.Errorf("VarInt is too big")
|
|
}
|
|
}
|
|
return value, length, nil
|
|
}
|
|
|
|
// Decodes a VarInt from the byte slice.
|
|
// This function handles the decoding in a manner similar to Java's VarInt,
|
|
// correctly managing positive and negative values.
|
|
func receiveVarint(b []byte) (currentOffset int, value int32) {
|
|
var shift uint
|
|
for {
|
|
byteValue := b[currentOffset]
|
|
currentOffset++
|
|
value |= int32(byteValue&0x7F) << shift
|
|
if (byteValue & 0x80) == 0 {
|
|
// Apply sign extension for negative values
|
|
break
|
|
}
|
|
shift += 7
|
|
if shift >= 32 {
|
|
panic("VarInt is too big")
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func addVarint(buffer *[]byte, value int32) {
|
|
for {
|
|
// Extract the lower 7 bits of the value
|
|
temp := byte(value & 0x7F)
|
|
value >>= 7
|
|
|
|
// If the value is not zero or if there are more bytes to encode
|
|
if value != 0 && value != -1 {
|
|
temp |= 0x80 // Set the continuation bit
|
|
} else {
|
|
temp &= 0x7F // Clear the continuation bit if we're done
|
|
}
|
|
|
|
// Append the byte to the buffer
|
|
addByte(buffer, temp)
|
|
|
|
// Break the loop if no more continuation bit is set
|
|
if (temp & 0x80) == 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func addVarlong(buffer *[]byte, value int64) {
|
|
for {
|
|
temp := byte(value & 0x7F) // Get the last 7 bits
|
|
value >>= 7
|
|
if value != 0 && value != -1 {
|
|
temp |= 0x80 // Set the continuation bit
|
|
}
|
|
addByte(buffer, temp)
|
|
if temp&0x80 == 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Decodes a VarLong from the byte slice.
|
|
func receiveVarlong(b []byte) (currentOffset int, value int64) {
|
|
var shift uint
|
|
for {
|
|
byteValue := b[currentOffset]
|
|
currentOffset++
|
|
value |= int64(byteValue&0x7F) << shift
|
|
if (byteValue & 0x80) == 0 {
|
|
// Sign extension for negative values
|
|
if shift < 32 {
|
|
value |= -1 << shift
|
|
}
|
|
break
|
|
}
|
|
shift += 7
|
|
if shift >= 32 {
|
|
panic("VarLong is too big")
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func addByte(buffer *[]byte, byte byte) {
|
|
*buffer = append(*buffer, byte)
|
|
}
|
|
|
|
func receiveBool(buffer []byte) (currentOffset int, boolean bool) {
|
|
boolean = buffer[0] == 1
|
|
currentOffset = 1
|
|
return
|
|
}
|
|
|
|
func addBool(buffer *[]byte, bool bool) {
|
|
var boolByte byte
|
|
boolByte = 0x00
|
|
if bool {
|
|
boolByte = 0x01
|
|
}
|
|
*buffer = append(*buffer, boolByte)
|
|
}
|
|
|
|
func constructOfflinePlayerUUID(username string) (dataArray []byte) {
|
|
data := md5.Sum([]byte("OfflinePlayer:" + username))
|
|
dataArray = data[:]
|
|
|
|
// Set the version to 3 -> Name based md5 hash
|
|
dataArray[6] = (dataArray[6] & 0x0f) | 0x30
|
|
// IETF variant
|
|
dataArray[8] = (dataArray[8] & 0x3f) | 0x80
|
|
|
|
return dataArray
|
|
}
|
|
|
|
func createOfflineLoginPacket(username string, threshold int32) (outBuffer []byte, err error) {
|
|
addString(&outBuffer, username)
|
|
outBuffer = append(outBuffer, constructOfflinePlayerUUID(username)...)
|
|
outBuffer, err = createPacket(0, outBuffer, threshold, false)
|
|
return
|
|
}
|
|
|
|
func createClientBrandPacket(brand string, threshold int32, startedCompression bool) (outBuffer []byte, err error) {
|
|
addString(&outBuffer, "minecraft:brand")
|
|
addString(&outBuffer, brand)
|
|
outBuffer, err = createPacket(2, outBuffer, threshold, startedCompression)
|
|
return
|
|
}
|
|
|
|
func createConfirmLoginPacket(threshold int32, startedCompression bool) (packet []byte, err error) {
|
|
packet, err = createPacket(3, []byte{}, threshold, startedCompression)
|
|
return
|
|
}
|
|
|
|
func createStatusRequestPacket(threshold int32) (packet []byte, err error) {
|
|
packet, err = createPacket(0, []byte{}, threshold, false)
|
|
return
|
|
}
|
|
|
|
func addUint16(buffer *[]byte, value uint16) {
|
|
*buffer = binary.BigEndian.AppendUint16(*buffer, value)
|
|
}
|
|
|
|
func addInt64(buffer *[]byte, value int64) {
|
|
*buffer = binary.BigEndian.AppendUint64(*buffer, uint64(value))
|
|
}
|
|
|
|
func receiveFloat32(b []byte) (currentOffset int, value float32) {
|
|
value = math.Float32frombits(binary.BigEndian.Uint32(b))
|
|
currentOffset = binary.Size(value)
|
|
return
|
|
}
|
|
|
|
func receiveFloat64(b []byte) (currentOffset int, value float64) {
|
|
value = math.Float64frombits(binary.BigEndian.Uint64(b))
|
|
currentOffset = binary.Size(value)
|
|
return
|
|
}
|
|
|
|
func receiveInt32(b []byte) (currentOffset int, value int32) {
|
|
value = int32(binary.BigEndian.Uint32(b))
|
|
currentOffset = binary.Size(value)
|
|
return
|
|
}
|
|
|
|
func receiveInt64(b []byte) (currentOffset int, value int64) {
|
|
value = int64(binary.BigEndian.Uint64(b))
|
|
currentOffset = binary.Size(value)
|
|
return
|
|
}
|
|
|
|
func receiveByte(b []byte) (currentOffset int, value byte) {
|
|
value = b[0]
|
|
currentOffset = binary.Size(value)
|
|
return
|
|
}
|
|
|
|
func receivePosition(b []byte) (currentOffset int, position Position) {
|
|
encoded := binary.BigEndian.Uint64(b)
|
|
x, y, z := DecodePosition(encoded)
|
|
position = Position{X: x, Y: y, Z: z}
|
|
currentOffset = binary.Size(encoded)
|
|
return
|
|
}
|
|
|
|
func DecodePosition(encoded uint64) (x int32, y int16, z int32) {
|
|
// Extract x, y, z from encoded value
|
|
x = int32((encoded >> 38) & 0x3FFFFFF)
|
|
y = int16(encoded & 0xFFF)
|
|
z = int32((encoded >> 12) & 0x3FFFFFF)
|
|
|
|
// Adjust for sign
|
|
if x >= 1<<25 {
|
|
x -= 1 << 26
|
|
}
|
|
if y >= 1<<11 {
|
|
y -= 1 << 12
|
|
}
|
|
if z >= 1<<25 {
|
|
z -= 1 << 26
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func createHandshakePacket(version int32, address string, port uint16, nextState int32, threshold int32) (outBuffer []byte, err error) {
|
|
addVarint(&outBuffer, version)
|
|
addString(&outBuffer, address)
|
|
addUint16(&outBuffer, port)
|
|
addVarint(&outBuffer, nextState)
|
|
outBuffer, err = createPacket(0, outBuffer, threshold, false)
|
|
return
|
|
}
|
|
|
|
func createTeleportConfirmPacket(teleportID int32, threshold int32, startedCompression bool) (outBuffer []byte, err error) {
|
|
addVarint(&outBuffer, teleportID)
|
|
outBuffer, err = createPacket(0, outBuffer, threshold, startedCompression)
|
|
return
|
|
}
|
|
|
|
func receiveString(buf []byte) (currentOffset int, strOut string) {
|
|
n, stringLen := receiveVarint(buf)
|
|
currentOffset = n
|
|
strOut = string(buf[currentOffset : currentOffset+int(stringLen)])
|
|
currentOffset += int(stringLen)
|
|
return
|
|
}
|
|
|
|
func receiveTextComponent(buf []byte) (currentOffset int, component TextComponent, err error) {
|
|
currentOffset, jsonString := receiveString(buf)
|
|
if strings.ContainsAny(jsonString, "") {
|
|
}
|
|
jsonDecoderText := json.NewDecoder(strings.NewReader(jsonString))
|
|
err = jsonDecoderText.Decode(&component)
|
|
if err != nil {
|
|
err = nil
|
|
component.Text = jsonString
|
|
component.CleanText = cleanMinecraftFormatting(jsonString)
|
|
}
|
|
return
|
|
}
|
|
|
|
func createEnabledDatapacksPacket(datapacks []DatapackInfo, threshold int32, startedCompression bool) (outBuffer []byte, err error) {
|
|
addVarint(&outBuffer, int32(len(datapacks)))
|
|
for _, datapack := range datapacks {
|
|
addString(&outBuffer, datapack.Namespace)
|
|
addString(&outBuffer, datapack.ID)
|
|
addString(&outBuffer, datapack.Version)
|
|
}
|
|
outBuffer, err = createPacket(0x07, outBuffer, threshold, startedCompression)
|
|
return
|
|
}
|
|
|
|
func createChatMessagePacket(message string, threshold int32, startedCompression bool) (outBuffer []byte, err error) {
|
|
addString(&outBuffer, message)
|
|
addInt64(&outBuffer, time.Now().Unix())
|
|
addInt64(&outBuffer, time.Now().Unix())
|
|
addBool(&outBuffer, false)
|
|
addVarint(&outBuffer, 0)
|
|
addByte(&outBuffer, 0)
|
|
addByte(&outBuffer, 0)
|
|
addByte(&outBuffer, 0)
|
|
outBuffer, err = createPacket(0x06, outBuffer, threshold, startedCompression)
|
|
return
|
|
}
|
|
|
|
func decodeEnabledDatapacks(buf []byte) (currentOffset int, outPacks []DatapackInfo) {
|
|
currentOffset, datapackCount := receiveVarint(buf)
|
|
for i := 0; i < int(datapackCount); i++ {
|
|
nextOffset, namespace := receiveString(buf[currentOffset:])
|
|
currentOffset += nextOffset
|
|
nextOffset, ID := receiveString(buf[currentOffset:])
|
|
currentOffset += nextOffset
|
|
nextOffset, version := receiveString(buf[currentOffset:])
|
|
currentOffset += nextOffset
|
|
enabledDatapack := DatapackInfo{
|
|
Namespace: namespace,
|
|
ID: ID,
|
|
Version: version,
|
|
}
|
|
outPacks = append(outPacks, enabledDatapack)
|
|
}
|
|
return
|
|
}
|
|
|
|
func createClientInformationPacket(locale string, renderDistance byte, chatMode int32, colors bool, skinParts byte, mainLeftHand bool, textFiltering bool, serverListing bool, threshold int32, startedCompression bool) (outBuffer []byte, err error) {
|
|
var mainHand int32
|
|
if mainLeftHand {
|
|
mainHand = 0
|
|
} else {
|
|
mainHand = 1
|
|
}
|
|
addString(&outBuffer, locale)
|
|
addByte(&outBuffer, renderDistance)
|
|
addVarint(&outBuffer, chatMode)
|
|
addBool(&outBuffer, colors)
|
|
addByte(&outBuffer, skinParts)
|
|
addVarint(&outBuffer, mainHand)
|
|
addBool(&outBuffer, textFiltering)
|
|
addBool(&outBuffer, serverListing)
|
|
outBuffer, err = createPacket(0, outBuffer, threshold, startedCompression)
|
|
return
|
|
}
|