init
This commit is contained in:
commit
8726a03e66
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
out
|
||||
out/
|
||||
out/*
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
9
.idea/mcpingquick.iml
Normal file
9
.idea/mcpingquick.iml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/mcpingquick.iml" filepath="$PROJECT_DIR$/.idea/mcpingquick.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
BIN
favicon.png
Normal file
BIN
favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
||||
module mcpingquick
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/Tnze/go-mc v1.20.2
|
||||
github.com/google/uuid v1.6.0
|
||||
)
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
github.com/Tnze/go-mc v1.20.2 h1:arHCE/WxLCxY73C/4ZNLdOymRYtdwoXE05ohB7HVN6Q=
|
||||
github.com/Tnze/go-mc v1.20.2/go.mod h1:geoRj2HsXSkB3FJBuhr7wCzXegRlzWsVXd7h7jiJ6aQ=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
27
main.go
Normal file
27
main.go
Normal file
@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//resp, err := PingHostname("play.survival-games.cz", 25565)
|
||||
resp, err := PingHostname("127.0.0.2", 25565)
|
||||
//resp, err := PingHostname("vps.brn.systems", 25965)
|
||||
if err != nil {
|
||||
fmt.Println("Ty debil")
|
||||
fmt.Println(err)
|
||||
}
|
||||
// Pretty print the response
|
||||
respJson, err := json.MarshalIndent(resp, "", " ")
|
||||
if err != nil {
|
||||
fmt.Println("Error marshalling response to JSON:", err)
|
||||
return
|
||||
}
|
||||
err = os.WriteFile("out/server.json", respJson, 0644)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating server.json:", err)
|
||||
}
|
||||
}
|
459
packetcreator.go
Normal file
459
packetcreator.go
Normal file
@ -0,0 +1,459 @@
|
||||
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
|
||||
}
|
782
packetsender.go
Normal file
782
packetsender.go
Normal file
@ -0,0 +1,782 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func PingIP(ip net.IP, port uint16, host string) (response Response, errOut error) {
|
||||
|
||||
if host == "" {
|
||||
host = ip.String()
|
||||
}
|
||||
|
||||
addr := &net.TCPAddr{IP: ip, Port: int(port)}
|
||||
|
||||
conn, err := net.DialTCP("tcp", nil, addr)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
|
||||
didRestartConnection := false
|
||||
|
||||
defer func(conn *net.TCPConn) {
|
||||
_ = conn.Close()
|
||||
}(conn)
|
||||
|
||||
var state byte
|
||||
state = 0
|
||||
// 0 - handshaking
|
||||
// 1 - status
|
||||
// 2 - login
|
||||
// 3 - configuration
|
||||
// 4 - play(since this is a scanner, we don't need to actually join the game)
|
||||
|
||||
response.Encryption = false
|
||||
response.CompressionThreshold = -2
|
||||
response.PluginDataSent = map[string]string{}
|
||||
|
||||
handshakePacketPing, err := createHandshakePacket(-1, host, port, 1, response.CompressionThreshold)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(handshakePacketPing)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
|
||||
statusRequestPacket, err := createStatusRequestPacket(response.CompressionThreshold)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(statusRequestPacket)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
|
||||
var username string
|
||||
var PlayerUUID []byte
|
||||
compressionStarted := false
|
||||
reader := bufio.NewReader(conn)
|
||||
for {
|
||||
packetID, packetData, packetLength, err := readPacket(reader, response.CompressionThreshold)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
switch packetID {
|
||||
case 0x00:
|
||||
if state == 0 {
|
||||
_, stringJson := receiveString(packetData)
|
||||
jsonDecoder := json.NewDecoder(strings.NewReader(stringJson))
|
||||
err = jsonDecoder.Decode(&response)
|
||||
if !didRestartConnection {
|
||||
handshakePacketJoin, err := createHandshakePacket(response.Version.Protocol, host, port, 2, response.CompressionThreshold)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
|
||||
conn, err = net.DialTCP("tcp", nil, addr)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
reader = bufio.NewReader(conn)
|
||||
_, err = conn.Write(handshakePacketJoin)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
didRestartConnection = true
|
||||
}
|
||||
if len(response.Players.Sample) > 0 {
|
||||
username = response.Players.Sample[0].Name
|
||||
} else {
|
||||
username = "YeahAkis_"
|
||||
}
|
||||
response.Username = username
|
||||
PlayerUUID = constructOfflinePlayerUUID(username)
|
||||
response.RawMessage = stringJson
|
||||
if err != nil {
|
||||
errOut = err
|
||||
println(stringJson)
|
||||
return
|
||||
}
|
||||
state = 1
|
||||
loginPacket, err := createOfflineLoginPacket(username, response.CompressionThreshold)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(loginPacket)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
state = 2
|
||||
} else if state == 2 {
|
||||
currentOffset, component, err := receiveTextComponent(packetData)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
response.Message = component
|
||||
return
|
||||
}
|
||||
break
|
||||
|
||||
case 0x01:
|
||||
if state == 3 {
|
||||
|
||||
currentOffset, channelName := receiveString(packetData)
|
||||
nextOffset, channelValue := receiveString(packetData[currentOffset:])
|
||||
currentOffset += nextOffset
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
response.PluginDataSent[channelName] = channelValue
|
||||
} else if state == 2 {
|
||||
response.Encryption = true
|
||||
return
|
||||
} else if state == 0 {
|
||||
//ping response
|
||||
}
|
||||
break
|
||||
|
||||
case 0x02:
|
||||
if state == 2 {
|
||||
playerRecvUUID := packetData[:16]
|
||||
currentOffset := 16
|
||||
if !reflect.DeepEqual(PlayerUUID, playerRecvUUID) {
|
||||
errOut = errors.New("player UUID mismatch")
|
||||
return
|
||||
}
|
||||
addToOffset, receivedUsername := receiveString(packetData[16:])
|
||||
currentOffset += addToOffset
|
||||
if receivedUsername != username {
|
||||
errOut = errors.New("username mismatch")
|
||||
return
|
||||
}
|
||||
//TODO property array handling
|
||||
confirmLoginPacket, err := createConfirmLoginPacket(response.CompressionThreshold, compressionStarted)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(confirmLoginPacket)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
clientBrandPacket, err := createClientBrandPacket("vanilla", response.CompressionThreshold, compressionStarted)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(clientBrandPacket)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
state = 3
|
||||
} else if state == 3 {
|
||||
currentOffset, component, err := receiveTextComponent(packetData)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
response.Message = component
|
||||
return
|
||||
}
|
||||
break
|
||||
|
||||
case 0x03:
|
||||
if state == 2 {
|
||||
currentOffset, compression := receiveVarint(packetData)
|
||||
response.CompressionThreshold = compression
|
||||
compressionStarted = true
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
} else if state == 3 {
|
||||
confirmPlaySwitchPacket, err := createConfirmLoginPacket(response.CompressionThreshold, compressionStarted)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(confirmPlaySwitchPacket)
|
||||
state = 4
|
||||
}
|
||||
break
|
||||
|
||||
case 0x07:
|
||||
if state == 3 {
|
||||
currentOffset, registryID := receiveString(packetData)
|
||||
newOffset, entryCount := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
var entries []RegistryEntry
|
||||
for i := 0; i < int(entryCount); i++ {
|
||||
newOffset, entryID := receiveString(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, hasNBT := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
var nbtData []byte
|
||||
if hasNBT {
|
||||
nbtData = packetData[currentOffset:]
|
||||
}
|
||||
entries = append(entries, RegistryEntry{EntryID: entryID, HasNBT: hasNBT, NBTData: nbtData})
|
||||
}
|
||||
|
||||
registryData := RegistryData{RegistryID: registryID, Entries: entries}
|
||||
response.RegistryDatas = append(response.RegistryDatas, registryData)
|
||||
}
|
||||
break
|
||||
|
||||
case 0x0b:
|
||||
if state == 4 {
|
||||
currentOffset, difficulty := receiveByte(packetData)
|
||||
newOffset, locked := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
response.ServerDifficulty = DifficultyObject{Difficulty: difficulty, Locked: locked}
|
||||
}
|
||||
|
||||
case 0x0c:
|
||||
if state == 3 {
|
||||
currentOffset, flagCount := receiveVarint(packetData)
|
||||
for i := 0; i < int(flagCount); i++ {
|
||||
nextOffset, feature := receiveString(packetData[currentOffset:])
|
||||
response.FeatureFlags = append(response.FeatureFlags, feature)
|
||||
currentOffset += nextOffset
|
||||
}
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
}
|
||||
clientInformationPacket, err := createClientInformationPacket("en_us", 32, 0, true, 0x7f, false, false, false, response.CompressionThreshold, compressionStarted)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(clientInformationPacket)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
}
|
||||
break
|
||||
case 0x0d:
|
||||
if state == 3 {
|
||||
currentOffset, entryCount := receiveVarint(packetData)
|
||||
for i := 0; i < int(entryCount); i++ {
|
||||
nextOffset, registryIdentifier := receiveString(packetData[currentOffset:])
|
||||
currentOffset += nextOffset
|
||||
nextOffset, lengthSubarray := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += nextOffset
|
||||
var tagArray []TagArray
|
||||
for j := 0; j < int(lengthSubarray); j++ {
|
||||
nextOffset, tagIdentifier := receiveString(packetData[currentOffset:])
|
||||
currentOffset += nextOffset
|
||||
nextOffset, subSubArrayLength := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += nextOffset
|
||||
var varInts []int32
|
||||
for e := 0; e < int(subSubArrayLength); e++ {
|
||||
nextOffset, varIntInArray := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += nextOffset
|
||||
varInts = append(varInts, varIntInArray)
|
||||
}
|
||||
tagPiece := TagArray{TagName: tagIdentifier, Entries: varInts}
|
||||
tagArray = append(tagArray, tagPiece)
|
||||
}
|
||||
updateTag := UpdateTag{TagRegistryIdentifier: registryIdentifier, Tags: tagArray}
|
||||
response.Tags = append(response.Tags, updateTag)
|
||||
|
||||
}
|
||||
}
|
||||
break
|
||||
case 0x0e:
|
||||
if state == 3 {
|
||||
currentOffset, enabledDatapacks := decodeEnabledDatapacks(packetData)
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
}
|
||||
response.EnabledDatapacks = enabledDatapacks
|
||||
datapackPacket, err := createEnabledDatapacksPacket(enabledDatapacks, response.CompressionThreshold, compressionStarted)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(datapackPacket)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 0x1D:
|
||||
if state == 4 {
|
||||
currentOffset, component, err := receiveTextComponent(packetData)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
response.Message = component
|
||||
return
|
||||
}
|
||||
break
|
||||
|
||||
case 0x2b:
|
||||
if state == 4 {
|
||||
currentOffset, entityID := receiveInt32(packetData)
|
||||
newOffset, isHardCore := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, dimensionCount := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
var dimensions []string
|
||||
for i := 0; i < int(dimensionCount); i++ {
|
||||
newOffset, dimension := receiveString(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
dimensions = append(dimensions, dimension)
|
||||
}
|
||||
newOffset, maxPlayers := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, viewDistance := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, simulationDistance := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, reducedDebugInfo := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, enableRespawnScreen := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, doLimitedCrafting := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, dimensionType := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, dimensionName := receiveString(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, hashedSeed := receiveInt64(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, gameMode := receiveByte(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, previousGameMode := receiveByte(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, isDebug := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, isFlat := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, hasDeathLocation := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
var deathDimensionName string
|
||||
var deathLocation Position
|
||||
|
||||
if hasDeathLocation {
|
||||
newOffset, deathDimensionName = receiveString(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, deathLocation = receivePosition(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
} else {
|
||||
deathDimensionName = ""
|
||||
deathLocation = Position{}
|
||||
}
|
||||
newOffset, portalCooldown := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, enforcesSecureChat := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
response.PlayerLoginInfo = LoginInfo{
|
||||
EntityID: entityID,
|
||||
Hardcore: isHardCore,
|
||||
Dimensions: dimensions,
|
||||
MaxPlayers: maxPlayers,
|
||||
ViewDistance: viewDistance,
|
||||
SimulationDistance: simulationDistance,
|
||||
ReducedDebugInfo: reducedDebugInfo,
|
||||
EnableRespawnScreen: enableRespawnScreen,
|
||||
DoLimitedCrafting: doLimitedCrafting,
|
||||
DimensionType: dimensionType,
|
||||
DimensionName: dimensionName,
|
||||
HashedSeed: hashedSeed,
|
||||
GameMode: gameMode,
|
||||
PreviousGameMode: previousGameMode,
|
||||
IsDebug: isDebug,
|
||||
IsFlat: isFlat,
|
||||
HasDeathLocation: hasDeathLocation,
|
||||
DeathDimensionName: deathDimensionName,
|
||||
DeathLocation: deathLocation,
|
||||
PortalCooldown: portalCooldown,
|
||||
EnforcesSecureChat: enforcesSecureChat,
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 0x38:
|
||||
if state == 4 {
|
||||
currentOffset, flags := receiveByte(packetData)
|
||||
newOffset, flyingSpeed := receiveFloat32(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, fieldOfViewModifer := receiveFloat32(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
}
|
||||
response.PlayerAbilities = PlayerAbilitiesObject{
|
||||
Invulnerable: flags&0x01 != 0x00,
|
||||
Flying: flags&0x02 != 0x00,
|
||||
AllowFlying: flags&0x04 != 0x00,
|
||||
CreativeMode: flags&0x08 != 0x00,
|
||||
FlyingSpeed: flyingSpeed,
|
||||
FieldOfViewModifier: fieldOfViewModifer,
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 0x53:
|
||||
if state == 4 {
|
||||
currentOffset, heldSlot := receiveByte(packetData)
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
response.PlayerSlot = heldSlot
|
||||
}
|
||||
break
|
||||
case 0x77:
|
||||
if state == 4 {
|
||||
//todo RECIPES
|
||||
}
|
||||
break
|
||||
case 0x11:
|
||||
//commands
|
||||
if state == 4 {
|
||||
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case 0x41:
|
||||
//recipe book
|
||||
if state == 4 {
|
||||
|
||||
}
|
||||
break
|
||||
|
||||
case 0x40:
|
||||
//position update
|
||||
if state == 4 {
|
||||
currentOffset, x := receiveFloat64(packetData)
|
||||
newOffset, y := receiveFloat64(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, z := receiveFloat64(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, yaw := receiveFloat32(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, pitch := receiveFloat32(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, flags := receiveByte(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, teleportId := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
teleportConfirmPacket, err := createTeleportConfirmPacket(teleportId, response.CompressionThreshold, compressionStarted)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(teleportConfirmPacket)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
response.PlayerLocation = PlayerPosition{
|
||||
X: x,
|
||||
Y: y,
|
||||
Z: z,
|
||||
Yaw: yaw,
|
||||
Pitch: pitch,
|
||||
IsXRelative: flags&0x01 != 0x00,
|
||||
IsYRelative: flags&0x02 != 0x00,
|
||||
IsZRelative: flags&0x04 != 0x00,
|
||||
IsYawRelative: flags&0x08 != 0x00,
|
||||
IsPitchRelative: flags&0x10 != 0x00,
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 0x4b:
|
||||
if state == 4 {
|
||||
currentOffset, motd, errx := receiveTextComponent(packetData[2:])
|
||||
currentOffset += 2
|
||||
err = errx
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
response.Description = motd
|
||||
newOffset, hasIcon := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
var iconSize int32
|
||||
var iconData []byte
|
||||
if hasIcon {
|
||||
newOffset, iconSize = receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
iconData = packetData[currentOffset : currentOffset+int(iconSize)]
|
||||
response.Favicon.PngData = iconData
|
||||
} else {
|
||||
iconSize = 0
|
||||
iconData = []byte{}
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 0x3e:
|
||||
if state == 4 {
|
||||
currentOffset, actions := receiveByte(packetData)
|
||||
newOffset, numberOfPlayers := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
|
||||
for i := 0; i < int(numberOfPlayers); i++ {
|
||||
player := PlayerUpdate{}
|
||||
player.UUID = packetData[currentOffset : currentOffset+16]
|
||||
currentOffset += 16
|
||||
|
||||
if actions&0x01 != 0x00 {
|
||||
newOffset, player.Name = receiveString(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
|
||||
newOffset, numberOfProperties := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
|
||||
for x := 0; x < int(numberOfProperties); x++ {
|
||||
property := PlayerProperty{}
|
||||
|
||||
newOffset, property.Name = receiveString(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, property.Value = receiveString(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
|
||||
newOffset, propertySigned := receiveByte(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
|
||||
if propertySigned != 0 {
|
||||
newOffset, property.Signature = receiveString(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
}
|
||||
|
||||
player.Properties = append(player.Properties, property)
|
||||
}
|
||||
}
|
||||
|
||||
if actions&0x02 != 0x00 {
|
||||
newOffset, hasSignatureData := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
|
||||
if hasSignatureData {
|
||||
player.SignatureData = &PlayerSignatureData{}
|
||||
|
||||
player.SignatureData.ChatSessionID = packetData[currentOffset : currentOffset+16]
|
||||
currentOffset += 16
|
||||
|
||||
newOffset, player.SignatureData.PublicKeyExpiryTime = receiveInt64(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
|
||||
newOffset, encodedPublicKeySize := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
player.SignatureData.EncodedPublicKey = packetData[currentOffset : currentOffset+int(encodedPublicKeySize)]
|
||||
currentOffset += int(encodedPublicKeySize)
|
||||
|
||||
newOffset, publicKeySignatureSize := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
player.SignatureData.PublicKeySignature = packetData[currentOffset : currentOffset+int(publicKeySignatureSize)]
|
||||
currentOffset += int(publicKeySignatureSize)
|
||||
}
|
||||
}
|
||||
|
||||
if actions&0x04 != 0x00 {
|
||||
newOffset, player.GameMode = receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
}
|
||||
|
||||
if actions&0x08 != 0x00 {
|
||||
newOffset, player.Listed = receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
}
|
||||
|
||||
if actions&0x10 != 0x00 {
|
||||
newOffset, player.Ping = receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
}
|
||||
|
||||
if actions&0x20 != 0x00 {
|
||||
newOffset, hasDisplayName := receiveBool(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
|
||||
if hasDisplayName {
|
||||
newOffset, displayName, err := receiveTextComponent(packetData[currentOffset:])
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
currentOffset += newOffset
|
||||
player.DisplayName = &displayName
|
||||
}
|
||||
}
|
||||
|
||||
response.PlayersInfo = append(response.PlayersInfo, player)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 0x25:
|
||||
if state == 4 {
|
||||
currentOffset, x := receiveFloat64(packetData)
|
||||
newOffset, z := receiveFloat64(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, oldDiameter := receiveFloat64(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, newDiameter := receiveFloat64(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, speed := receiveVarlong(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, portalTeleportBoundary := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, warningBlocks := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
newOffset, warningTime := receiveVarint(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
|
||||
response.WorldBorder = WorldBorderInfo{
|
||||
X: x,
|
||||
Z: z,
|
||||
OldDiameter: oldDiameter,
|
||||
NewDiameter: newDiameter,
|
||||
Speed: speed,
|
||||
PortalTeleportBoundry: portalTeleportBoundary,
|
||||
WarningBlocks: warningBlocks,
|
||||
WarningTime: warningTime,
|
||||
}
|
||||
|
||||
}
|
||||
break
|
||||
case 0x64:
|
||||
if state == 4 {
|
||||
currentOffset, worldAge := receiveInt64(packetData)
|
||||
newOffset, timeOfDay := receiveInt64(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
response.WorldAge = worldAge
|
||||
response.TimeOfDay = timeOfDay
|
||||
}
|
||||
break
|
||||
|
||||
case 0x56:
|
||||
if state == 4 {
|
||||
currentOffset, location := receivePosition(packetData)
|
||||
newOffset, angle := receiveFloat32(packetData[currentOffset:])
|
||||
currentOffset += newOffset
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
response.DefaultPositionSpawn = DefaultSpawnPosition{
|
||||
Location: location,
|
||||
Angle: angle,
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 0x22:
|
||||
if state == 4 {
|
||||
currentOffset, event := receiveByte(packetData)
|
||||
nextOffset, value := receiveFloat32(packetData[currentOffset:])
|
||||
currentOffset += nextOffset
|
||||
if currentOffset != packetLength {
|
||||
errOut = errors.New("packet length mismatch")
|
||||
return
|
||||
}
|
||||
if event == 13 && value == 0 {
|
||||
chatMessagePacket, err := createChatMessagePacket("Thanks for letting me scan this server", response.CompressionThreshold, compressionStarted)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(chatMessagePacket)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
return
|
||||
}
|
||||
return //we dont want chunks
|
||||
}
|
||||
}
|
||||
|
||||
case 0x1f:
|
||||
if state == 4 {
|
||||
//todo entityEvent
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
packetIDBytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(packetIDBytes, uint64(packetID))
|
||||
fmt.Printf("Unknown packet type %d (%s)\n", packetID, hex.EncodeToString(packetIDBytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PingHostname(host string, port uint16) (Response, error) {
|
||||
|
||||
tcpServer, err := net.ResolveTCPAddr("tcp", host+":"+strconv.Itoa(int(port)))
|
||||
|
||||
if err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
return PingIP(tcpServer.IP, port, host)
|
||||
|
||||
}
|
8
player.go
Normal file
8
player.go
Normal file
@ -0,0 +1,8 @@
|
||||
package main
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type Player struct {
|
||||
Name string `json:"name"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
}
|
251
response.go
Normal file
251
response.go
Normal file
@ -0,0 +1,251 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PlayerPosition struct {
|
||||
X float64 `json:"x,omitempty"`
|
||||
Y float64 `json:"y,omitempty"`
|
||||
Z float64 `json:"z,omitempty"`
|
||||
Yaw float32 `json:"yaw,omitempty"`
|
||||
Pitch float32 `json:"pitch,omitempty"`
|
||||
IsXRelative bool `json:"isXRelative,omitempty"`
|
||||
IsYRelative bool `json:"isYRelative,omitempty"`
|
||||
IsZRelative bool `json:"isZRelative,omitempty"`
|
||||
IsYawRelative bool `json:"isYawRelative,omitempty"`
|
||||
IsPitchRelative bool `json:"isPitchRelative,omitempty"`
|
||||
}
|
||||
|
||||
type Position struct {
|
||||
X int32 `json:"x,omitempty"`
|
||||
Y int16 `json:"y,omitempty"`
|
||||
Z int32 `json:"z,omitempty"`
|
||||
}
|
||||
|
||||
type DefaultSpawnPosition struct {
|
||||
Location Position `json:"location,omitempty"`
|
||||
Angle float32 `json:"angle,omitempty"`
|
||||
}
|
||||
|
||||
type PlayerAbilitiesObject struct {
|
||||
Invulnerable bool `json:"invulnerable,omitempty"`
|
||||
Flying bool `json:"flying,omitempty"`
|
||||
AllowFlying bool `json:"allowFlying,omitempty"`
|
||||
CreativeMode bool `json:"creativeMode,omitempty"`
|
||||
FlyingSpeed float32 `json:"flyingSpeed,omitempty"`
|
||||
FieldOfViewModifier float32 `json:"fieldOfViewModifier,omitempty"`
|
||||
}
|
||||
|
||||
type PlayerProperty struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Signature string `json:"signature,omitempty"`
|
||||
}
|
||||
|
||||
type PlayerSignatureData struct {
|
||||
ChatSessionID []byte `json:"chatSessionID,omitempty"`
|
||||
PublicKeyExpiryTime int64 `json:"publicKeyExpiryTime,omitempty"`
|
||||
EncodedPublicKey []byte `json:"encodedPublicKey,omitempty"`
|
||||
PublicKeySignature []byte `json:"publicKeySignature,omitempty"`
|
||||
}
|
||||
|
||||
type PlayerUpdate struct {
|
||||
UUID []byte `json:"uuid,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Properties []PlayerProperty `json:"properties,omitempty"`
|
||||
SignatureData *PlayerSignatureData `json:"signatureData,omitempty"`
|
||||
GameMode int32 `json:"gameMode,omitempty"`
|
||||
Listed bool `json:"listed,omitempty"`
|
||||
Ping int32 `json:"ping,omitempty"`
|
||||
DisplayName *TextComponent `json:"displayName,omitempty"`
|
||||
}
|
||||
|
||||
type LoginInfo struct {
|
||||
EntityID int32 `json:"entityID,omitempty"`
|
||||
Hardcore bool `json:"hardcore,omitempty"`
|
||||
Dimensions []string `json:"dimensions,omitempty"`
|
||||
MaxPlayers int32 `json:"maxPlayers,omitempty"`
|
||||
ViewDistance int32 `json:"viewDistance,omitempty"`
|
||||
SimulationDistance int32 `json:"simulationDistance,omitempty"`
|
||||
ReducedDebugInfo bool `json:"reducedDebugInfo,omitempty"`
|
||||
EnableRespawnScreen bool `json:"enableRespawnScreen,omitempty"`
|
||||
DoLimitedCrafting bool `json:"doLimitedCrafting,omitempty"`
|
||||
DimensionType int32 `json:"dimensionType,omitempty"`
|
||||
DimensionName string `json:"dimensionName,omitempty"`
|
||||
HashedSeed int64 `json:"hashedSeed,omitempty"`
|
||||
GameMode byte `json:"gameMode,omitempty"`
|
||||
PreviousGameMode byte `json:"previousGameMode,omitempty"`
|
||||
IsDebug bool `json:"isDebug,omitempty"`
|
||||
IsFlat bool `json:"isFlat,omitempty"`
|
||||
HasDeathLocation bool `json:"hasDeathLocation,omitempty"`
|
||||
DeathDimensionName string `json:"deathDimensionName,omitempty"`
|
||||
DeathLocation Position `json:"deathLocation,omitempty"`
|
||||
PortalCooldown int32 `json:"portalCooldown,omitempty"`
|
||||
EnforcesSecureChat bool `json:"enforcesSecureChat,omitempty"`
|
||||
}
|
||||
|
||||
type DifficultyObject struct {
|
||||
Difficulty byte `json:"difficulty,omitempty"`
|
||||
Locked bool `json:"locked,omitempty"`
|
||||
}
|
||||
|
||||
type TagArray struct {
|
||||
TagName string `json:"tagName,omitempty"`
|
||||
Entries []int32 `json:"entries,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateTag struct {
|
||||
TagRegistryIdentifier string `json:"tagRegistryIdentifier,omitempty"`
|
||||
Tags []TagArray `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
type RegistryEntry struct {
|
||||
EntryID string `json:"entryID,omitempty"`
|
||||
HasNBT bool `json:"hasNBT,omitempty"`
|
||||
//TODO implement the nbt data
|
||||
NBTData []byte `json:"nbtData,omitempty"`
|
||||
}
|
||||
|
||||
type RegistryData struct {
|
||||
RegistryID string `json:"registryID,omitempty"`
|
||||
Entries []RegistryEntry `json:"entries,omitempty"`
|
||||
}
|
||||
|
||||
type Favicon struct {
|
||||
PngData []byte `json:"pngData,omitempty"`
|
||||
}
|
||||
|
||||
type DatapackInfo struct {
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
type TextPiece struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
Color string `json:"color,omitempty"`
|
||||
CleanText string `json:"cleantext,omitempty"`
|
||||
}
|
||||
|
||||
type TextComponent struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
Extra []TextPiece `json:"extra,omitempty"`
|
||||
CleanText string `json:"cleantext,omitempty"`
|
||||
}
|
||||
|
||||
type WorldBorderInfo struct {
|
||||
X float64 `json:"x,omitempty"`
|
||||
Z float64 `json:"z,omitempty"`
|
||||
OldDiameter float64 `json:"oldDiameter,omitempty"`
|
||||
NewDiameter float64 `json:"newDiameter,omitempty"`
|
||||
Speed int64 `json:"speed,omitempty"`
|
||||
PortalTeleportBoundry int32 `json:"portalTeleportBoundry,omitempty"`
|
||||
WarningBlocks int32 `json:"warningBlocks,omitempty"`
|
||||
WarningTime int32 `json:"warningTime,omitempty"`
|
||||
}
|
||||
|
||||
// Custom unmarshaler for the TextComponent type
|
||||
func (d *TextComponent) UnmarshalJSON(data []byte) error {
|
||||
// Try to unmarshal as a simple string
|
||||
var text string
|
||||
if err := json.Unmarshal(data, &text); err == nil {
|
||||
d.Text = text
|
||||
d.CleanText = cleanMinecraftFormatting(text)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If that fails, try to unmarshal as an object with a "text" field and optional "extra" field
|
||||
var temp struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
Extra []TextPiece `json:"extra,omitempty"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &temp); err == nil {
|
||||
d.Text = temp.Text
|
||||
d.Extra = temp.Extra
|
||||
|
||||
// Clean the text and combine it with cleaned children texts
|
||||
var sb strings.Builder
|
||||
sb.Write([]byte(temp.Text))
|
||||
for _, extra := range temp.Extra {
|
||||
sb.Write([]byte(extra.Text))
|
||||
}
|
||||
d.CleanText = cleanMinecraftFormatting(sb.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return an error if neither unmarshaling succeeds
|
||||
return fmt.Errorf("could not unmarshal TextComponent: data is neither a string nor an object with 'text' and 'extra' fields")
|
||||
}
|
||||
|
||||
// Function to clean Minecraft formatting from a text
|
||||
func cleanMinecraftFormatting(text string) string {
|
||||
// Regex to match Minecraft formatting codes like § and &
|
||||
regex := regexp.MustCompile(`(?i)§[0-9A-FK-OR]`)
|
||||
return regex.ReplaceAllString(text, "")
|
||||
}
|
||||
|
||||
// Custom unmarshaler for the TextComponent type
|
||||
func (f *Favicon) UnmarshalJSON(data []byte) error {
|
||||
// Try to unmarshal as a simple string
|
||||
var text string
|
||||
if err := json.Unmarshal(data, &text); err == nil {
|
||||
if strings.HasPrefix(text, "data:image/png;base64,") {
|
||||
trimmed := strings.TrimPrefix(text, "data:image/png;base64,")
|
||||
pngDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(trimmed))
|
||||
pngData, err := io.ReadAll(pngDecoder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.PngData = pngData
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("wrong prefix for favicon")
|
||||
}
|
||||
}
|
||||
return errors.New("could not unmarshal Favicon")
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
RawMessage string `json:"rawMessage,omitempty"`
|
||||
Version struct {
|
||||
Name string `json:"name"`
|
||||
Protocol int32 `json:"protocol"`
|
||||
} `json:"version"`
|
||||
Players struct {
|
||||
Max int32 `json:"max"`
|
||||
Online int32 `json:"online"`
|
||||
Sample []Player `json:"sample"`
|
||||
} `json:"players"`
|
||||
Description TextComponent `json:"description"` // Handles both string and object with "text" and "extra" fields
|
||||
Favicon Favicon `json:"favicon"`
|
||||
EnforcesSecureChat bool `json:"enforcesSecureChat"`
|
||||
PreventsChatReports bool `json:"preventsChatReports"`
|
||||
CompressionThreshold int32 `json:"compressionThreshold,omitempty"`
|
||||
IsOfflineMode bool `json:"IsOfflineMode,omitempty"`
|
||||
PluginDataSent map[string]string `json:"PluginDataSent,omitempty"`
|
||||
FeatureFlags []string `json:"featureFlags,omitempty"`
|
||||
EnabledDatapacks []DatapackInfo `json:"EnabledDatapacks,omitempty"`
|
||||
Encryption bool `json:"Encryption,omitempty"`
|
||||
Message TextComponent `json:"Message,omitempty"`
|
||||
RegistryDatas []RegistryData `json:"RegistryDatas,omitempty"`
|
||||
Tags []UpdateTag `json:"Tags,omitempty"`
|
||||
Username string `json:"Username,omitempty"`
|
||||
PlayerLoginInfo LoginInfo `json:"PlayerLoginInfo,omitempty"`
|
||||
ServerDifficulty DifficultyObject `json:"ServerDifficulty,omitempty"`
|
||||
PlayerAbilities PlayerAbilitiesObject `json:"PlayerAbilities,omitempty"`
|
||||
PlayerSlot byte `json:"PlayerSlot,omitempty"`
|
||||
PlayerLocation PlayerPosition `json:"PlayerLocation,omitempty"`
|
||||
DefaultPositionSpawn DefaultSpawnPosition `json:"DefaultPositionSpawn,omitempty"`
|
||||
TimeOfDay int64 `json:"TimeOfDay,omitempty"`
|
||||
WorldAge int64 `json:"worldAge,omitempty"`
|
||||
WorldBorder WorldBorderInfo `json:"worldBorder,omitempty"`
|
||||
PlayersInfo []PlayerUpdate `json:"playersinfo,omitempty"`
|
||||
}
|
Loading…
Reference in New Issue
Block a user