init
This commit is contained in:
commit
efd6d64d01
0
.gitignore
vendored
Normal file
0
.gitignore
vendored
Normal file
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module mcsrv
|
||||||
|
|
||||||
|
go 1.23.1
|
||||||
|
|
||||||
|
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=
|
BIN
icon.png
Normal file
BIN
icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
387
main.go
Normal file
387
main.go
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HOST = "127.0.0.2"
|
||||||
|
PORT = "25565"
|
||||||
|
TYPE = "tcp"
|
||||||
|
ProtocolVersion = 767
|
||||||
|
VERSION = "1.21.1"
|
||||||
|
CompressionThreshold = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
var maxPlayers int32
|
||||||
|
var icon string
|
||||||
|
var serverID = make([]byte, 20)
|
||||||
|
|
||||||
|
var serverPrivateKey *rsa.PrivateKey
|
||||||
|
var serverPublicKey *rsa.PublicKey
|
||||||
|
|
||||||
|
var usernameRegex = regexp.MustCompile("^[a-zA-Z0-9_]{3,16}$")
|
||||||
|
|
||||||
|
var world World
|
||||||
|
|
||||||
|
func readIcon(fileName string) (icon string) {
|
||||||
|
iconData, err := os.ReadFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
icon = "data:image/png;base64," + base64.StdEncoding.EncodeToString(iconData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
maxPlayers = 200
|
||||||
|
icon = readIcon("icon.png")
|
||||||
|
|
||||||
|
world = World{
|
||||||
|
Difficulty: 0,
|
||||||
|
DifficultyLocked: false,
|
||||||
|
Hardcore: false,
|
||||||
|
Dimensions: []Dimension{
|
||||||
|
{
|
||||||
|
ID: 0,
|
||||||
|
Name: "minecraft:overworld",
|
||||||
|
}, {
|
||||||
|
ID: 1,
|
||||||
|
Name: "minecraft:the_nether",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
Name: "minecraft:the_end",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ViewDistance: 4,
|
||||||
|
SimulationDistance: 4,
|
||||||
|
ReducedDebugInfo: false,
|
||||||
|
EnableRespawnScreen: true,
|
||||||
|
DoLimitedCrafting: false,
|
||||||
|
Seed: uuid.New(),
|
||||||
|
FeatureFlags: []string{"minecraft:vanilla", "minecraft:bundle", "minecraft:trade_rebalance"},
|
||||||
|
DataPacks: []DatapackInfo{{
|
||||||
|
Namespace: "minecraft",
|
||||||
|
ID: "core",
|
||||||
|
Version: "1.21",
|
||||||
|
}},
|
||||||
|
ReportDetails: map[string]string{"I hate myself": "YES, REALLY"},
|
||||||
|
ServerLinks: []ServerLink{{
|
||||||
|
LabelString: "",
|
||||||
|
LabelInt: 0,
|
||||||
|
URL: "https://brn.systems",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
//serverID, err = serverIDCreate()
|
||||||
|
serverID = []byte("")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
serverPrivateKey, serverPublicKey, err = GenerateRSAKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
listen, err := net.Listen(TYPE, HOST+":"+PORT)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// close listener
|
||||||
|
defer func(listen net.Listener) {
|
||||||
|
err := listen.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}(listen)
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listen.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
go handleRequest(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRequest(conn net.Conn) {
|
||||||
|
var errOut error
|
||||||
|
var player = Player{
|
||||||
|
state: 0,
|
||||||
|
conn: conn,
|
||||||
|
name: "",
|
||||||
|
version: 0,
|
||||||
|
requestedAddress: "",
|
||||||
|
requestedPort: 0,
|
||||||
|
reader: bufio.NewReader(conn),
|
||||||
|
compressionThreshold: -1,
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
packetID, packetData, err := player.readPacket()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch player.state {
|
||||||
|
case 0: // Handshake state
|
||||||
|
switch packetID {
|
||||||
|
case 0x00:
|
||||||
|
version, address, port, nextState, err := getHandshakePacket(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.version = version
|
||||||
|
player.requestedAddress = address
|
||||||
|
player.requestedPort = port
|
||||||
|
player.state = nextState
|
||||||
|
if player.state == 3 {
|
||||||
|
player.state = 2
|
||||||
|
player.transferred = true
|
||||||
|
}
|
||||||
|
|
||||||
|
world.Players = append(world.Players, player)
|
||||||
|
default:
|
||||||
|
errOut = fmt.Errorf("unexpected packet %x in handshake state", packetID)
|
||||||
|
log.Println(errOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: // Status state
|
||||||
|
switch packetID {
|
||||||
|
case 0x00:
|
||||||
|
err := player.sendStatusResponse("Hello there")
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 0x01:
|
||||||
|
payload, err := getPingPacket(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = player.sendPongPacket(payload)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
errOut = fmt.Errorf("unexpected packet %x in status state", packetID)
|
||||||
|
log.Println(errOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: // Login state
|
||||||
|
switch packetID {
|
||||||
|
case 0x00:
|
||||||
|
username, playerUUID, err := getLoginStart(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.name = username
|
||||||
|
player.uuid = playerUUID
|
||||||
|
if !usernameRegex.MatchString(player.name) {
|
||||||
|
errOut = errors.New("bad username")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = player.sendEncryptionRequest()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 0x01:
|
||||||
|
sharedSecretFromClient, verifyTokenFromClient, err := getEncryptionResponse(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
decryptedVerifyTokenFromClient, err := rsa.DecryptPKCS1v15(rand.Reader, serverPrivateKey, verifyTokenFromClient)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(player.verifyToken, decryptedVerifyTokenFromClient) {
|
||||||
|
errOut = errors.New("wrong session verify token")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
decryptedSecret, err := rsa.DecryptPKCS1v15(rand.Reader, serverPrivateKey, sharedSecretFromClient)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
success, err := player.hasJoinedSession(decryptedSecret)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !success {
|
||||||
|
errOut = errors.New("joined session not found")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if player.profile.ID != strings.Replace(player.uuid.String(), "-", "", -1) || player.profile.Name != player.name {
|
||||||
|
errOut = errors.New("wrong session name")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.encryptStream, player.decryptStream, err = NewCFB8Cipher(decryptedSecret)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = player.sendCompression()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.compressionThreshold = CompressionThreshold
|
||||||
|
log.Println("sent compression")
|
||||||
|
err = player.sendLoginSuccess()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Println("sent login success")
|
||||||
|
err = player.sendDataPacks()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = player.sendRegistryData()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = player.sendFeatureFlags()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = player.sendUpdateTags()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = player.sendReportDetails()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = player.sendServerLinks()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x03:
|
||||||
|
player.state = 3
|
||||||
|
|
||||||
|
default:
|
||||||
|
errOut = fmt.Errorf("unexpected packet %x in login state", packetID)
|
||||||
|
log.Println(errOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3: //configuration
|
||||||
|
switch packetID {
|
||||||
|
case 0x00:
|
||||||
|
locale, viewDistance, chatMode, chatColors, skinParts, isRightHanded, textFiltering, serverListing, err := getClientInformation(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.locale = locale
|
||||||
|
player.viewDistance = viewDistance
|
||||||
|
player.chatMode = chatMode
|
||||||
|
player.chatColors = chatColors
|
||||||
|
player.skinParts = skinParts
|
||||||
|
player.isRightHanded = isRightHanded
|
||||||
|
player.textFiltering = textFiltering
|
||||||
|
player.serverListing = serverListing
|
||||||
|
case 0x01:
|
||||||
|
//TODO Implement cookie
|
||||||
|
case 0x02:
|
||||||
|
_, _, _ = getPluginPacket(packetData)
|
||||||
|
case 0x03:
|
||||||
|
player.state = 4
|
||||||
|
go player.sendKeepAlives()
|
||||||
|
err := player.sendPlayStart()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.sendDifficulty()
|
||||||
|
//player.send
|
||||||
|
case 0x04:
|
||||||
|
player.keepAliveReceivedTimestamp = time.Now().Unix()
|
||||||
|
case 0x05:
|
||||||
|
err := player.getPongPacket(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 0x06:
|
||||||
|
err := player.getResourcePackPacket(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 0x07:
|
||||||
|
err := player.getDataPacksPacket(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
errOut = fmt.Errorf("unexpected packet %x in configration state", packetID)
|
||||||
|
log.Println(errOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
switch packetID {
|
||||||
|
|
||||||
|
default:
|
||||||
|
errOut = fmt.Errorf("unexpected packet %x in play state", packetID)
|
||||||
|
log.Println(errOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
errOut = fmt.Errorf("unknown player state %d", player.state)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if errOut != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errOut != nil {
|
||||||
|
log.Println(errOut.Error())
|
||||||
|
_ = player.sendDisconnect(fmt.Sprintf("Your server thread crashed: %s", errOut.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close connection
|
||||||
|
err := conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
for index, client := range world.Players {
|
||||||
|
if player.conn == client.conn {
|
||||||
|
world.Players = append(world.Players[:index], world.Players[index+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
812
packetLib.go
Normal file
812
packetLib.go
Normal file
@ -0,0 +1,812 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateRandomBytes(size int) ([]byte, error) {
|
||||||
|
randomBytes := make([]byte, size)
|
||||||
|
_, err := rand.Read(randomBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return randomBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func serverIDCreate() ([]byte, error) {
|
||||||
|
result := make([]byte, 20)
|
||||||
|
charRange := 0x7E - 0x21 + 1
|
||||||
|
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
// Generate a random number in the range [charStart, charEnd]
|
||||||
|
num, err := rand.Int(rand.Reader, big.NewInt(int64(charRange)))
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
result[i] = byte(0x21 + num.Int64())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 RegistryEntry struct {
|
||||||
|
EntryID string `json:"entryID,omitempty"`
|
||||||
|
HasNBT bool `json:"hasNBT,omitempty"`
|
||||||
|
//TODO implement the nbt data
|
||||||
|
NBTData *bytes.Buffer `json:"nbtData,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegistryData struct {
|
||||||
|
RegistryID string `json:"registryID,omitempty"`
|
||||||
|
Entries []RegistryEntry `json:"entries,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 DatapackInfo struct {
|
||||||
|
Namespace string `json:"namespace,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerLink struct {
|
||||||
|
LabelString string `json:"labelString,omitempty"`
|
||||||
|
LabelInt int32 `json:"labelInt,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRSAKeyPair generates an RSA key pair and returns the public and private keys.
|
||||||
|
func GenerateRSAKeyPair() (*rsa.PrivateKey, *rsa.PublicKey, error) {
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return privateKey, &privateKey.PublicKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportRSAPublicKey exports the public key in ASN.1 DER format.
|
||||||
|
func ExportRSAPublicKey(pubKey *rsa.PublicKey) ([]byte, error) {
|
||||||
|
// Marshal the public key to ASN.1 DER format
|
||||||
|
pubASN1, err := x509.MarshalPKIXPublicKey(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Return the raw DER-encoded bytes
|
||||||
|
return pubASN1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type cfb8 struct {
|
||||||
|
b cipher.Block
|
||||||
|
blockSize int
|
||||||
|
in []byte
|
||||||
|
out []byte
|
||||||
|
|
||||||
|
decrypt bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *cfb8) XORKeyStream(dst, src []byte) {
|
||||||
|
for i := range src {
|
||||||
|
x.b.Encrypt(x.out, x.in)
|
||||||
|
copy(x.in[:x.blockSize-1], x.in[1:])
|
||||||
|
if x.decrypt {
|
||||||
|
x.in[x.blockSize-1] = src[i]
|
||||||
|
}
|
||||||
|
dst[i] = src[i] ^ x.out[0]
|
||||||
|
if !x.decrypt {
|
||||||
|
x.in[x.blockSize-1] = dst[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCFB8Encryptor returns a Stream which encrypts with cipher feedback mode
|
||||||
|
// (segment size = 8), using the given Block. The iv must be the same length as
|
||||||
|
// the Block's block size.
|
||||||
|
func newCFB8Encryptor(block cipher.Block, iv []byte) cipher.Stream {
|
||||||
|
return newCFB8(block, iv, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCFB8Decryptor returns a Stream which decrypts with cipher feedback mode
|
||||||
|
// (segment size = 8), using the given Block. The iv must be the same length as
|
||||||
|
// the Block's block size.
|
||||||
|
func newCFB8Decryptor(block cipher.Block, iv []byte) cipher.Stream {
|
||||||
|
return newCFB8(block, iv, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCFB8(block cipher.Block, iv []byte, decrypt bool) cipher.Stream {
|
||||||
|
blockSize := block.BlockSize()
|
||||||
|
if len(iv) != blockSize {
|
||||||
|
// stack trace will indicate whether it was de or encryption
|
||||||
|
panic("cipher.newCFB: IV length must equal block size")
|
||||||
|
}
|
||||||
|
x := &cfb8{
|
||||||
|
b: block,
|
||||||
|
blockSize: blockSize,
|
||||||
|
out: make([]byte, blockSize),
|
||||||
|
in: make([]byte, blockSize),
|
||||||
|
decrypt: decrypt,
|
||||||
|
}
|
||||||
|
copy(x.in, iv)
|
||||||
|
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCFB8Cipher Initialize AES/CFB8 encryption using the shared secret
|
||||||
|
func NewCFB8Cipher(secret []byte) (cipher.Stream, cipher.Stream, error) {
|
||||||
|
block, err := aes.NewCipher(secret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create CFB8 encryption/decryption ciphers using the custom implementation
|
||||||
|
iv := secret // Initial vector (IV) is the same as the secret in this case
|
||||||
|
encryptStream := newCFB8Encryptor(block, iv)
|
||||||
|
decryptStream := newCFB8Decryptor(block, iv)
|
||||||
|
|
||||||
|
return encryptStream, decryptStream, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthDigest(parts [][]byte) string {
|
||||||
|
h := sha1.New()
|
||||||
|
|
||||||
|
// Hash each part incrementally
|
||||||
|
for _, part := range parts {
|
||||||
|
io.WriteString(h, string(part))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize the hash
|
||||||
|
hash := h.Sum(nil)
|
||||||
|
|
||||||
|
// Check for negative hashes
|
||||||
|
negative := (hash[0] & 0x80) == 0x80
|
||||||
|
if negative {
|
||||||
|
hash = twosComplement(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim away zeroes
|
||||||
|
res := strings.TrimLeft(hex.EncodeToString(hash), "0")
|
||||||
|
if negative {
|
||||||
|
res = "-" + res
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// little endian
|
||||||
|
func twosComplement(p []byte) []byte {
|
||||||
|
carry := true
|
||||||
|
for i := len(p) - 1; i >= 0; i-- {
|
||||||
|
p[i] = ^p[i]
|
||||||
|
if carry {
|
||||||
|
carry = p[i] == 0xff
|
||||||
|
p[i]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
type MojangProfile struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Properties []MojangProperty `json:"properties"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MojangProperty struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) hasJoinedSession(sharedSecret []byte) (bool, error) {
|
||||||
|
serverKey, err := ExportRSAPublicKey(serverPublicKey)
|
||||||
|
serverIDHash := AuthDigest([][]byte{serverID, sharedSecret, serverKey})
|
||||||
|
remoteAddr := strings.Split(player.conn.RemoteAddr().String(), ":")[0]
|
||||||
|
url := fmt.Sprintf("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s", player.name, serverIDHash, remoteAddr)
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return false, fmt.Errorf("failed to verify session: %s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile MojangProfile
|
||||||
|
err = json.Unmarshal(body, &profile)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
player.profile = profile
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptPacket encrypts a packet using the AES/CFB8 cipher stream.
|
||||||
|
func EncryptPacket(stream cipher.Stream, data *bytes.Buffer) *bytes.Buffer {
|
||||||
|
if stream == nil {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
encryptedData := make([]byte, data.Len())
|
||||||
|
stream.XORKeyStream(encryptedData, data.Bytes())
|
||||||
|
return bytes.NewBuffer(encryptedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptingReader returns a *bufio.Reader that provides decrypted data from the original reader.
|
||||||
|
func DecryptingReader(stream cipher.Stream, reader *bufio.Reader) *bufio.Reader {
|
||||||
|
pr, pw := io.Pipe() // create a pipe to stream decrypted data
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer pw.Close() // ensure the pipe writer is closed when done
|
||||||
|
buf := make([]byte, 4096) // buffer size, adjust as needed
|
||||||
|
for {
|
||||||
|
n, err := reader.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pw.CloseWithError(err) // propagate any non-EOF error to the pipe reader
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := buf[:n]
|
||||||
|
decryptedBlock := make([]byte, len(data))
|
||||||
|
if stream != nil {
|
||||||
|
stream.XORKeyStream(decryptedBlock, data) // decrypt the data
|
||||||
|
} else {
|
||||||
|
decryptedBlock = data // if no stream, just pass the data through
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = pw.Write(decryptedBlock) // write decrypted data to the pipe writer
|
||||||
|
if err != nil {
|
||||||
|
pw.CloseWithError(err) // propagate write errors
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return bufio.NewReader(pr) // wrap the pipe reader in a bufio.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompress function using zlib
|
||||||
|
func decompress(data *bytes.Buffer) (buf *bytes.Buffer, err error) {
|
||||||
|
reader, err := zlib.NewReader(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func(reader io.ReadCloser) {
|
||||||
|
_ = reader.Close()
|
||||||
|
}(reader)
|
||||||
|
|
||||||
|
var decompressedData bytes.Buffer
|
||||||
|
_, err = io.Copy(&decompressedData, reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return &decompressedData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compress function using zlib
|
||||||
|
func compress(data *bytes.Buffer) (*bytes.Buffer, error) {
|
||||||
|
var compressedData bytes.Buffer
|
||||||
|
writer := zlib.NewWriter(&compressedData)
|
||||||
|
|
||||||
|
_, err := writer.Write(data.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &compressedData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified version of `sendPacket` with hexdump logging, including unencrypted data
|
||||||
|
func (player *Player) sendPacket(packetID int32, packetData *bytes.Buffer) (err error) {
|
||||||
|
var origDataBuffer bytes.Buffer
|
||||||
|
addVarint(&origDataBuffer, packetID) // Add the Packet ID as a VarInt
|
||||||
|
_, err = origDataBuffer.ReadFrom(packetData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var outBuffer bytes.Buffer
|
||||||
|
var outTempBuffer bytes.Buffer
|
||||||
|
length := int32(origDataBuffer.Len()) // Get the length of the uncompressed packet
|
||||||
|
compressed := false // Track if the packet is compressed
|
||||||
|
|
||||||
|
// Compression based on a threshold
|
||||||
|
if player.compressionThreshold >= 0 && length >= player.compressionThreshold {
|
||||||
|
compressed = true // Mark that compression was applied
|
||||||
|
compressedData, err := compress(&origDataBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
origDataBuffer = *compressedData
|
||||||
|
|
||||||
|
// Add the uncompressed length to outTempBuffer
|
||||||
|
addVarint(&outTempBuffer, length)
|
||||||
|
}
|
||||||
|
encrypted := player.encryptStream != nil
|
||||||
|
|
||||||
|
// Log the unencrypted packet data
|
||||||
|
log.Printf("Unencrypted Packet ID: %d", packetID)
|
||||||
|
log.Printf("Unencrypted Packet Length: %d (compressed: %v, encrypted: %v)", origDataBuffer.Len(), compressed, encrypted)
|
||||||
|
log.Printf("Hexdump of Unencrypted Packet:\n%s", hex.Dump(origDataBuffer.Bytes()))
|
||||||
|
|
||||||
|
// Encrypt the (compressed or uncompressed) data buffer
|
||||||
|
dataBuffer := origDataBuffer
|
||||||
|
|
||||||
|
// Append the (compressed or uncompressed) data buffer to outTempBuffer
|
||||||
|
_, err = outTempBuffer.ReadFrom(&dataBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the total packet length to outBuffer
|
||||||
|
addVarint(&outBuffer, int32(outTempBuffer.Len()))
|
||||||
|
_, err = outBuffer.ReadFrom(&outTempBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted {
|
||||||
|
outBuffer = *EncryptPacket(player.encryptStream, &outBuffer)
|
||||||
|
}
|
||||||
|
_, err = player.conn.Write(outBuffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified version of `readPacket` with hexdump logging
|
||||||
|
func (player *Player) readPacket() (packetID int32, packetData *bytes.Buffer, err error) {
|
||||||
|
packetData = &bytes.Buffer{}
|
||||||
|
finalReader := player.reader
|
||||||
|
if player.decryptStream != nil {
|
||||||
|
finalReader = DecryptingReader(player.decryptStream, player.reader)
|
||||||
|
}
|
||||||
|
packetLength, _, err := readVarint(finalReader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
packetDataBytes := make([]byte, packetLength)
|
||||||
|
nt, err := io.ReadFull(finalReader, packetDataBytes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = packetData.Write(packetDataBytes)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &bytes.Buffer{}, err
|
||||||
|
}
|
||||||
|
n := int32(nt)
|
||||||
|
if n != packetLength {
|
||||||
|
err = errors.New("packet read length mismatch")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataLength int
|
||||||
|
compressed := false // Track if the packet is compressed
|
||||||
|
if player.compressionThreshold > 0 {
|
||||||
|
dataLengthTemp, errTemp := receiveVarint(packetData)
|
||||||
|
err = errTemp
|
||||||
|
dataLength = int(dataLengthTemp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If dataLength > 0, it means the packet is compressed
|
||||||
|
if dataLength > 0 {
|
||||||
|
compressed = true
|
||||||
|
packetData, err = decompress(packetData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract packet ID
|
||||||
|
packetID, err = receiveVarint(packetData)
|
||||||
|
|
||||||
|
// Log the packet information
|
||||||
|
log.Printf("Received Packet ID: %d", packetID)
|
||||||
|
log.Printf("Packet Length: %d (compressed: %v)", packetLength, compressed)
|
||||||
|
log.Printf("Hexdump of Decoded Packet:\n%s", hex.Dump(packetData.Bytes()))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func addVarint(buffer *bytes.Buffer, value int32) {
|
||||||
|
uValue := uint32(value) // Explicitly cast to an unsigned type
|
||||||
|
for {
|
||||||
|
b := byte(uValue & 0x7F)
|
||||||
|
uValue >>= 7
|
||||||
|
|
||||||
|
if (uValue != 0 && uValue != 0xFFFFFFFF) || (b&0x40 != 0) {
|
||||||
|
b |= 0x80
|
||||||
|
}
|
||||||
|
|
||||||
|
addByte(buffer, b)
|
||||||
|
|
||||||
|
if uValue == 0 || uValue == 0xFFFFFFFF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addVarlong(buffer *bytes.Buffer, value int64) {
|
||||||
|
uValue := uint64(value) // Explicitly cast to an unsigned type
|
||||||
|
for {
|
||||||
|
b := byte(uValue & 0x7F)
|
||||||
|
uValue >>= 7
|
||||||
|
|
||||||
|
if (uValue != 0 && uValue != 0xFFFFFFFFFFFFFFFF) || (b&0x40 != 0) {
|
||||||
|
b |= 0x80
|
||||||
|
}
|
||||||
|
|
||||||
|
addByte(buffer, b)
|
||||||
|
|
||||||
|
if uValue == 0 || uValue == 0xFFFFFFFFFFFFFFFF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readVarint(r *bufio.Reader) (value int32, length uint32, err error) {
|
||||||
|
var (
|
||||||
|
b byte
|
||||||
|
shift uint64
|
||||||
|
uValue uint32
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
b, err = r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
length++
|
||||||
|
|
||||||
|
uValue |= (uint32(b) & 0x7F) << shift
|
||||||
|
shift += 7
|
||||||
|
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shift < 8*uint64(length) && b&0x40 != 0 {
|
||||||
|
uValue |= ^uint32(0) << shift
|
||||||
|
}
|
||||||
|
value = int32(uValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveVarint(buf *bytes.Buffer) (value int32, err error) {
|
||||||
|
var (
|
||||||
|
b byte
|
||||||
|
shift uint64
|
||||||
|
uValue uint32
|
||||||
|
length uint32
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < buf.Len(); i++ {
|
||||||
|
b, err = buf.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
length++
|
||||||
|
|
||||||
|
uValue |= (uint32(b) & 0x7F) << shift
|
||||||
|
shift += 7
|
||||||
|
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shift < 8*uint64(length) && b&0x40 != 0 {
|
||||||
|
uValue |= ^uint32(0) << shift
|
||||||
|
}
|
||||||
|
value = int32(uValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveVarlong(buf *bytes.Buffer) (currentValue int64) {
|
||||||
|
var (
|
||||||
|
b byte
|
||||||
|
shift uint64
|
||||||
|
uValue uint64
|
||||||
|
length uint32
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < buf.Len(); i++ {
|
||||||
|
bx, err := buf.ReadByte()
|
||||||
|
b = bx
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
length++
|
||||||
|
|
||||||
|
uValue |= (uint64(b) & 0x7F) << shift
|
||||||
|
shift += 7
|
||||||
|
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shift < 8*uint64(length) && b&0x40 != 0 {
|
||||||
|
uValue |= ^uint64(0) << shift
|
||||||
|
}
|
||||||
|
currentValue = int64(uValue)
|
||||||
|
return currentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func addUUID(buffer *bytes.Buffer, inUUID uuid.UUID) int32 {
|
||||||
|
binaryUUID, err := inUUID.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error marshalling UUID:", err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
buffer.Write(binaryUUID)
|
||||||
|
return int32(len(binaryUUID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes a UUID from the buffer.
|
||||||
|
func receiveUUID(buffer *bytes.Buffer) (value uuid.UUID, err error) {
|
||||||
|
b := make([]byte, 16)
|
||||||
|
_, err = buffer.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.UUID{}, err
|
||||||
|
}
|
||||||
|
value, err = uuid.FromBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.UUID{}, err
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addByte(buffer *bytes.Buffer, byte byte) {
|
||||||
|
buffer.WriteByte(byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addBool(buffer *bytes.Buffer, boolean bool) {
|
||||||
|
var boolByte byte = 0x00
|
||||||
|
if boolean {
|
||||||
|
boolByte = 0x01
|
||||||
|
}
|
||||||
|
buffer.WriteByte(boolByte)
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveBool(buffer *bytes.Buffer) (boolean bool, err error) {
|
||||||
|
b, err := buffer.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
boolean = b == 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func addUint16(buffer *bytes.Buffer, value uint16) {
|
||||||
|
b := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(b, value)
|
||||||
|
buffer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes an uint16 from the buffer.
|
||||||
|
func receiveUint16(buffer *bytes.Buffer) (value uint16) {
|
||||||
|
// Create a byte slice to hold the 2 bytes for the uint16.
|
||||||
|
b := make([]byte, 2)
|
||||||
|
|
||||||
|
// Read exactly 2 bytes from the buffer.
|
||||||
|
_, err := buffer.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the byte slice to uint16 using BigEndian.
|
||||||
|
value = binary.BigEndian.Uint16(b)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func addInt64(buffer *bytes.Buffer, value int64) {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(b, uint64(value))
|
||||||
|
buffer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveInt64(buffer *bytes.Buffer) (value int64) {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
_, err := buffer.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
value = int64(binary.BigEndian.Uint64(b))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFloat32(buffer *bytes.Buffer, value float32) {
|
||||||
|
bits := math.Float32bits(value)
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(b, bits)
|
||||||
|
buffer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes a float32 from the buffer.
|
||||||
|
func receiveFloat32(buffer *bytes.Buffer) (value float32) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
_, err := buffer.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
value = math.Float32frombits(binary.BigEndian.Uint32(b))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFloat64(buffer *bytes.Buffer, value float64) {
|
||||||
|
bits := math.Float64bits(value)
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(b, bits)
|
||||||
|
buffer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes a float64 from the buffer.
|
||||||
|
func receiveFloat64(buffer *bytes.Buffer) (value float64) {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
_, err := buffer.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
value = math.Float64frombits(binary.BigEndian.Uint64(b))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func addInt32(buffer *bytes.Buffer, value int32) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(b, uint32(value))
|
||||||
|
buffer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes an int32 from the buffer.
|
||||||
|
func receiveInt32(buffer *bytes.Buffer) (value int32) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
_, err := buffer.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
value = int32(binary.BigEndian.Uint32(b))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func addInt16(buffer *bytes.Buffer, value int16) {
|
||||||
|
b := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(b, uint16(value))
|
||||||
|
buffer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addUint32(buffer *bytes.Buffer, value uint32) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(b, value)
|
||||||
|
buffer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes an uint32 from the buffer.
|
||||||
|
func receiveUint32(buffer *bytes.Buffer) (value uint32, err error) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
if _, err = buffer.Read(b); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
value = binary.BigEndian.Uint32(b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func addString(buffer *bytes.Buffer, str string) {
|
||||||
|
// Encode string length using VarInt.
|
||||||
|
addVarint(buffer, int32(len(str)))
|
||||||
|
// Append the string bytes.
|
||||||
|
buffer.Write([]byte(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes a string from the buffer.
|
||||||
|
func receiveString(buf *bytes.Buffer) (strOut string, err error) {
|
||||||
|
stringLen, err := receiveVarint(buf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
stringBytes := make([]byte, stringLen)
|
||||||
|
_, err = buf.Read(stringBytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
strOut = string(stringBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// addTextComponent serializes a TextComponent to JSON and adds it to the buffer
|
||||||
|
func addTextComponent(buffer *bytes.Buffer, component TextComponent) {
|
||||||
|
// Serialize the TextComponent to a JSON string
|
||||||
|
jsonBytes, err := json.Marshal(component)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert JSON bytes to a string
|
||||||
|
jsonString := string(jsonBytes)
|
||||||
|
|
||||||
|
// Use addString to addExplosion the serialized TextComponent JSON to the buffer
|
||||||
|
addString(buffer, jsonString)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveTextComponent(buf *bytes.Buffer) (component TextComponent, err error) {
|
||||||
|
jsonString, err := receiveString(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
361
playPackets.go
Normal file
361
playPackets.go
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"github.com/Tnze/go-mc/nbt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (player *Player) sendBundleDelimiter() error {
|
||||||
|
return player.sendPacket(0x00, &bytes.Buffer{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendSpawnEntity(entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
entity.add(&buf)
|
||||||
|
return player.sendPacket(0x01, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendSpawnXP(xpOrb XPOrb) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
xpOrb.add(&buf)
|
||||||
|
return player.sendPacket(0x02, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendEntityAnimation(entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
addByte(&buf, entity.Animation)
|
||||||
|
return player.sendPacket(0x03, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendAwardStatistics() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, int32(len(player.statistics)))
|
||||||
|
for _, stat := range player.statistics {
|
||||||
|
addVarint(&buf, stat.CategoryID)
|
||||||
|
addVarint(&buf, stat.StatisticID)
|
||||||
|
addVarint(&buf, stat.Value)
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x04, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendBlockChangeACK(seqID int32) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, seqID)
|
||||||
|
return player.sendPacket(0x05, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendBlockDestroyStage(entity *Entity, block *Block) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
block.Position.add(&buf)
|
||||||
|
addByte(&buf, block.DestroyStage)
|
||||||
|
return player.sendPacket(0x06, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendBlockEntityData(entity *Entity, block *Block) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
block.Position.add(&buf)
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
var nbtBuf bytes.Buffer
|
||||||
|
nbtWriter := bufio.NewWriter(&nbtBuf)
|
||||||
|
nbtEnc := nbt.NewEncoder(nbtWriter)
|
||||||
|
for key, val := range block.BlockEntityData {
|
||||||
|
err := nbtEnc.Encode(val, key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nbtWriter.Flush()
|
||||||
|
buf.ReadFrom(&nbtBuf)
|
||||||
|
return player.sendPacket(0x07, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendBlockAction(block *Block, actionID uint8, actionParam uint8) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
block.Position.add(&buf)
|
||||||
|
addByte(&buf, actionID)
|
||||||
|
addByte(&buf, actionParam)
|
||||||
|
addVarint(&buf, block.BlockTypeID)
|
||||||
|
return player.sendPacket(0x08, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendBlockUpdate(block *Block) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
block.Position.add(&buf)
|
||||||
|
addVarint(&buf, block.BlockStateID)
|
||||||
|
return player.sendPacket(0x09, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendDifficulty() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addByte(&buf, world.Difficulty)
|
||||||
|
addBool(&buf, world.DifficultyLocked)
|
||||||
|
return player.sendPacket(0x0B, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendChunks() error {
|
||||||
|
//var buf bytes.Buffer
|
||||||
|
var bufFinished bytes.Buffer
|
||||||
|
err := player.sendPacket(0x0D, &bytes.Buffer{}) //chunk batch start
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//count:= int32(len(chunks))
|
||||||
|
count := int32(0)
|
||||||
|
addVarint(&bufFinished, count)
|
||||||
|
err = player.sendPacket(0x0C, &bufFinished) //chunk batch end
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//0x0E is chunk biomes, might implement later
|
||||||
|
//0x10 is Command Suggestions Response, might implement later
|
||||||
|
//0x11 is Commands, might implement later
|
||||||
|
|
||||||
|
func (player *Player) sendCloseContainer(windowID uint8) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addByte(&buf, windowID)
|
||||||
|
return player.sendPacket(0x12, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendContainerContent(windowID uint8, slots []*Slot, cursor *Slot) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addByte(&buf, windowID)
|
||||||
|
addVarint(&buf, player.lastContainerStateID)
|
||||||
|
player.lastContainerStateID++
|
||||||
|
addVarint(&buf, int32(len(slots)))
|
||||||
|
for _, slot := range slots {
|
||||||
|
slot.add(&buf)
|
||||||
|
}
|
||||||
|
cursor.add(&buf)
|
||||||
|
return player.sendPacket(0x13, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendContainerProperty(windowID uint8, property int16, value int16) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addByte(&buf, windowID)
|
||||||
|
addInt16(&buf, property)
|
||||||
|
addInt16(&buf, value)
|
||||||
|
return player.sendPacket(0x14, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendContainerSlot(windowID uint8, slotIndex int16, slot *Slot) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addByte(&buf, windowID)
|
||||||
|
addVarint(&buf, player.lastContainerStateID)
|
||||||
|
player.lastContainerStateID++
|
||||||
|
addInt16(&buf, slotIndex)
|
||||||
|
slot.add(&buf)
|
||||||
|
return player.sendPacket(0x15, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
//0x16 is cookie, might get implemented cross-state
|
||||||
|
|
||||||
|
func (player *Player) sendCooldown(itemID int32, cooldownTicks int32) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, itemID)
|
||||||
|
addVarint(&buf, cooldownTicks)
|
||||||
|
return player.sendPacket(0x17, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendChatSuggestions(action int32, entries []string) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, action)
|
||||||
|
addVarint(&buf, int32(len(entries)))
|
||||||
|
for _, entry := range entries {
|
||||||
|
addString(&buf, entry)
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x18, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
//0x19 is plugin message, should be implemented globally
|
||||||
|
|
||||||
|
func (player *Player) sendDamageEvent(damagedEntity *Entity, damageType int32, sourceEntity *Entity, sourceDirectEntity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, damagedEntity.ID)
|
||||||
|
addVarint(&buf, damageType)
|
||||||
|
addVarint(&buf, sourceEntity.ID)
|
||||||
|
if sourceDirectEntity != nil {
|
||||||
|
addVarint(&buf, sourceDirectEntity.ID)
|
||||||
|
} else {
|
||||||
|
addVarint(&buf, sourceEntity.ID)
|
||||||
|
}
|
||||||
|
addBool(&buf, true)
|
||||||
|
sourceEntity.Position.add(&buf)
|
||||||
|
return player.sendPacket(0x1A, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendDebugSample(sampleType int32, sample []int64) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, int32(len(sample)))
|
||||||
|
for _, val := range sample {
|
||||||
|
addInt64(&buf, val)
|
||||||
|
}
|
||||||
|
addVarint(&buf, sampleType)
|
||||||
|
return player.sendPacket(0x1B, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) deleteMessage(messageID int32, signature []byte) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, messageID)
|
||||||
|
if messageID == 0 {
|
||||||
|
for _, sigPiece := range signature {
|
||||||
|
addByte(&buf, sigPiece)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x1C, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
//0x1D is disconnect, should be implemented globally
|
||||||
|
|
||||||
|
func (player *Player) sendDisguisedChatMessage(message TextComponent, typeID int32, senderName TextComponent, targetName *TextComponent) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addTextComponent(&buf, message)
|
||||||
|
addVarint(&buf, typeID)
|
||||||
|
addTextComponent(&buf, senderName)
|
||||||
|
hasTargetName := targetName != nil
|
||||||
|
addBool(&buf, hasTargetName)
|
||||||
|
if hasTargetName {
|
||||||
|
addTextComponent(&buf, *targetName)
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x1E, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendEntityEvent(entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addInt32(&buf, entity.ID)
|
||||||
|
addByte(&buf, byte(entity.status))
|
||||||
|
return player.sendPacket(0x1F, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendExplosion(explosion *Explosion) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
explosion.Position.add(&buf)
|
||||||
|
addFloat32(&buf, explosion.Strength)
|
||||||
|
addVarint(&buf, int32(len(explosion.AffectedBlockRecords)))
|
||||||
|
for _, affectedBlockRecord := range explosion.AffectedBlockRecords {
|
||||||
|
affectedBlockRecord.add(&buf)
|
||||||
|
}
|
||||||
|
explosion.PlayerMotion.add(&buf)
|
||||||
|
addVarint(&buf, explosion.BlockInteraction)
|
||||||
|
explosion.SmallExplosionParticle.addExplosion(&buf)
|
||||||
|
explosion.LargeExplosionParticle.addExplosion(&buf)
|
||||||
|
explosion.ExplosionSound.add(&buf)
|
||||||
|
return player.sendPacket(0x20, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendUnloadChunk(chunk *Chunk) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addInt32(&buf, chunk.Z)
|
||||||
|
addInt32(&buf, chunk.X)
|
||||||
|
return player.sendPacket(0x21, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendGameEvent(event uint8, value float32) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addByte(&buf, event)
|
||||||
|
addFloat32(&buf, value)
|
||||||
|
return player.sendPacket(0x22, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendHorseScreen(windowID uint8, slotCount int32, entityID int32) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addByte(&buf, windowID)
|
||||||
|
addVarint(&buf, slotCount)
|
||||||
|
addInt32(&buf, entityID)
|
||||||
|
return player.sendPacket(0x23, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example usage in sendHurtAnimation
|
||||||
|
func (player *Player) sendHurtAnimation(attacker *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, attacker.ID)
|
||||||
|
|
||||||
|
// Calculate yaw based on attacker and player positions
|
||||||
|
attackYaw := calculateYaw(attacker.Position, player.Position)
|
||||||
|
|
||||||
|
addFloat32(&buf, attackYaw)
|
||||||
|
|
||||||
|
return player.sendPacket(0x24, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendWorldBorder(border *WorldBorder) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addFloat64(&buf, border.X)
|
||||||
|
addFloat64(&buf, border.Z)
|
||||||
|
addFloat64(&buf, border.OldDiameter)
|
||||||
|
addFloat64(&buf, border.NewDiameter)
|
||||||
|
addVarlong(&buf, border.Speed)
|
||||||
|
addVarint(&buf, border.PortalTeleportBoundary)
|
||||||
|
addVarint(&buf, border.WarningBlocks)
|
||||||
|
addVarint(&buf, border.WarningTime)
|
||||||
|
return player.sendPacket(0x25, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
//0x26 is keepalive
|
||||||
|
//0x27 is chunk and light //TODO LATER
|
||||||
|
|
||||||
|
func (player *Player) sendWorldEvent(event int32, position BlockPosition, data int32, disableRelativeVolume bool) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addInt32(&buf, event)
|
||||||
|
position.add(&buf)
|
||||||
|
addInt32(&buf, data)
|
||||||
|
addBool(&buf, disableRelativeVolume)
|
||||||
|
return player.sendPacket(0x28, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendParticle(particle *Particle, count int32) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
particle.addPacket(&buf, count)
|
||||||
|
return player.sendPacket(0x29, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
//0x2A is Update light levels //TODO LATER
|
||||||
|
|
||||||
|
func (player *Player) sendPlayStart() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addInt32(&buf, player.Entity.ID)
|
||||||
|
addBool(&buf, world.Hardcore)
|
||||||
|
addVarint(&buf, int32(len(world.Dimensions)))
|
||||||
|
for _, dimension := range world.Dimensions {
|
||||||
|
addString(&buf, dimension.Name)
|
||||||
|
}
|
||||||
|
addVarint(&buf, maxPlayers)
|
||||||
|
addVarint(&buf, world.ViewDistance)
|
||||||
|
addVarint(&buf, world.SimulationDistance)
|
||||||
|
addBool(&buf, world.ReducedDebugInfo)
|
||||||
|
addBool(&buf, world.EnableRespawnScreen)
|
||||||
|
addBool(&buf, world.DoLimitedCrafting)
|
||||||
|
addVarint(&buf, player.Dimension.ID)
|
||||||
|
addString(&buf, player.Dimension.Name)
|
||||||
|
noise, err := world.Seed.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
addByte(&buf, noise[i])
|
||||||
|
}
|
||||||
|
addByte(&buf, player.GameMode)
|
||||||
|
addByte(&buf, byte(player.PreviousGameMode))
|
||||||
|
addBool(&buf, world.Debug)
|
||||||
|
addBool(&buf, world.Flat)
|
||||||
|
hasDeathLocation := player.DeathDimension != nil
|
||||||
|
addBool(&buf, hasDeathLocation)
|
||||||
|
if hasDeathLocation {
|
||||||
|
addString(&buf, player.DeathDimension.Name)
|
||||||
|
player.DeathPosition.add(&buf)
|
||||||
|
}
|
||||||
|
addVarint(&buf, player.Entity.PortalCooldown)
|
||||||
|
addBool(&buf, world.EnforceSecureChat)
|
||||||
|
return player.sendPacket(0x2B, &buf)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (player *Player) sendMapItemData(mapId int32) error {
|
||||||
|
// mapEntry := world.m
|
||||||
|
//}
|
494
prePlayPackets.go
Normal file
494
prePlayPackets.go
Normal file
@ -0,0 +1,494 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getHandshakePacket(data *bytes.Buffer) (version int32, address string, port uint16, nextState int32, err error) {
|
||||||
|
version, err = receiveVarint(data)
|
||||||
|
address, err = receiveString(data)
|
||||||
|
port = receiveUint16(data)
|
||||||
|
nextState, err = receiveVarint(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeTextComponent(buffer *bytes.Buffer, component TextComponent) (err error) {
|
||||||
|
// First, serialize the component to JSON
|
||||||
|
jsonData, err := json.Marshal(component)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addString(buffer, string(jsonData))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPingPacket(data *bytes.Buffer) (payload int64, err error) {
|
||||||
|
payload = receiveInt64(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLoginStart(data *bytes.Buffer) (userName string, playerUUID uuid.UUID, err error) {
|
||||||
|
userName, err = receiveString(data)
|
||||||
|
playerUUID, err = receiveUUID(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes encryption response data from the buffer.
|
||||||
|
func getEncryptionResponse(data *bytes.Buffer) (sharedSecret []byte, verifyToken []byte, err error) {
|
||||||
|
// Read sharedSecret length using VarInt
|
||||||
|
sharedSecretLen, err := receiveVarint(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read sharedSecret bytes
|
||||||
|
sharedSecret = make([]byte, sharedSecretLen)
|
||||||
|
_, err = data.Read(sharedSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read verifyToken length using VarInt
|
||||||
|
verifyTokenLen, err := receiveVarint(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read verifyToken bytes
|
||||||
|
verifyToken = make([]byte, verifyTokenLen)
|
||||||
|
_, err = data.Read(verifyToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sharedSecret, verifyToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClientInformation(data *bytes.Buffer) (locale string, viewDistance int8, chatMode int32, chatColors bool, skinParts uint8, isRightHanded bool, textFiltering bool, serverListing bool, err error) {
|
||||||
|
var offset int32
|
||||||
|
|
||||||
|
// Decode locale string
|
||||||
|
locale, err = receiveString(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode viewDistance
|
||||||
|
viewDistance = int8(data.Bytes()[offset])
|
||||||
|
offset++
|
||||||
|
|
||||||
|
// Decode chatMode
|
||||||
|
chatMode, err = receiveVarint(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode chatColors
|
||||||
|
chatColors, err = receiveBool(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode skinParts
|
||||||
|
skinParts = data.Bytes()[offset]
|
||||||
|
offset++
|
||||||
|
|
||||||
|
// Decode isRightHanded
|
||||||
|
mainHand, err := receiveVarint(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isRightHanded = mainHand == 1
|
||||||
|
|
||||||
|
// Decode textFiltering
|
||||||
|
textFiltering, err = receiveBool(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode serverListing
|
||||||
|
serverListing, err = receiveBool(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPluginPacket(inData *bytes.Buffer) (key string, data *bytes.Buffer, err error) {
|
||||||
|
key, err = receiveString(inData)
|
||||||
|
data = inData
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) getResourcePackPacket(data *bytes.Buffer) (err error) {
|
||||||
|
packUUID, err := receiveUUID(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := receiveVarint(data)
|
||||||
|
player.resourcePackResponse[packUUID] = result
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) getDataPacksPacket(data *bytes.Buffer) (err error) {
|
||||||
|
packCount, err := receiveVarint(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < int(packCount); i++ {
|
||||||
|
namespace, err := receiveString(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
id, err := receiveString(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version, err := receiveString(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
player.knownPacks = append(player.knownPacks, DatapackInfo{
|
||||||
|
Namespace: namespace,
|
||||||
|
ID: id,
|
||||||
|
Version: version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendPongPacket(payload int64) (err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addInt64(&buf, payload)
|
||||||
|
err = player.sendPacket(0x01, &buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendDisconnect(reason string) (err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addString(&buf, reason)
|
||||||
|
switch player.state {
|
||||||
|
case 2: //login
|
||||||
|
err = player.sendPacket(0x00, &buf)
|
||||||
|
case 3: //configuration
|
||||||
|
err = player.sendPacket(0x02, &buf)
|
||||||
|
case 4: //play
|
||||||
|
err = player.sendPacket(0x1D, &buf)
|
||||||
|
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (player *Player) sendEncryptionRequest() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Send an empty server ID
|
||||||
|
addVarint(&buf, int32(len(serverID))) // VarInt length of server ID (0)
|
||||||
|
if len(serverID) > 0 {
|
||||||
|
buf.Write(serverID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue with the rest of the packet (public key, token, etc.)
|
||||||
|
serverPubKeyBytes, err := ExportRSAPublicKey(serverPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyToken, err := generateRandomBytes(4)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
player.verifyToken = verifyToken
|
||||||
|
|
||||||
|
addVarint(&buf, int32(len(serverPubKeyBytes)))
|
||||||
|
buf.Write(serverPubKeyBytes)
|
||||||
|
|
||||||
|
addVarint(&buf, int32(len(player.verifyToken)))
|
||||||
|
buf.Write(player.verifyToken)
|
||||||
|
|
||||||
|
// Indicate that encryption is enabled
|
||||||
|
addBool(&buf, true)
|
||||||
|
|
||||||
|
// Send the packet with ID 0x01 (encryption request)
|
||||||
|
err = player.sendPacket(0x01, &buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendLoginSuccess() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addUUID(&buf, player.uuid)
|
||||||
|
|
||||||
|
addString(&buf, player.name)
|
||||||
|
|
||||||
|
addVarint(&buf, int32(len(player.profile.Properties)))
|
||||||
|
|
||||||
|
for _, property := range player.profile.Properties {
|
||||||
|
addString(&buf, property.Name)
|
||||||
|
addString(&buf, property.Value)
|
||||||
|
isSigned := len(property.Signature) > 0
|
||||||
|
addBool(&buf, isSigned)
|
||||||
|
if isSigned {
|
||||||
|
addString(&buf, property.Signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addBool(&buf, true)
|
||||||
|
|
||||||
|
return player.sendPacket(0x02, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendKeepAlive() error {
|
||||||
|
player.keepAlivePayload = rand.Int63()
|
||||||
|
player.keepAliveSentTimestamp = time.Now().Unix()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
addInt64(&buf, player.keepAlivePayload)
|
||||||
|
switch player.state {
|
||||||
|
case 3:
|
||||||
|
return player.sendPacket(0x04, &buf)
|
||||||
|
case 4:
|
||||||
|
return player.sendPacket(0x26, &buf)
|
||||||
|
}
|
||||||
|
return errors.New("invalid keep alive state")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendKeepAlives() {
|
||||||
|
for {
|
||||||
|
err := player.sendKeepAlive()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(18 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendPingPacket() error {
|
||||||
|
player.pingID = rand.Uint32()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addUint32(&buf, player.pingID)
|
||||||
|
player.pingSentTimestamp = time.Now().Unix()
|
||||||
|
switch player.state {
|
||||||
|
case 3:
|
||||||
|
return player.sendPacket(0x05, &buf)
|
||||||
|
case 4:
|
||||||
|
return player.sendPacket(0x36, &buf)
|
||||||
|
}
|
||||||
|
return errors.New("invalid ping state")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) getPongPacket(data *bytes.Buffer) (err error) {
|
||||||
|
pingID, err := receiveUint32(data)
|
||||||
|
if pingID == player.pingID {
|
||||||
|
player.pingReceivedTimestamp = time.Now().Unix()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendResetChat() error {
|
||||||
|
return player.sendPacket(0x06, &bytes.Buffer{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendRegistryData() (err error) {
|
||||||
|
for _, registryData := range world.RegistryDataThings {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addString(&buf, registryData.RegistryID)
|
||||||
|
addVarint(&buf, int32(len(registryData.Entries)))
|
||||||
|
for _, entry := range registryData.Entries {
|
||||||
|
addString(&buf, entry.EntryID)
|
||||||
|
addBool(&buf, entry.HasNBT)
|
||||||
|
if entry.HasNBT {
|
||||||
|
buf.ReadFrom(entry.NBTData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = player.sendPacket(0x07, &buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) removeResourcePack(packUUID uuid.UUID) (err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
hasUUID := packUUID != uuid.Nil
|
||||||
|
addBool(&buf, hasUUID)
|
||||||
|
|
||||||
|
if hasUUID {
|
||||||
|
addUUID(&buf, packUUID)
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x08, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) addResourcePack(packUUID uuid.UUID, url string, hash string, forced bool, message string) (err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
addUUID(&buf, packUUID)
|
||||||
|
addString(&buf, url)
|
||||||
|
addString(&buf, hash)
|
||||||
|
addBool(&buf, forced)
|
||||||
|
hasMessage := len(message) > 0
|
||||||
|
addBool(&buf, hasMessage)
|
||||||
|
if hasMessage {
|
||||||
|
addString(&buf, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return player.sendPacket(0x09, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendTransfer(host string, port uint16) (err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addString(&buf, host)
|
||||||
|
addVarint(&buf, int32(port))
|
||||||
|
switch player.state {
|
||||||
|
case 3:
|
||||||
|
return player.sendPacket(0x0B, &buf)
|
||||||
|
case 4:
|
||||||
|
return player.sendPacket(0x73, &buf)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendFeatureFlags() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, int32(len(world.FeatureFlags)))
|
||||||
|
for _, featureFlag := range world.FeatureFlags {
|
||||||
|
addString(&buf, featureFlag)
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x0C, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendUpdateTags() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, int32(len(world.UpdateTags)))
|
||||||
|
for _, updateTag := range world.UpdateTags {
|
||||||
|
addString(&buf, updateTag.TagRegistryIdentifier)
|
||||||
|
addVarint(&buf, int32(len(updateTag.Tags)))
|
||||||
|
for _, tag := range updateTag.Tags {
|
||||||
|
addString(&buf, tag.TagName)
|
||||||
|
addVarint(&buf, int32(len(tag.Entries)))
|
||||||
|
for _, entry := range tag.Entries {
|
||||||
|
addVarint(&buf, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x0D, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendDataPacks() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, int32(len(world.DataPacks)))
|
||||||
|
for _, dataPack := range world.DataPacks {
|
||||||
|
addString(&buf, dataPack.Namespace)
|
||||||
|
addString(&buf, dataPack.ID)
|
||||||
|
addString(&buf, dataPack.Version)
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x0E, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendReportDetails() (err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, int32(len(world.ReportDetails)))
|
||||||
|
for key, value := range world.ReportDetails {
|
||||||
|
addString(&buf, key)
|
||||||
|
addString(&buf, value)
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x0F, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendServerLinks() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, int32(len(world.ServerLinks)))
|
||||||
|
for _, serverLink := range world.ServerLinks {
|
||||||
|
if len(serverLink.LabelString) > 0 {
|
||||||
|
addBool(&buf, false)
|
||||||
|
addString(&buf, serverLink.LabelString)
|
||||||
|
} else {
|
||||||
|
addBool(&buf, true)
|
||||||
|
addVarint(&buf, serverLink.LabelInt)
|
||||||
|
}
|
||||||
|
addString(&buf, serverLink.URL)
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x10, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendPluginMessage(channel string, data *bytes.Buffer) (err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addString(&buf, channel)
|
||||||
|
_, err = buf.ReadFrom(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch player.state {
|
||||||
|
case 3:
|
||||||
|
return player.sendPacket(0x01, &buf)
|
||||||
|
case 4:
|
||||||
|
return player.sendPacket(0x19, &buf)
|
||||||
|
}
|
||||||
|
return errors.New("unknown player state")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendConfigurationFinish() error {
|
||||||
|
return player.sendPacket(0x03, &bytes.Buffer{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendCompression() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
addVarint(&buf, CompressionThreshold)
|
||||||
|
return player.sendPacket(0x03, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendStatusResponse(description string) (err error) {
|
||||||
|
if player.state == 1 {
|
||||||
|
var playerSamples []PlayerSample
|
||||||
|
for _, player := range world.Players {
|
||||||
|
if !player.serverListing || player.uuid == uuid.Nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
playerSamples = append(playerSamples, PlayerSample{
|
||||||
|
Name: player.name,
|
||||||
|
ID: player.uuid.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
status := ServerStatus{
|
||||||
|
Version: Version{
|
||||||
|
Name: VERSION,
|
||||||
|
Protocol: ProtocolVersion,
|
||||||
|
},
|
||||||
|
Players: Players{
|
||||||
|
Max: maxPlayers,
|
||||||
|
Online: len(world.Players) - 1,
|
||||||
|
Sample: playerSamples,
|
||||||
|
},
|
||||||
|
Description: Description{description},
|
||||||
|
Favicon: icon,
|
||||||
|
EnforcesSecureChat: false,
|
||||||
|
}
|
||||||
|
jsonData, err := json.Marshal(status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var outBuffer bytes.Buffer
|
||||||
|
addString(&outBuffer, string(jsonData))
|
||||||
|
err = player.sendPacket(0x00, &outBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
716
structs.go
Normal file
716
structs.go
Normal file
@ -0,0 +1,716 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/binary"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Version struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Protocol int `json:"protocol"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlayerSample struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Players struct {
|
||||||
|
Max int32 `json:"max"`
|
||||||
|
Online int `json:"online"`
|
||||||
|
Sample []PlayerSample `json:"sample"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Description struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerStatus struct {
|
||||||
|
Version Version `json:"version"`
|
||||||
|
Players Players `json:"players,omitempty"`
|
||||||
|
Description Description `json:"description,omitempty"`
|
||||||
|
Favicon string `json:"favicon,omitempty"`
|
||||||
|
EnforcesSecureChat bool `json:"enforcesSecureChat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Player struct {
|
||||||
|
conn net.Conn
|
||||||
|
|
||||||
|
state int32
|
||||||
|
version int32
|
||||||
|
requestedAddress string
|
||||||
|
requestedPort uint16
|
||||||
|
compressionThreshold int32
|
||||||
|
verifyToken []byte
|
||||||
|
decryptStream cipher.Stream
|
||||||
|
encryptStream cipher.Stream
|
||||||
|
reader *bufio.Reader
|
||||||
|
profile MojangProfile
|
||||||
|
transferred bool
|
||||||
|
resourcePackResponse map[uuid.UUID]int32
|
||||||
|
knownPacks []DatapackInfo
|
||||||
|
Entity *Entity
|
||||||
|
Dimension *Dimension
|
||||||
|
DeathDimension *Dimension
|
||||||
|
DeathPosition BlockPosition
|
||||||
|
|
||||||
|
Position EntityPosition
|
||||||
|
|
||||||
|
GameMode uint8
|
||||||
|
PreviousGameMode int8
|
||||||
|
|
||||||
|
locale string
|
||||||
|
viewDistance int8
|
||||||
|
chatMode int32
|
||||||
|
chatColors bool
|
||||||
|
skinParts uint8
|
||||||
|
isRightHanded bool
|
||||||
|
textFiltering bool
|
||||||
|
serverListing bool
|
||||||
|
|
||||||
|
keepAlivePayload int64
|
||||||
|
keepAliveSentTimestamp int64
|
||||||
|
keepAliveReceivedTimestamp int64
|
||||||
|
|
||||||
|
pingID uint32
|
||||||
|
pingSentTimestamp int64
|
||||||
|
pingReceivedTimestamp int64
|
||||||
|
statistics []StatisticPiece
|
||||||
|
|
||||||
|
lastContainerStateID int32
|
||||||
|
|
||||||
|
name string
|
||||||
|
uuid uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dimension struct {
|
||||||
|
ID int32
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type World struct {
|
||||||
|
Difficulty uint8
|
||||||
|
DifficultyLocked bool
|
||||||
|
|
||||||
|
Hardcore bool
|
||||||
|
Dimensions []Dimension
|
||||||
|
ViewDistance int32
|
||||||
|
SimulationDistance int32
|
||||||
|
ReducedDebugInfo bool
|
||||||
|
EnableRespawnScreen bool
|
||||||
|
DoLimitedCrafting bool
|
||||||
|
Seed uuid.UUID
|
||||||
|
Debug bool
|
||||||
|
Flat bool
|
||||||
|
EnforceSecureChat bool
|
||||||
|
|
||||||
|
Entities []Entity
|
||||||
|
Players []Player
|
||||||
|
RegistryDataThings []RegistryData
|
||||||
|
FeatureFlags []string
|
||||||
|
UpdateTags []UpdateTag
|
||||||
|
DataPacks []DatapackInfo
|
||||||
|
ReportDetails map[string]string
|
||||||
|
ServerLinks []ServerLink
|
||||||
|
|
||||||
|
Maps map[int32]Map
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatisticPiece struct {
|
||||||
|
CategoryID int32
|
||||||
|
StatisticID int32
|
||||||
|
Value int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityPosition struct {
|
||||||
|
X float64
|
||||||
|
Y float64
|
||||||
|
Z float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type FloatOffset struct {
|
||||||
|
X float32
|
||||||
|
Y float32
|
||||||
|
Z float32
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockPosition struct {
|
||||||
|
X int32
|
||||||
|
Y int16
|
||||||
|
Z int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type AffectedBlockRecord struct {
|
||||||
|
OffsetX int8
|
||||||
|
OffsetY int8
|
||||||
|
OffsetZ int8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (affectedBlockRecord *AffectedBlockRecord) add(buf *bytes.Buffer) {
|
||||||
|
addByte(buf, byte(affectedBlockRecord.OffsetX))
|
||||||
|
addByte(buf, byte(affectedBlockRecord.OffsetY))
|
||||||
|
addByte(buf, byte(affectedBlockRecord.OffsetZ))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExplosionSound struct {
|
||||||
|
Name string
|
||||||
|
FixedRange float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (explosionSound *ExplosionSound) add(buf *bytes.Buffer) {
|
||||||
|
addString(buf, explosionSound.Name)
|
||||||
|
hasFixedRange := explosionSound.FixedRange > 0
|
||||||
|
addBool(buf, hasFixedRange)
|
||||||
|
if hasFixedRange {
|
||||||
|
addFloat32(buf, explosionSound.FixedRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Particle struct {
|
||||||
|
ParticleID int32
|
||||||
|
BlockState int32 // Used by block-related particles
|
||||||
|
FromColor [3]float32 // Used for color particles (RGB)
|
||||||
|
ToColor [3]float32 // Used for dust_color_transition particles (RGB)
|
||||||
|
Scale float32 // Used for dust/dust_color_transition particles
|
||||||
|
PositionSourceType int32 // Used for vibration particles
|
||||||
|
BlockPosition BlockPosition // Used for vibration particles (minecraft:block)
|
||||||
|
EntityID int32 // Used for vibration particles (minecraft:entity)
|
||||||
|
EntityEyeHeight float32 // Used for vibration particles (minecraft:entity)
|
||||||
|
Ticks int32 // Used for vibration particles
|
||||||
|
Delay int32 // Used for shriek particles
|
||||||
|
|
||||||
|
Count int32
|
||||||
|
LongDistance bool
|
||||||
|
Position EntityPosition
|
||||||
|
Offset FloatOffset
|
||||||
|
MaxSpeed float32
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorldBorder struct {
|
||||||
|
X float64
|
||||||
|
Z float64
|
||||||
|
OldDiameter float64
|
||||||
|
NewDiameter float64
|
||||||
|
Speed int64
|
||||||
|
PortalTeleportBoundary int32
|
||||||
|
WarningBlocks int32
|
||||||
|
WarningTime int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute yaw between two positions
|
||||||
|
func calculateYaw(attackerPos, playerPos EntityPosition) float32 {
|
||||||
|
deltaX := attackerPos.X - playerPos.X
|
||||||
|
deltaZ := attackerPos.Z - playerPos.Z
|
||||||
|
|
||||||
|
// atan2 returns the angle in radians, we convert it to degrees
|
||||||
|
yaw := math.Atan2(deltaZ, deltaX) * (180 / math.Pi)
|
||||||
|
|
||||||
|
// In Minecraft, yaw 0 points towards positive Z, so we adjust the yaw to this system
|
||||||
|
yaw = -yaw + 90
|
||||||
|
|
||||||
|
// Normalize yaw to be between 0 and 360 degrees
|
||||||
|
if yaw < 0 {
|
||||||
|
yaw += 360
|
||||||
|
}
|
||||||
|
|
||||||
|
return float32(yaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeARGB(color [3]float32) int32 {
|
||||||
|
// Encodes RGB color values into an ARGB int (alpha assumed to be 255)
|
||||||
|
r := int32(color[0] * 255)
|
||||||
|
g := int32(color[1] * 255)
|
||||||
|
b := int32(color[2] * 255)
|
||||||
|
a := int32(255)
|
||||||
|
return (a << 24) | (r << 16) | (g << 8) | b
|
||||||
|
}
|
||||||
|
|
||||||
|
func clampScale(scale float32) float32 {
|
||||||
|
// Clamps the scale between 0.01 and 4.0
|
||||||
|
if scale < 0.01 {
|
||||||
|
return 0.01
|
||||||
|
}
|
||||||
|
if scale > 4.0 {
|
||||||
|
return 4.0
|
||||||
|
}
|
||||||
|
return scale
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Particle) addPacket(buf *bytes.Buffer, count int32) {
|
||||||
|
addBool(buf, p.LongDistance)
|
||||||
|
p.Position.add(buf)
|
||||||
|
p.Offset.add(buf)
|
||||||
|
addFloat32(buf, p.MaxSpeed)
|
||||||
|
addInt32(buf, count)
|
||||||
|
addInt32(buf, p.ParticleID)
|
||||||
|
|
||||||
|
// Handle based on particle ID
|
||||||
|
switch p.ParticleID {
|
||||||
|
case 1, 2, 28, 105: // minecraft:block, minecraft:block_marker, falling_dust, dust_pillar
|
||||||
|
addVarint(buf, p.BlockState)
|
||||||
|
|
||||||
|
case 13: // minecraft:dust
|
||||||
|
addFloat32(buf, p.FromColor[0])
|
||||||
|
addFloat32(buf, p.FromColor[1])
|
||||||
|
addFloat32(buf, p.FromColor[2])
|
||||||
|
addFloat32(buf, clampScale(p.Scale))
|
||||||
|
|
||||||
|
case 14: // minecraft:dust_color_transition
|
||||||
|
addFloat32(buf, p.FromColor[0])
|
||||||
|
addFloat32(buf, p.FromColor[1])
|
||||||
|
addFloat32(buf, p.FromColor[2])
|
||||||
|
addFloat32(buf, p.ToColor[0])
|
||||||
|
addFloat32(buf, p.ToColor[1])
|
||||||
|
addFloat32(buf, p.ToColor[2])
|
||||||
|
addFloat32(buf, clampScale(p.Scale))
|
||||||
|
|
||||||
|
case 20: // minecraft:entity_effect
|
||||||
|
addInt32(buf, encodeARGB(p.FromColor))
|
||||||
|
|
||||||
|
case 45: // minecraft:vibration
|
||||||
|
addVarint(buf, p.PositionSourceType)
|
||||||
|
if p.PositionSourceType == 0 {
|
||||||
|
p.BlockPosition.add(buf)
|
||||||
|
} else if p.PositionSourceType == 1 {
|
||||||
|
addVarint(buf, p.EntityID)
|
||||||
|
addFloat32(buf, p.EntityEyeHeight)
|
||||||
|
}
|
||||||
|
addVarint(buf, p.Ticks)
|
||||||
|
|
||||||
|
case 99: // minecraft:shriek
|
||||||
|
addVarint(buf, p.Delay)
|
||||||
|
|
||||||
|
// Particles with no additional data
|
||||||
|
case 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 100, 101, 102, 103, 104, 106, 107, 108:
|
||||||
|
// No additional data required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Particle) addExplosion(buf *bytes.Buffer) {
|
||||||
|
addInt32(buf, p.ParticleID)
|
||||||
|
|
||||||
|
// Handle based on particle ID
|
||||||
|
switch p.ParticleID {
|
||||||
|
case 1, 2, 28, 105: // minecraft:block, minecraft:block_marker, falling_dust, dust_pillar
|
||||||
|
addVarint(buf, p.BlockState)
|
||||||
|
|
||||||
|
case 13: // minecraft:dust
|
||||||
|
addFloat32(buf, p.FromColor[0])
|
||||||
|
addFloat32(buf, p.FromColor[1])
|
||||||
|
addFloat32(buf, p.FromColor[2])
|
||||||
|
addFloat32(buf, clampScale(p.Scale))
|
||||||
|
|
||||||
|
case 14: // minecraft:dust_color_transition
|
||||||
|
addFloat32(buf, p.FromColor[0])
|
||||||
|
addFloat32(buf, p.FromColor[1])
|
||||||
|
addFloat32(buf, p.FromColor[2])
|
||||||
|
addFloat32(buf, p.ToColor[0])
|
||||||
|
addFloat32(buf, p.ToColor[1])
|
||||||
|
addFloat32(buf, p.ToColor[2])
|
||||||
|
addFloat32(buf, clampScale(p.Scale))
|
||||||
|
|
||||||
|
case 20: // minecraft:entity_effect
|
||||||
|
addInt32(buf, encodeARGB(p.FromColor))
|
||||||
|
|
||||||
|
case 45: // minecraft:vibration
|
||||||
|
addVarint(buf, p.PositionSourceType)
|
||||||
|
if p.PositionSourceType == 0 {
|
||||||
|
p.BlockPosition.add(buf)
|
||||||
|
} else if p.PositionSourceType == 1 {
|
||||||
|
addVarint(buf, p.EntityID)
|
||||||
|
addFloat32(buf, p.EntityEyeHeight)
|
||||||
|
}
|
||||||
|
addVarint(buf, p.Ticks)
|
||||||
|
|
||||||
|
case 99: // minecraft:shriek
|
||||||
|
addVarint(buf, p.Delay)
|
||||||
|
|
||||||
|
// Particles with no additional data
|
||||||
|
case 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 100, 101, 102, 103, 104, 106, 107, 108:
|
||||||
|
// No additional data required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Explosion struct {
|
||||||
|
Position EntityPosition
|
||||||
|
Strength float32
|
||||||
|
AffectedBlockRecords []AffectedBlockRecord
|
||||||
|
PlayerMotion EntityPosition
|
||||||
|
BlockInteraction int32
|
||||||
|
SmallExplosionParticle Particle
|
||||||
|
LargeExplosionParticle Particle
|
||||||
|
ExplosionSound ExplosionSound
|
||||||
|
}
|
||||||
|
|
||||||
|
type Slot struct {
|
||||||
|
Count int32
|
||||||
|
ItemID int32
|
||||||
|
ComponentsToAdd map[int32]any
|
||||||
|
ComponentsToRemove []int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slot *Slot) add(buf *bytes.Buffer) {
|
||||||
|
addVarint(buf, slot.Count)
|
||||||
|
if slot.Count > 0 {
|
||||||
|
addVarint(buf, slot.ItemID)
|
||||||
|
addVarint(buf, int32(len(slot.ComponentsToAdd)))
|
||||||
|
addVarint(buf, int32(len(slot.ComponentsToRemove)))
|
||||||
|
for componentID, component := range slot.ComponentsToAdd {
|
||||||
|
addVarint(buf, componentID)
|
||||||
|
component = component //TODO IMPLEMENT ALL THESE STUPID COMPONENTS
|
||||||
|
}
|
||||||
|
for _, componentID := range slot.ComponentsToRemove {
|
||||||
|
addVarint(buf, componentID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PalettedContainer represents a palette-based storage of entries.
|
||||||
|
type PalettedContainer struct {
|
||||||
|
BitsPerEntry uint8
|
||||||
|
Palette []int32
|
||||||
|
DataArray []uint64 // Packed data for blocks or biomes
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddEntry adds an entry (block or biome) to the palette and updates the DataArray.
|
||||||
|
func (p *PalettedContainer) AddEntry(entry int32, index int) {
|
||||||
|
// Check if the entry already exists in the palette.
|
||||||
|
for i, val := range p.Palette {
|
||||||
|
if val == entry {
|
||||||
|
// Entry already exists, no need to addExplosion to palette.
|
||||||
|
p.setEntryAtIndex(i, index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new entry to the palette.
|
||||||
|
p.Palette = append(p.Palette, entry)
|
||||||
|
|
||||||
|
// Use the index of the new entry in the palette and store it in the DataArray.
|
||||||
|
p.setEntryAtIndex(len(p.Palette)-1, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setEntryAtIndex sets the palette index for the block/biome at the specified index in the DataArray.
|
||||||
|
func (p *PalettedContainer) setEntryAtIndex(paletteIndex int, index int) {
|
||||||
|
// Each entry is packed into the DataArray with the specified BitsPerEntry.
|
||||||
|
entriesPerLong := 64 / p.BitsPerEntry
|
||||||
|
longIndex := index / int(entriesPerLong)
|
||||||
|
bitIndex := (index % int(entriesPerLong)) * int(p.BitsPerEntry)
|
||||||
|
|
||||||
|
// Clear the bits at the specified location before setting the new value.
|
||||||
|
mask := uint64(math.MaxUint64>>(64-p.BitsPerEntry)) << bitIndex
|
||||||
|
p.DataArray[longIndex] &= ^mask
|
||||||
|
|
||||||
|
// Set the new value.
|
||||||
|
p.DataArray[longIndex] |= (uint64(paletteIndex) & (math.MaxUint64 >> (64 - p.BitsPerEntry))) << bitIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// add serializes the PalettedContainer to a buffer for network transmission.
|
||||||
|
func (p *PalettedContainer) add(buf *bytes.Buffer) error {
|
||||||
|
// Write BitsPerEntry
|
||||||
|
buf.WriteByte(p.BitsPerEntry)
|
||||||
|
|
||||||
|
// Write Palette
|
||||||
|
if len(p.Palette) > 0 {
|
||||||
|
// Write the length of the palette as a VarInt.
|
||||||
|
addVarint(buf, int32(len(p.Palette)))
|
||||||
|
// Write each palette entry as a VarInt.
|
||||||
|
for _, entry := range p.Palette {
|
||||||
|
addVarint(buf, entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Write a palette length of 0 for a global palette.
|
||||||
|
addVarint(buf, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write DataArray
|
||||||
|
// First, write the length of the DataArray (in terms of the number of longs).
|
||||||
|
addVarint(buf, int32(len(p.DataArray)))
|
||||||
|
// Write each uint64 in the DataArray as a little-endian long.
|
||||||
|
for _, data := range p.DataArray {
|
||||||
|
err := binary.Write(buf, binary.BigEndian, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChunkSection represents a 16x16x16 section of a chunk.
|
||||||
|
type ChunkSection struct {
|
||||||
|
BlockCount uint16 // Non-air block count
|
||||||
|
BlockStates PalettedContainer // Block palette and states
|
||||||
|
Biomes PalettedContainer // Biome palette and data
|
||||||
|
}
|
||||||
|
|
||||||
|
// add serializes the ChunkSection into the buffer.
|
||||||
|
func (cs *ChunkSection) add(buf *bytes.Buffer) error {
|
||||||
|
err := binary.Write(buf, binary.BigEndian, cs.BlockCount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write BlockStates and Biomes.
|
||||||
|
err = cs.BlockStates.add(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cs.Biomes.add(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chunk represents a chunk column, containing multiple chunk sections.
|
||||||
|
type Chunk struct {
|
||||||
|
X int32 // Chunk X coordinate
|
||||||
|
Z int32 // Chunk Z coordinate
|
||||||
|
Sections []ChunkSection // Chunk sections (16x16x16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add serializes the Chunk into the buffer.
|
||||||
|
func (c *Chunk) add(buf *bytes.Buffer) error {
|
||||||
|
err := binary.Write(buf, binary.BigEndian, c.X)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Write(buf, binary.BigEndian, c.Z)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the number of sections as a VarInt.
|
||||||
|
addVarint(buf, int32(len(c.Sections)))
|
||||||
|
|
||||||
|
// Write each chunk section.
|
||||||
|
for _, section := range c.Sections {
|
||||||
|
err := section.add(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Block struct {
|
||||||
|
Position BlockPosition
|
||||||
|
BlockEntityType int32
|
||||||
|
BlockTypeID int32
|
||||||
|
BlockStateID int32
|
||||||
|
BlockEntityData map[string]any
|
||||||
|
DestroyStage uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type BossBar struct {
|
||||||
|
UUID uuid.UUID
|
||||||
|
Title TextComponent
|
||||||
|
Health float32
|
||||||
|
Color int32
|
||||||
|
Division int32
|
||||||
|
Flags uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bossBar BossBar) add(player *Player) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addUUID(&buf, bossBar.UUID)
|
||||||
|
addVarint(&buf, 0)
|
||||||
|
addTextComponent(&buf, bossBar.Title)
|
||||||
|
addFloat32(&buf, bossBar.Health)
|
||||||
|
addVarint(&buf, bossBar.Color)
|
||||||
|
addVarint(&buf, bossBar.Division)
|
||||||
|
addByte(&buf, bossBar.Flags)
|
||||||
|
return player.sendPacket(0x0A, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bossBar BossBar) remove(player *Player) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addUUID(&buf, bossBar.UUID)
|
||||||
|
addVarint(&buf, 1)
|
||||||
|
return player.sendPacket(0x0A, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bossBar BossBar) updateHealth(player *Player) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addUUID(&buf, bossBar.UUID)
|
||||||
|
addVarint(&buf, 2)
|
||||||
|
addFloat32(&buf, bossBar.Health)
|
||||||
|
return player.sendPacket(0x0A, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bossBar BossBar) updateTitle(player *Player) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addUUID(&buf, bossBar.UUID)
|
||||||
|
addVarint(&buf, 3)
|
||||||
|
addTextComponent(&buf, bossBar.Title)
|
||||||
|
return player.sendPacket(0x0A, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bossBar BossBar) updateStyle(player *Player) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addUUID(&buf, bossBar.UUID)
|
||||||
|
addVarint(&buf, 4)
|
||||||
|
addVarint(&buf, bossBar.Color)
|
||||||
|
addVarint(&buf, bossBar.Division)
|
||||||
|
return player.sendPacket(0x0A, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bossBar BossBar) updateFlags(player *Player) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addUUID(&buf, bossBar.UUID)
|
||||||
|
addVarint(&buf, 5)
|
||||||
|
addByte(&buf, bossBar.Flags)
|
||||||
|
return player.sendPacket(0x0A, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (blockPosition *BlockPosition) add(buf *bytes.Buffer) {
|
||||||
|
var xBuf, yBuf, zBuf []byte
|
||||||
|
binary.BigEndian.PutUint32(xBuf, uint32(blockPosition.X))
|
||||||
|
binary.BigEndian.PutUint16(yBuf, uint16(blockPosition.Y))
|
||||||
|
binary.BigEndian.PutUint32(zBuf, uint32(blockPosition.Z))
|
||||||
|
|
||||||
|
buf.Write(xBuf[0:26])
|
||||||
|
buf.Write(zBuf[0:26])
|
||||||
|
buf.Write(yBuf[0:12])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entityPosition *EntityPosition) add(buf *bytes.Buffer) {
|
||||||
|
addFloat64(buf, entityPosition.X)
|
||||||
|
addFloat64(buf, entityPosition.Y)
|
||||||
|
addFloat64(buf, entityPosition.Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (floatOffset *FloatOffset) add(buf *bytes.Buffer) {
|
||||||
|
addFloat32(buf, floatOffset.X)
|
||||||
|
addFloat32(buf, floatOffset.Y)
|
||||||
|
addFloat32(buf, floatOffset.Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityRotation struct {
|
||||||
|
Pitch uint8
|
||||||
|
Yaw uint8
|
||||||
|
HeadYaw uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entityRotation *EntityRotation) add(buf *bytes.Buffer) {
|
||||||
|
addByte(buf, entityRotation.Pitch)
|
||||||
|
addByte(buf, entityRotation.Yaw)
|
||||||
|
addByte(buf, entityRotation.HeadYaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Velocity struct {
|
||||||
|
X float32
|
||||||
|
Y float32
|
||||||
|
Z float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (velocity *Velocity) add(buf *bytes.Buffer) {
|
||||||
|
addFloat32(buf, velocity.X)
|
||||||
|
addFloat32(buf, velocity.Y)
|
||||||
|
addFloat32(buf, velocity.Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entity struct {
|
||||||
|
ID int32
|
||||||
|
UUID uuid.UUID
|
||||||
|
Type int32
|
||||||
|
Position EntityPosition
|
||||||
|
Rotation EntityRotation
|
||||||
|
Data int32
|
||||||
|
Velocity Velocity
|
||||||
|
Animation uint8
|
||||||
|
status int8
|
||||||
|
PortalCooldown int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entity *Entity) add(buf *bytes.Buffer) {
|
||||||
|
addVarint(buf, entity.ID)
|
||||||
|
addUUID(buf, entity.UUID)
|
||||||
|
addVarint(buf, entity.Type)
|
||||||
|
entity.Position.add(buf)
|
||||||
|
entity.Rotation.add(buf)
|
||||||
|
addVarint(buf, entity.Data)
|
||||||
|
entity.Velocity.add(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
type XPOrb struct {
|
||||||
|
ID int32
|
||||||
|
Position EntityPosition
|
||||||
|
Count int16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (XPOrb *XPOrb) add(buf *bytes.Buffer) {
|
||||||
|
addVarint(buf, XPOrb.ID)
|
||||||
|
XPOrb.Position.add(buf)
|
||||||
|
addInt16(buf, XPOrb.Count)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MapIcon struct {
|
||||||
|
Type int32
|
||||||
|
X int8
|
||||||
|
Z int8
|
||||||
|
Direction int8
|
||||||
|
DisplayName *TextComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MapIcon *MapIcon) add(buf *bytes.Buffer) {
|
||||||
|
addVarint(buf, MapIcon.Type)
|
||||||
|
addByte(buf, byte(MapIcon.X))
|
||||||
|
addByte(buf, byte(MapIcon.Z))
|
||||||
|
addByte(buf, byte(MapIcon.Direction))
|
||||||
|
hasTextComponent := MapIcon.DisplayName != nil
|
||||||
|
addBool(buf, hasTextComponent)
|
||||||
|
if hasTextComponent {
|
||||||
|
addTextComponent(buf, *MapIcon.DisplayName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Map struct {
|
||||||
|
ID int32
|
||||||
|
Scale int8
|
||||||
|
Locked bool
|
||||||
|
Icons []MapIcon
|
||||||
|
|
||||||
|
MapUpdateDataW uint8
|
||||||
|
MapUpdateDataH uint8
|
||||||
|
MapUpdateDataX uint8
|
||||||
|
MapUpdateDataZ uint8
|
||||||
|
MapUpdateData []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Map *Map) add(buf *bytes.Buffer) {
|
||||||
|
addVarint(buf, Map.ID)
|
||||||
|
addByte(buf, byte(Map.Scale))
|
||||||
|
addBool(buf, Map.Locked)
|
||||||
|
|
||||||
|
iconCount := int32(len(Map.Icons))
|
||||||
|
|
||||||
|
addBool(buf, iconCount > 0)
|
||||||
|
if iconCount > 0 {
|
||||||
|
addVarint(buf, iconCount)
|
||||||
|
for _, mapIcon := range Map.Icons {
|
||||||
|
mapIcon.add(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addByte(buf, Map.MapUpdateDataH)
|
||||||
|
if Map.MapUpdateDataH > 0 {
|
||||||
|
addByte(buf, Map.MapUpdateDataW)
|
||||||
|
addByte(buf, Map.MapUpdateDataX)
|
||||||
|
addByte(buf, Map.MapUpdateDataZ)
|
||||||
|
addVarint(buf, int32(len(Map.MapUpdateData)))
|
||||||
|
for _, mapUpdatePiece := range Map.MapUpdateData {
|
||||||
|
addByte(buf, mapUpdatePiece)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user