configuration protocol support draft
This commit is contained in:
@ -41,11 +41,3 @@ To get the first of each primary version: `go get github.com/Tnze/go-mc@v1.19.0`
|
|||||||
|
|
||||||
- Run `go run github.com/Tnze/go-mc/cmd/mcping localhost` to ping and list the localhost mc server.
|
- Run `go run github.com/Tnze/go-mc/cmd/mcping localhost` to ping and list the localhost mc server.
|
||||||
- Run `go run github.com/Tnze/go-mc/cmd/daze` to join the local server at *localhost:25565* as player named Daze on the offline mode.
|
- Run `go run github.com/Tnze/go-mc/cmd/daze` to join the local server at *localhost:25565* as player named Daze on the offline mode.
|
||||||
|
|
||||||
## Supported Version
|
|
||||||
|
|
||||||
As the `go-mc/net` package implements the minecraft network protocol, there is no update between the versions at this
|
|
||||||
level. So net package actually supports any version. It's just that the ID and content of the package are different
|
|
||||||
between different versions.
|
|
||||||
|
|
||||||
由于`go-mc/net`实现的是MC底层的网络协议,而这个协议在MC更新时其实并不会有改动,MC更新时其实只是包的ID和内容的定义发生了变化,所以net包本身是跨版本的。
|
|
||||||
|
67
bot/configuration.go
Normal file
67
bot/configuration.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
|
"github.com/Tnze/go-mc/nbt"
|
||||||
|
"github.com/Tnze/go-mc/net"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) joinConfiguration(conn *net.Conn) error {
|
||||||
|
receiving := "config custom payload"
|
||||||
|
for {
|
||||||
|
var p pk.Packet
|
||||||
|
if err := conn.ReadPacket(&p); err != nil {
|
||||||
|
return LoginErr{receiving, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch packetid.ClientboundPacketID(p.ID) {
|
||||||
|
case packetid.ClientboundConfigCustomPayload:
|
||||||
|
var channel pk.Identifier
|
||||||
|
var data pk.PluginMessageData
|
||||||
|
err := p.Scan(&channel, &data)
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"custom payload", err}
|
||||||
|
}
|
||||||
|
// TODO: Provide configuration custom data handling interface
|
||||||
|
|
||||||
|
case packetid.ClientboundConfigDisconnect:
|
||||||
|
case packetid.ClientboundConfigFinishConfiguration:
|
||||||
|
err := conn.WritePacket(pk.Marshal(
|
||||||
|
packetid.ServerboundConfigFinishConfiguration,
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"finish config", err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case packetid.ClientboundConfigKeepAlive:
|
||||||
|
var keepAliveID pk.Long
|
||||||
|
err := p.Scan(&keepAliveID)
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"keep alive", err}
|
||||||
|
}
|
||||||
|
// send it back
|
||||||
|
err = conn.WritePacket(pk.Marshal(
|
||||||
|
packetid.ServerboundConfigKeepAlive,
|
||||||
|
keepAliveID,
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"keep alive", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
case packetid.ClientboundConfigPing:
|
||||||
|
case packetid.ClientboundConfigRegistryData:
|
||||||
|
var registryCodec nbt.RawMessage
|
||||||
|
err := p.Scan(pk.NBT(®istryCodec))
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"registry data", err}
|
||||||
|
}
|
||||||
|
// TODO: Handle registries
|
||||||
|
|
||||||
|
case packetid.ClientboundConfigResourcePack:
|
||||||
|
case packetid.ClientboundConfigUpdateEnabledFeatures:
|
||||||
|
case packetid.ClientboundConfigUpdateTags:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
96
bot/login.go
96
bot/login.go
@ -15,12 +15,106 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/chat"
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
"github.com/Tnze/go-mc/net"
|
"github.com/Tnze/go-mc/net"
|
||||||
"github.com/Tnze/go-mc/net/CFB8"
|
"github.com/Tnze/go-mc/net/CFB8"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *Client) joinLogin(conn *net.Conn) error {
|
||||||
|
var err error
|
||||||
|
if c.Auth.UUID != "" {
|
||||||
|
c.UUID, err = uuid.Parse(c.Auth.UUID)
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"login start", err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = conn.WritePacket(pk.Marshal(
|
||||||
|
packetid.ServerboundLoginStart,
|
||||||
|
pk.String(c.Auth.Name),
|
||||||
|
pk.UUID(c.UUID),
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"login start", err}
|
||||||
|
}
|
||||||
|
receiving := "encrypt start"
|
||||||
|
for {
|
||||||
|
// Receive Packet
|
||||||
|
var p pk.Packet
|
||||||
|
if err = conn.ReadPacket(&p); err != nil {
|
||||||
|
return LoginErr{receiving, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Packet
|
||||||
|
switch packetid.ClientboundPacketID(p.ID) {
|
||||||
|
case packetid.ClientboundLoginDisconnect: // LoginDisconnect
|
||||||
|
var reason chat.Message
|
||||||
|
err = p.Scan(&reason)
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"disconnect", err}
|
||||||
|
}
|
||||||
|
return LoginErr{"disconnect", DisconnectErr(reason)}
|
||||||
|
|
||||||
|
case packetid.ClientboundLoginEncryptionRequest: // Encryption Request
|
||||||
|
if err := handleEncryptionRequest(conn, c, p); err != nil {
|
||||||
|
return LoginErr{"encryption", err}
|
||||||
|
}
|
||||||
|
receiving = "set compression"
|
||||||
|
|
||||||
|
case packetid.ClientboundLoginSuccess: // Login Success
|
||||||
|
err := p.Scan(
|
||||||
|
(*pk.UUID)(&c.UUID),
|
||||||
|
(*pk.String)(&c.Name),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"login success", err}
|
||||||
|
}
|
||||||
|
err = conn.WritePacket(pk.Marshal(packetid.ServerboundLoginAcknowledged))
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"login success", err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case packetid.ClientboundLoginCompression: // Set Compression
|
||||||
|
var threshold pk.VarInt
|
||||||
|
if err := p.Scan(&threshold); err != nil {
|
||||||
|
return LoginErr{"compression", err}
|
||||||
|
}
|
||||||
|
conn.SetThreshold(int(threshold))
|
||||||
|
receiving = "login success"
|
||||||
|
|
||||||
|
case packetid.ClientboundLoginPluginRequest: // Login Plugin Request
|
||||||
|
var (
|
||||||
|
msgid pk.VarInt
|
||||||
|
channel pk.Identifier
|
||||||
|
data pk.PluginMessageData
|
||||||
|
)
|
||||||
|
if err := p.Scan(&msgid, &channel, &data); err != nil {
|
||||||
|
return LoginErr{"Login Plugin", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var PluginMessageData pk.Option[pk.PluginMessageData, *pk.PluginMessageData]
|
||||||
|
if handler, ok := c.LoginPlugin[string(channel)]; ok {
|
||||||
|
PluginMessageData.Has = true
|
||||||
|
PluginMessageData.Val, err = handler(data)
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"Login Plugin", err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conn.WritePacket(pk.Marshal(
|
||||||
|
packetid.ServerboundLoginPluginResponse,
|
||||||
|
msgid, PluginMessageData,
|
||||||
|
)); err != nil {
|
||||||
|
return LoginErr{"login Plugin", err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Auth includes an account
|
// Auth includes an account
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
Name string
|
Name string
|
||||||
@ -196,7 +290,7 @@ func genEncryptionKeyResponse(shareSecret, publicKey, verifyToken []byte) (erp p
|
|||||||
return erp, err
|
return erp, err
|
||||||
}
|
}
|
||||||
return pk.Marshal(
|
return pk.Marshal(
|
||||||
packetid.LoginEncryptionResponse,
|
packetid.ServerboundLoginEncryptionResponse,
|
||||||
pk.ByteArray(cryptPK),
|
pk.ByteArray(cryptPK),
|
||||||
pk.ByteArray(verifyT),
|
pk.ByteArray(verifyT),
|
||||||
), nil
|
), nil
|
||||||
|
95
bot/mcbot.go
95
bot/mcbot.go
@ -10,10 +10,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/chat"
|
"github.com/Tnze/go-mc/chat"
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
|
||||||
mcnet "github.com/Tnze/go-mc/net"
|
mcnet "github.com/Tnze/go-mc/net"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
"github.com/Tnze/go-mc/net/queue"
|
"github.com/Tnze/go-mc/net/queue"
|
||||||
@ -22,7 +19,7 @@ import (
|
|||||||
|
|
||||||
// ProtocolVersion is the protocol version number of minecraft net protocol
|
// ProtocolVersion is the protocol version number of minecraft net protocol
|
||||||
const (
|
const (
|
||||||
ProtocolVersion = 763
|
ProtocolVersion = 764
|
||||||
DefaultPort = mcnet.DefaultPort
|
DefaultPort = mcnet.DefaultPort
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -110,90 +107,18 @@ func (c *Client) join(addr string, options JoinOptions) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return LoginErr{"handshake", err}
|
return LoginErr{"handshake", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login Start
|
// Login Start
|
||||||
c.UUID, err = uuid.Parse(c.Auth.UUID)
|
if err := c.joinLogin(conn); err != nil {
|
||||||
PlayerUUID := pk.Option[pk.UUID, *pk.UUID]{
|
return err
|
||||||
Has: err == nil,
|
|
||||||
Val: pk.UUID(c.UUID),
|
|
||||||
}
|
}
|
||||||
err = conn.WritePacket(pk.Marshal(
|
|
||||||
packetid.LoginStart,
|
// Configuration
|
||||||
pk.String(c.Auth.Name),
|
if err := c.joinConfiguration(conn); err != nil {
|
||||||
PlayerUUID,
|
return err
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return LoginErr{"login start", err}
|
|
||||||
}
|
|
||||||
receiving := "encrypt start"
|
|
||||||
for {
|
|
||||||
// Receive Packet
|
|
||||||
var p pk.Packet
|
|
||||||
if err = conn.ReadPacket(&p); err != nil {
|
|
||||||
return LoginErr{receiving, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Packet
|
|
||||||
switch p.ID {
|
|
||||||
case packetid.LoginDisconnect: // LoginDisconnect
|
|
||||||
var reason chat.Message
|
|
||||||
err = p.Scan(&reason)
|
|
||||||
if err != nil {
|
|
||||||
return LoginErr{"disconnect", err}
|
|
||||||
}
|
|
||||||
return LoginErr{"disconnect", DisconnectErr(reason)}
|
|
||||||
|
|
||||||
case packetid.LoginEncryptionRequest: // Encryption Request
|
|
||||||
if err := handleEncryptionRequest(conn, c, p); err != nil {
|
|
||||||
return LoginErr{"encryption", err}
|
|
||||||
}
|
|
||||||
receiving = "set compression"
|
|
||||||
|
|
||||||
case packetid.LoginSuccess: // Login Success
|
|
||||||
err := p.Scan(
|
|
||||||
(*pk.UUID)(&c.UUID),
|
|
||||||
(*pk.String)(&c.Name),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return LoginErr{"login success", err}
|
|
||||||
}
|
|
||||||
c.Conn = warpConn(conn, options.QueueRead, options.QueueWrite)
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case packetid.LoginCompression: // Set Compression
|
|
||||||
var threshold pk.VarInt
|
|
||||||
if err := p.Scan(&threshold); err != nil {
|
|
||||||
return LoginErr{"compression", err}
|
|
||||||
}
|
|
||||||
conn.SetThreshold(int(threshold))
|
|
||||||
receiving = "login success"
|
|
||||||
|
|
||||||
case packetid.LoginPluginRequest: // Login Plugin Request
|
|
||||||
var (
|
|
||||||
msgid pk.VarInt
|
|
||||||
channel pk.Identifier
|
|
||||||
data pk.PluginMessageData
|
|
||||||
)
|
|
||||||
if err := p.Scan(&msgid, &channel, &data); err != nil {
|
|
||||||
return LoginErr{"Login Plugin", err}
|
|
||||||
}
|
|
||||||
|
|
||||||
var PluginMessageData pk.Option[pk.PluginMessageData, *pk.PluginMessageData]
|
|
||||||
if handler, ok := c.LoginPlugin[string(channel)]; ok {
|
|
||||||
PluginMessageData.Has = true
|
|
||||||
PluginMessageData.Val, err = handler(data)
|
|
||||||
if err != nil {
|
|
||||||
return LoginErr{"Login Plugin", err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := conn.WritePacket(pk.Marshal(
|
|
||||||
packetid.LoginPluginResponse,
|
|
||||||
msgid, PluginMessageData,
|
|
||||||
)); err != nil {
|
|
||||||
return LoginErr{"login Plugin", err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
c.Conn = warpConn(conn, options.QueueRead, options.QueueWrite)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginErr struct {
|
type LoginErr struct {
|
||||||
|
@ -94,7 +94,7 @@ func pingAndList(ctx context.Context, addr string, conn *mcnet.Conn) (data []byt
|
|||||||
// LIST
|
// LIST
|
||||||
// 请求服务器状态
|
// 请求服务器状态
|
||||||
err = conn.WritePacket(pk.Marshal(
|
err = conn.WritePacket(pk.Marshal(
|
||||||
packetid.StatusRequest,
|
packetid.ServerboundStatusRequest,
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, fmt.Errorf("bot: send list packect fail: %v", err)
|
return nil, 0, fmt.Errorf("bot: send list packect fail: %v", err)
|
||||||
@ -114,7 +114,7 @@ func pingAndList(ctx context.Context, addr string, conn *mcnet.Conn) (data []byt
|
|||||||
// PING
|
// PING
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
err = conn.WritePacket(pk.Marshal(
|
err = conn.WritePacket(pk.Marshal(
|
||||||
packetid.StatusPingRequest,
|
packetid.ServerboundStatusPingRequest,
|
||||||
pk.Long(startTime.Unix()),
|
pk.Long(startTime.Unix()),
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -79,7 +79,7 @@ func Encrypt(conn *net.Conn, name string, serverKey *rsa.PrivateKey) (*Resp, err
|
|||||||
|
|
||||||
func encryptionRequest(conn *net.Conn, publicKey, verifyToken []byte) error {
|
func encryptionRequest(conn *net.Conn, publicKey, verifyToken []byte) error {
|
||||||
return conn.WritePacket(pk.Marshal(
|
return conn.WritePacket(pk.Marshal(
|
||||||
packetid.LoginEncryptionRequest,
|
packetid.ClientboundLoginEncryptionRequest,
|
||||||
pk.String(""),
|
pk.String(""),
|
||||||
pk.ByteArray(publicKey),
|
pk.ByteArray(publicKey),
|
||||||
pk.ByteArray(verifyToken),
|
pk.ByteArray(verifyToken),
|
||||||
@ -92,7 +92,7 @@ func encryptionResponse(conn *net.Conn, serverKey *rsa.PrivateKey, verifyToken [
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if p.ID != packetid.LoginEncryptionResponse {
|
if packetid.ServerboundPacketID(p.ID) != packetid.ServerboundLoginEncryptionResponse {
|
||||||
return nil, fmt.Errorf("0x%02X is not Encryption Response", p.ID)
|
return nil, fmt.Errorf("0x%02X is not Encryption Response", p.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,8 +96,8 @@ func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name s
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if p.ID != packetid.LoginStart {
|
if packetid.ServerboundPacketID(p.ID) != packetid.ServerboundLoginStart {
|
||||||
err = wrongPacketErr{expect: packetid.LoginStart, get: p.ID}
|
err = wrongPacketErr{expect: int32(packetid.ServerboundLoginStart), get: p.ID}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name s
|
|||||||
// set compression
|
// set compression
|
||||||
if d.Threshold >= 0 {
|
if d.Threshold >= 0 {
|
||||||
err = conn.WritePacket(pk.Marshal(
|
err = conn.WritePacket(pk.Marshal(
|
||||||
packetid.LoginCompression,
|
packetid.ClientboundLoginCompression,
|
||||||
pk.VarInt(d.Threshold),
|
pk.VarInt(d.Threshold),
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -152,7 +152,7 @@ func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name s
|
|||||||
}
|
}
|
||||||
// send login success
|
// send login success
|
||||||
err = conn.WritePacket(pk.Marshal(
|
err = conn.WritePacket(pk.Marshal(
|
||||||
packetid.LoginSuccess,
|
packetid.ClientboundLoginSuccess,
|
||||||
pk.UUID(id),
|
pk.UUID(id),
|
||||||
pk.String(name),
|
pk.String(name),
|
||||||
pk.Array(properties),
|
pk.Array(properties),
|
||||||
|
@ -59,15 +59,15 @@ func (s *Server) acceptListPing(conn *net.Conn, clientProtocol int32) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch p.ID {
|
switch packetid.ClientboundPacketID(p.ID) {
|
||||||
case packetid.StatusResponse: // List
|
case packetid.ClientboundStatusResponse: // List
|
||||||
var resp []byte
|
var resp []byte
|
||||||
resp, err = s.listResp(clientProtocol)
|
resp, err = s.listResp(clientProtocol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
err = conn.WritePacket(pk.Marshal(0x00, pk.String(resp)))
|
err = conn.WritePacket(pk.Marshal(0x00, pk.String(resp)))
|
||||||
case packetid.StatusPongResponse: // Ping
|
case packetid.ClientboundStatusPongResponse: // Ping
|
||||||
err = conn.WritePacket(p)
|
err = conn.WritePacket(p)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -80,7 +80,7 @@ func (s *Server) AcceptConn(conn *net.Conn) {
|
|||||||
var loginErr LoginFailErr
|
var loginErr LoginFailErr
|
||||||
if errors.As(err, &loginErr) {
|
if errors.As(err, &loginErr) {
|
||||||
_ = conn.WritePacket(pk.Marshal(
|
_ = conn.WritePacket(pk.Marshal(
|
||||||
packetid.LoginDisconnect,
|
packetid.ClientboundLoginDisconnect,
|
||||||
loginErr.reason,
|
loginErr.reason,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user