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