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 }